在C#中获取属性值(反射)的最快方法

6

我想知道从对象属性中获取值的最快方法(仅针对此问题)是什么?

经过一些搜索,我在这个网站上看到了@MarkGravell的帖子

他写了这段代码:

using System;
using System.Reflection;
using System.Reflection.Emit;

public class Foo
{
    public Foo(int bar)
    {
        Bar = bar;
    }
    private int Bar { get; set; }
}
static class Program {
    static void Main()
    {
        var method = new DynamicMethod("cheat", typeof(int),
            new[] { typeof(object) }, typeof(Foo), true);
        var il = method.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Castclass, typeof(Foo));
        il.Emit(OpCodes.Callvirt, typeof(Foo).GetProperty("Bar",
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
            ).GetGetMethod(true));
        il.Emit(OpCodes.Ret);
        var func = (Func<object, int>)method.CreateDelegate(
            typeof(Func<object, int>));

        var obj = new Foo(123);
        Console.WriteLine(func(obj));
    }
}

var method = typeof(Foo).GetProperty("Bar",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                                                  .GetGetMethod(true);
var func = (Func<Foo, int>)
Delegate.CreateDelegate(typeof(Func<Foo, int>), method);

我把它改成了
var pt = propertyInfo.PropertyType; // I dont know what is Type
var method = pt.GetProperty("Bar",
    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                                                      .GetGetMethod(true);
var func = (Func<Foo, object>) // I dont know what is return type so set object !!!
Delegate.CreateDelegate(typeof(Func<Foo, object>), method); // I want get value as object ?!!!
return func(entity).ToString(); // cast return value to string

但是我遇到了一个异常错误

 Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.

我不知道我的属性类型是什么,它可以是任何东西。如何为此自定义代码?如果没有属性类型限制,有人能帮我以更好的方式(最快的方式)吗?请介绍一下。

请查看我在相关问题中的答案。 - Sriram Sakthivel
1
你知道你的类型有一个名为Bar的属性,但不知道它的实际类型吗?这听起来像是糟糕的设计,应该为此实现一个公共接口。 - MakePeaceGreatAgain
@HimBromBeere - 问题在这里(Func<Foo,T>)。必须将T设置为与属性类型相同,但如何设置?像这样:var pt = propertyInfo.PropertyType; (Func<Foo,pt>) !!!!!!!!!!! - user3153878
1个回答

13
Delegate.CreateDelegate在这种情况下不起作用,因为你必须将结果委托转换为某个已知类型,否则你只有DynamicInvoke,它不比直接调用PropertyInfo更好(请参见Marc Gravell的解释)。
我看到的最通用的方法不涉及Lambda表达式(就像Sriram Sakthivel提议的那样),而是由Jon Skeet在这里展示的。在他的方法和我们可以从PropertyInfo获取实际属性返回类型这一事实的基础上,我们可以发明一些定制化的属性调用方法。
首先,我们定义一个接口:
public interface IPropertyCallAdapter<TThis>
{
    object InvokeGet(TThis @this);
    //add void InvokeSet(TThis @this, object value) if necessary
}

然后,接口的实现:

public class PropertyCallAdapter<TThis, TResult> : IPropertyCallAdapter<TThis>
{
    private readonly Func<TThis, TResult> _getterInvocation;

    public PropertyCallAdapter(Func<TThis, TResult> getterInvocation)
    {
        _getterInvocation = getterInvocation;
    }

    public object InvokeGet(TThis @this)
    {
        return _getterInvocation.Invoke(@this);
    }
}

InvokeGet方法看起来大部分像Jon Skeet使用的方法。

现在,来到“神奇”的部分。我们定义了一个服务,它将构建并缓存提供程序的实例。它看起来像这样:

public class PropertyCallAdapterProvider<TThis>
{
    private static readonly Dictionary<string, IPropertyCallAdapter<TThis>> _instances =
        new Dictionary<string,IPropertyCallAdapter<TThis>>();

    public static IPropertyCallAdapter<TThis> GetInstance(string forPropertyName)
    {
        IPropertyCallAdapter<TThis> instance;
        if (!_instances.TryGetValue(forPropertyName, out instance))
        {
            var property = typeof(TThis).GetProperty(
                forPropertyName,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            MethodInfo getMethod;
            Delegate getterInvocation = null;
            if (property != null && (getMethod = property.GetGetMethod(true)) != null)
            {
                var openGetterType = typeof(Func<,>);
                var concreteGetterType = openGetterType
                    .MakeGenericType(typeof(TThis), property.PropertyType);

                getterInvocation =
                    Delegate.CreateDelegate(concreteGetterType, null, getMethod);
            }
            else
            {
                //throw exception or create a default getterInvocation returning null
            }

            var openAdapterType = typeof(PropertyCallAdapter<,>);
            var concreteAdapterType = openAdapterType
                .MakeGenericType(typeof(TThis), property.PropertyType);
            instance = Activator
                .CreateInstance(concreteAdapterType, getterInvocation)
                    as IPropertyCallAdapter<TThis>;

            _instances.Add(forPropertyName, instance);
        }

        return instance;
    }
}

在不知道编译时确切的 TResult 类型的情况下,我们创建适配器并缓存它以备将来使用,以避免未来需要进行大量反射调用。

就这样。您可以按照以下方式使用它:

PropertyCallAdapterProvider<Foo>.GetInstance("Bar").InvokeGet(fooInstance)

此外,如果需要,您可以很容易地将其扩展到属性设置器。

在我的电脑上,当适配器实例从提供程序预取后进入循环时,使用各种方法访问获取器十百万次的结果如下:

  • 直接调用用时141毫秒
  • 适配器调用用时244毫秒
  • 反射调用用时1800毫秒
  • 动态委托调用用时8179毫秒

为什么要使用@this?在这种情况下,@代表什么意思? - Noir
@Fly_NighT 'this' 是 C# 中的保留字。因此,如果您想直接将其用作参数/变量名,则无法使用它。在开头加上 '@',您可以使其成为可能,因此很明显参数的作用,例如 this.InvokeGet()。但是它也可以被称为其他任何名称 - 如 'self'、'owner'、'subject' 等。 - galenus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接