欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

使用反射 快速访问属性

程序员文章站 2022-05-27 23:01:53
...

反射非常适合访问任意类型的所有属性(或运行时命名的任意属性)。但是,Reflection的性能成本在关键代码路径中是不可接受的。相反,您可以添加前期成本并创建通用委托来读取或写入此类属性而不会产生任何开销(取决于您对值的操作,甚至可以避免装箱)。

这种代码的典型目标是获取具有指定名称的属性的值。

这种天真(和最慢)的方式是直接反射:

string name
object o;

object value = o.GetType().GetProperty(name).GetValue(o);

这样做的所有工作都是定位属性并在每次调用时动态调用其getter。您可以通过缓存每个来改善这一点PropertyInfo

var properties = new Dictionary<string, PropertyInfo>();

PropertyInfo property;
if (!properties.TryGetValue(name, out property))
	properties.Add(name, (property = o.GetType().GetProperty(o)));
value = property.GetValue(o);

(如果涉及多个线程,则需要[ThreadStatic]或者a ConcurrentDictionary

但是,这仍然重复动态调用getter方法的工作。您可以通过创建指向get方法的委托来消除此问题。创建委托仍然需要一些代价高昂的反映,但一旦创建,调用它将与调用任何其他委托一样快。

var getters = new Dictionary<string, Func<T, object>();

Func<T, object> getter;
if (!getters.TryGetValue(name, out getter)) {
	getter = (Func<T, object>)Delegate.CreateDelegate(
		typeof(Func<T, object>),
		typeof(T).GetProperty(name).GetMethod
	);
	getters.Add(name, getter);
}

T o;
value = getter(o);

这使用委托返回类型协方差Func<..., object>从getter方法创建一个返回任何更多派生类型的方法。这个特征实际上早于一般协方差; 它甚至可以在.Net 2.0上运行。

这种方法有一些警告。这只有在您知道在编译时操作的对象类型时才有效(例如,在绑定或序列化参数的通用方法中)。如果你object在编译时运行s,这是行不通的,因为这样的委托不是类型安全的。

这也不适用于返回值类型的属性。方差是有效的,因为类型的运行时表示完全相同,因此JITted代码不知道或不关心实际的类型参数是不同的。值类型需要与引用类型不同的codegen,因此无法开始工作。

要解决这些问题,您可以使用非常方便的方式生成实际的运行时代码Expression<T>具体来说,您需要创建一个将object参数强制转换为的表达式树T(如果您在编译时不知道实际类型),然后将该属性的结果打包成object(如果它是值类型)。

var param = Expression.Parameter(typeof(T));
getter = Expression.Lambda<Func<T, object>>(
	Expression.Convert(
		Expression.Property(param, name),
		typeof(object)
	),
	param
).Compile();

所述Expression.Convert()如果节点将自动产生一个装箱转换T为值类型,使得这项工作。只有T值类型才需要整个块如果它是引用类型,则可以使用前面的示例跳过整个运行时codegen阶段。

英文

Reflection is great for accessing all properties (or an arbitrary property named at runtime) of an arbitrary type. However, Reflection has performance costs which can be unacceptable in critical codepaths. Instead, you can add an upfront cost and create generic delegates to read or write such properties without any overhead at all (depending on what you’re doing with the values, you can even avoid boxing).

The typical goal for this kind of code is to get the value of a property with the specified name.

The naive (and slowest) way to do this is straight-up reflection:

string name
object o;

object value = o.GetType().GetProperty(name).GetValue(o);

This does all of the work of locating the property and dynamically invoking its getter on every call. You can improve this by caching each PropertyInfo:

var properties = new Dictionary<string, PropertyInfo>();

PropertyInfo property;
if (!properties.TryGetValue(name, out property))
	properties.Add(name, (property = o.GetType().GetProperty(o)));
value = property.GetValue(o);

(if multiple threads are involved, you’ll need either [ThreadStatic] or a ConcurrentDictionary)

However, this still repeats the work of dynamically invoking the getter method. You can eliminate this by creating a delegate that points to the get method. Creating the delegate still involves some costly reflection, but once it’s created, calling it will be as fast as calling any other delegate.

var getters = new Dictionary<string, Func<T, object>();

Func<T, object> getter;
if (!getters.TryGetValue(name, out getter)) {
	getter = (Func<T, object>)Delegate.CreateDelegate(
		typeof(Func<T, object>),
		typeof(T).GetProperty(name).GetMethod
	);
	getters.Add(name, getter);
}

T o;
value = getter(o);

This uses delegate return type covariance to create a Func<..., object> from a getter method that returns any more-derived type. This feature actually predates generic covariance; it will work even on .Net 2.0.

This approach has some caveats. This can only work if you know the type of the objects you’re operating on at compile-time (eg, in a generic method to bind or serialize parameters). If you’re operating on objects at compile time, this cannot work, since such a delegate wouldn’t be type-safe.

This also won’t work for properties that return value types. Variance works because the runtime representation of the types are completely identical, so that the JITted code doesn’t know or care that the actual type parameter is different. Value types require different codegen than reference types, so this cannot begin to work.

To solve these problems, you can do actual runtime code generation, using the ever-handy Expression<T>. Specifically, you need to create an expression tree that casts an object parameter to T (if you don’t know the actual type at compile time), then boxes the result of the property into an object (if it’s a value type).

var param = Expression.Parameter(typeof(T));
getter = Expression.Lambda<Func<T, object>>(
	Expression.Convert(
		Expression.Property(param, name),
		typeof(object)
	),
	param
).Compile();

The Expression.Convert() node will automatically generate a boxing conversion if T is a value type, making this work. This whole block is only necessary if T is a value type; if it’s a reference type, you can skip the entire runtime codegen phase with the previous example.

 

性能测试代码

   public class Class1<T>
    {
     

        public Dictionary<string, Func<T, object>> getters = new Dictionary<string, Func<T, object>>();
        public Dictionary<string, Func<T, object>> getters1 = new Dictionary<string, Func<T, object>>();
        public object a(T o, string name)
        {
            Func<T, object> getter;
            if (!getters.TryGetValue(name, out getter))
            {
                getter = (Func<T, object>)Delegate.CreateDelegate(
                    typeof(Func<T, object>),
                    typeof(T).GetProperty(name).GetMethod

                );
                getters.Add(name, getter);
            }
            return getter(o);
        }
        Dictionary<string, PropertyInfo> properties = new Dictionary<string, PropertyInfo>();
        public object b(T o, string name)
        {

            PropertyInfo property;
            if (!properties.TryGetValue(name, out property))
                properties.Add(name, (property = o.GetType().GetProperty(name)));
            return property.GetValue(o);
        }

        public object c(T o, string name)
        {
            Func<T, object> getter;
            if (!getters1.TryGetValue(name, out getter))
            {
                ParameterExpression param = Expression.Parameter(typeof(T));
                getter = Expression.Lambda<Func<T, object>>(
                    Expression.Convert(
                        Expression.Property(param, name),
                        typeof(object)
                    ),
                    param
                ).Compile();
                getters1.Add(name, getter);
            }

            return getter(o);
        }
    }

 调用

class Program
{
    static void Main()
    {

        S s = new S { Name = "qq" };
        Class1<S> c = new Class1<S>();
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int i = 0; i < 100000; i++)
        {
            c.a(s, "Name").ToString();

        }
        Console.WriteLine(stopwatch.ElapsedTicks);
     
        stopwatch.Restart();
        for (int i = 0; i < 100000; i++)
        {
            c.b(s, "Name").ToString();

        }
        Console.WriteLine(stopwatch.ElapsedTicks);
        stopwatch.Restart();
        for (int i = 0; i < 100000; i++)
        {
          c.c(s, "Name").ToString();

        }
        Console.WriteLine(stopwatch.ElapsedTicks);
        stopwatch.Stop();
        Console.ReadKey();

    }
    public class S
    {
        public string Name { get; set; }
    }

 结论:

耗时

26471

83130

143730

 

 

相关标签: 反射