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

发布于 2024-10-17 07:42:52 字数 1871 浏览 5 评论 0原文

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

,因此如果我有类 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)

So

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 等可能会很好并且更优化,但似乎您会得到很多需要以这种方式实现的方法,并且语法并不完全漂亮,所以也许更好地接受并强制转换object 而不是使用 as 通用索引器可能会很好,但可惜 c# 不支持它们。

另一个问题是 GetValue 和 SetValue 会有多少开销,实现它们的类需要针对不同的字段名有一个 switch 或 if-else 分支。尽管我认为如果我使用 OrdinalIgnore 情况,它不应该增加那么多开销。也许有更好的解决方案,但我想不出一个,哈希表似乎更昂贵。 恩特波 为了避免手动创建适配器类的繁琐,我认为一个不错的解决方案是在运行时动态生成代码并编译它们(也许使用 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 表达式的速度慢一倍,我们讨论的只是毫秒,因此可能值得使用它来避免设置适配器。

I have a solution where i need to take different classes and access it's properties by name

so if I have for instance the classes Horse and Cat, i need to be able to access them through a generic class, say Adapter, like

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);

}

This is not difficult per se and there's a host of threads of stackoverflow on how to do it, you can use everything from reflection to dynamic. However in my case I need to optimize both performance and memory (don't ask why :)

To get around the dynamic performance penalties my strategy is to build an adapter interface , say IAdapter , which implements

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

So

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;
}

}

Then each class that needs it implements that interface. The question is how to best solve a couple of things, first type conversion. It would perhaps be nice and more optimized to have GetInt, GetString etc etc but it seems you'll get alot of methods that you need to implement that way and the syntax isn't exactly beautiful so perhaps better to take the hit and cast the object instead using as A generic indexer would perhaps have been nice but alas c# doesn't support them.

Another question is how much overhead the GetValue and SetValue will have, the classes that implements them needs to have a switch or if-else branch for different fieldnames. It shouldn't add that much of an overhead though I think if I use OrdinalIgnore case. Maybe there's a better solution but I can't think of one, a HashTable seems more expensive.
ntepo
To avoid the tediousness of manually creating the adapter classes I was thinking that a nice solution would be to generate the code for and compile them dynamically runtime(perhaps using CodeDom).

What do you think, what's the most elegant solution for a problem like this with high performance?

Benchmarks

I tested four different approaches of a large number of objects and five different properties. "Normal" property access, "Adapter" property access, Getting property using reflection and lastly the Linq Expression method described in an answer below

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

Seems like using an adapter is marginally slower then straight on properties, LINQ Expressions are about twice as slow and reflection is ten times as slow.

Even if LINQ Expressions are twice as slow it's milliseconds we're talking about so it might be worth using to avoid having to setup adapters.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

一笔一画续写前缘 2024-10-24 07:42:52

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

You could use the LinqExpression classes:

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);
    }
}

This example is somewhat simplified because it can only access properties of type string and it support no setters. But it should be a good starting point.

For each type that you encounter at run-time, you have to create a PropertyAccessor instance. It then caches a compiled expression for each property name accessed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文