高性能属性访问和可能的动态编译

时间:2011-02-13 11:46:10

标签: c# .net performance codedom

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

所以如果我有类 Horse Cat ,我需要能够通过泛型类来访问它们,比如说适配器,比如

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等等,它可能会更好,更优化但是看起来你会得到很多你需要实现的方法,而且语法不是很漂亮所以也许更好地采取命中并施放而不是使用作为一个通用索引器可能会很好但是a#c#不支持它们。

另一个问题是GetValue和SetValue会有多少开销,实现它们的类需要为不同的字段名分配一个switch或if-else分支。如果我使用OrdinalIgnore案例,我认为它不应该增加很多开销。也许有一个更好的解决方案,但我想不出一个,HashTable似乎更昂贵。 ntepo 为了避免手动创建适配器类的繁琐,我认为一个很好的解决方案是生成代码并动态编译它们(可能使用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表达式的速度是我们所讨论的毫秒的两倍,因此可能值得使用以避免必须设置适配器。

1 个答案:

答案 0 :(得分:4)

您可以使用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 实例。然后它为访问的每个属性名称缓存一个已编译的表达式。