【问题标题】:Performant property access and possible dynamic compiling高性能属性访问和可能的动态编译
【发布时间】:2011-06-26 10:00:00
【问题描述】:

我有一个解决方案,我需要采用不同的类并按名称访问它的属性

所以如果我有例如 HorseCat 类,我需要能够通过通用类访问它们,比如 Adapter,比如

HorseAdapter  adapter = new HorseAdapter();

public SomeMethod()
{
    Horse horse =  new Horse();
    DoStuff(horse, adapter);
}

public DoStuff(object obj, IAdapter  adapter)
{
    int speed = (int)adapter.GetValue(obj,"speed");
    string name = adapter.GetValue(obj,"name") as string;
    adapter.SetValue(obj,"gender",true);

}

这本身并不难,并且有很多关于如何做到这一点的 stackoverflow 线程,您可以使用从反射到动态的所有内容。但是在我的情况下,我需要优化性能和内存(不要问为什么:)

为了避免动态性能损失,我的策略是构建一个适配器接口,比如 IAdapter,它实现了

 object GetValue(object obj,string fieldName) 
 SetValue(object obj,string fieldName,object value)

所以

public class HorseAdapter :  IAdapter
{
..

public override GetValue(object obj, string fieldName)
{
   Horse horse = object as Horse,
   if (fieldName == "name")
      return horse.Name;
   else if (fieldName == "speed")
     return horse.Speed;
}

}

然后每个需要它的类都实现该接口。问题是如何最好地解决几件事,首先是类型转换。拥有 GetInt、GetString 等可能会更好并且更优化,但看起来你会得到很多你需要以这种方式实现的方法,而且语法并不完全漂亮,所以也许更好地接受打击并施放对象而不是使用 as 通用索引器可能会很好,但可惜 c# 不支持它们。

另一个问题是 GetValue 和 SetValue 会有多少开销,实现它们的类需要为不同的字段名有一个 switch 或 if-else 分支。尽管我认为如果我使用 OrdinalIgnore 案例,它不应该增加那么多开销。也许有更好的解决方案,但我想不出一个,HashTable 似乎更昂贵。 恩泰波 为了避免手动创建适配器类的乏味,我认为一个不错的解决方案是生成代码并在运行时动态编译它们(可能使用 CodeDom)。

您认为,对于此类问题,高性能的最佳解决方案是什么?

基准测试

我针对大量对象和五个不同属性测试了四种不同的方法。 “正常”属性访问,“适配器”属性访问,使用反射获取属性,最后是下面答案中描述的 Linq 表达式方法

elapsed time normal properties:468 ms
elapsed time reflection properties:4657 ms
elapsed time adapter properties:551 ms
elapsed time expression properties:1041 ms

似乎使用适配器比直接使用属性要慢一些,LINQ 表达式的速度大约是前者的两倍,而反射的速度是后者的十倍。

即使 LINQ 表达式的速度是我们谈论的毫秒数的两倍,所以它可能值得使用以避免必须设置适配器。

【问题讨论】:

  • 您可以使用泛型:GetValue<int>
  • 为什么不能让 Horse/Cat 类实现相同的interface IAnimal{string Name{get;set;} int Speed{get;set;}}?这可能会带来最好的性能。
  • @vlad:“适应”的类应该是完全独立的,不知道任何接口等
  • 好的,那么适配器可以实现相同的接口。但是,您需要显式定义每个适配器,因为与 C++ 的模板不同,泛型不支持鸭子类型。

标签: c# .net performance codedom


【解决方案1】:

您可以使用 LinqExpression 类:

public class PropertyAccessor
{
    Dictionary<string, Func<object, string>> _accessors = new Dictionary<string,Func<object,string>>();
    Type _type;

    public PropertyAccessor(Type t)
    {
        _type = t;
    }


    public string GetProperty(object obj, string propertyName)
    {
        Func<object, string> accessor;

        if (!_accessors.ContainsKey(propertyName))
        {
            ParameterExpression objExpr = Expression.Parameter(typeof(object), "obj");
            Expression e = Expression.Convert(objExpr, _type);
            e = Expression.Property(e, propertyName);
            Expression<Func<object, string>> expr = Expression.Lambda<Func<object, string>>(e, objExpr);
            accessor = expr.Compile();
            _accessors[propertyName] = accessor;
        }
        else
        {
            accessor = _accessors[propertyName];
        }

        return accessor(obj);
    }
}

这个例子有些简化,因为它只能访问 string 类型的属性,并且不支持 setter。但这应该是一个很好的起点。

对于您在运行时遇到的每种类型,您都必须创建一个 PropertyAccessor 实例。然后它为每个访问的属性名称缓存一个编译表达式。

【讨论】:

  • 哇,这真是太棒了。必须对此进行调查并运行一些性能测试,谢谢!
  • 我刚刚在代码中添加了一行:编译后的访问器从未添加到字典中。
猜你喜欢
  • 2010-11-24
  • 1970-01-01
  • 2012-04-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-14
相关资源
最近更新 更多