C#反射:更新属性值最快的方法是什么?

21

这是使用反射更新属性的最快方式吗?假设该属性始终为int类型:

PropertyInfo counterPropertyInfo = GetProperty();
int value = (int)counterPropertyInfo.GetValue(this, null);
counterPropertyInfo.SetValue(this, value + 1, null);

4
我看到你将“this”作为实例传递。为什么不直接设置属性呢? - Maxim
3
最快的方法是实现一个IIncrementable接口并使用它,而不是使用反射。 - parapura rajkumar
3个回答

28

我在这里进行了一些基准测试,当你知道类型参数时(非泛型方法不会有太大区别)。如果无法直接访问属性,则CreateDelegate是最快的方法。使用CreateDelegate可以直接获取PropertyInfoGetGetMethodGetSetMethod句柄,因此反射不会每次都被使用。

public static Func<S, T> BuildGetAccessor<S, T>(Expression<Func<S, T>> propertySelector)
{
    return propertySelector.GetPropertyInfo().GetGetMethod().CreateDelegate<Func<S, T>>();
}

public static Action<S, T> BuildSetAccessor<S, T>(Expression<Func<S, T>> propertySelector)
{
    return propertySelector.GetPropertyInfo().GetSetMethod().CreateDelegate<Action<S, T>>();
}

// a generic extension for CreateDelegate
public static T CreateDelegate<T>(this MethodInfo method) where T : class
{
    return Delegate.CreateDelegate(typeof(T), method) as T;
}

public static PropertyInfo GetPropertyInfo<S, T>(this Expression<Func<S, T>> propertySelector)
{
    var body = propertySelector.Body as MemberExpression;
    if (body == null)
        throw new MissingMemberException("something went wrong");

    return body.Member as PropertyInfo;
}

现在你需要调用:

TestClass cwp = new TestClass();
var access = BuildGetAccessor((TestClass t) => t.AnyValue);
var result = access(cwp);

或者更好的方法是将逻辑封装在专用类中,以便在其中具有获取和设置方法。

就像这样:

public class Accessor<S>
{
    public static Accessor<S, T> Create<T>(Expression<Func<S, T>> memberSelector)
    {
        return new GetterSetter<T>(memberSelector);
    }

    public Accessor<S, T> Get<T>(Expression<Func<S, T>> memberSelector)
    {
        return Create(memberSelector);
    }

    public Accessor()
    {

    }

    class GetterSetter<T> : Accessor<S, T>
    {
        public GetterSetter(Expression<Func<S, T>> memberSelector) : base(memberSelector)
        {

        }
    }
}

public class Accessor<S, T> : Accessor<S>
{
    Func<S, T> Getter;
    Action<S, T> Setter;

    public bool IsReadable { get; private set; }
    public bool IsWritable { get; private set; }
    public T this[S instance]
    {
        get
        {
            if (!IsReadable)
                throw new ArgumentException("Property get method not found.");

            return Getter(instance);
        }
        set
        {
            if (!IsWritable)
                throw new ArgumentException("Property set method not found.");

            Setter(instance, value);
        }
    }

    protected Accessor(Expression<Func<S, T>> memberSelector) //access not given to outside world
    {
        var prop = memberSelector.GetPropertyInfo();
        IsReadable = prop.CanRead;
        IsWritable = prop.CanWrite;
        AssignDelegate(IsReadable, ref Getter, prop.GetGetMethod());
        AssignDelegate(IsWritable, ref Setter, prop.GetSetMethod());
    }

    void AssignDelegate<K>(bool assignable, ref K assignee, MethodInfo assignor) where K : class
    {
        if (assignable)
            assignee = assignor.CreateDelegate<K>();
    }
}

简短而简单。您可以为每个“类属性”对携带一个此类的实例以进行获取/设置。

用法:

Person p = new Person { Age = 23 };
var ageAccessor = Accessor<Person>(x => x.Age);
int age = ageAccessor[p]; //gets 23
ageAccessor[p] = 45; //sets 45

这里对索引器的使用有些不太好,你可以用专门的“Get”和“Set”方法来替换它,但对我来说很直观 :)

为了避免每次都要指定类型,可以像下面这样:

var ageAccessor = Accessor<Person>(x => x.Age);
var nameAccessor = Accessor<Person>(x => x.Name);
var placeAccessor = Accessor<Person>(x => x.Place);

我将基础Accessor<>类改变成可实例化的,这意味着你可以进行以下操作:
var personAccessor = new Accessor<Person>();
var ageAccessor = personAccessor.Get(x => x.Age);
var nameAccessor = personAccessor.Get(x => x.Name);
var placeAccessor = personAccessor.Get(x => x.Place);

拥有一个基础的Accessor<>类可以让您将它们视为一种类型,例如,
var personAccessor = new Accessor<Person>();
var personAccessorArray = new Accessor<Person>[] 
                          {
                           personAccessor.Get(x => x.Age), 
                           personAccessor.Get(x => x.Name), 
                           personAccessor.Get(x => x.Place);
                          };

2
在我的情况下,对象必须是类型化的,而不是通用的对象。但是你真的没有类型吗?它们都只是对象吗?如果你真的必须处理对象,那么你将不得不依靠表达式树(我使用CreateDelegate)进行中间转换等操作,但仍然非常快。像这里显示的http://geekswithblogs.net/Madman/archive/2008/06/27/faster-reflection-using-expression-trees.aspx - nawfal
如果我只有一个完整的类名和属性名的字符串,例如MyNameSpace1.X.Y.Z.ClassName和PropertyName? - Kiquenet
@Kiquenet,您可以通过普通反射来实现(请参见此处),但我猜性能对您很重要。在这种情况下,请参见上面评论中的geekswithblog链接,其中声明了像Func<object, object>Action<object, object>这样的getter和setter。 - nawfal
如果他可以访问那些属性(无论是私有的还是在编译时不知道的),为什么他要使用反射来寻找解决方案呢?抱歉,但你基于表达式的方法在这种情况下是无用的。 - TakeMeAsAGuest
@TakeMeAsAGuest,你并不完全错误,但这只在非常有限的情况下才有用。例如,你可以携带Accessor<T>数组而不会导致装箱惩罚,并稍后调用它。除非使用Func<S, object>,否则无法为类创建通用的访问器集合。现在它不需要使用表达式树或反射,但我也展示了一种绕过GetValueSetValue API的方法,使用表达式编译。正如我所说,将我的示例扩展到完全像动态访问器一样是微不足道的。 - nawfal
显示剩余4条评论

16
你应该看一下FastMember(nuget, 源代码),与反射相比,它真的很快。
我已经测试过这3种实现: 基准测试需要一个基准函数:
static long Benchmark(Action action, int iterationCount, bool print = true)
{
    GC.Collect();
    var sw = new Stopwatch();
    action(); // Execute once before

    sw.Start();
    for (var i = 0; i <= iterationCount; i++)
    {
        action();
    }

    sw.Stop();
    if (print) System.Console.WriteLine("Elapsed: {0}ms", sw.ElapsedMilliseconds);
    return sw.ElapsedMilliseconds;
}

一个假的类:

public class ClassA
{
    public string PropertyA { get; set; }
}

几个测试方法:

private static void Set(string propertyName, string value)
{
    var obj = new ClassA();
    obj.PropertyA = value;
}

private static void FastMember(string propertyName, string value)
{
    var obj = new ClassA();
    var type = obj.GetType();
    var accessors = TypeAccessor.Create(type);
    accessors[obj, "PropertyA"] = "PropertyValue";
}

private static void SetValue(string propertyName, string value)
{
    var obj = new ClassA();
    var propertyInfo = obj.GetType().GetProperty(propertyName);
    propertyInfo.SetValue(obj, value);
}

private static void SetMethodInvoke(string propertyName, string value)
{
    var obj = new ClassA();
    var propertyInfo = obj.GetType().GetProperty(propertyName);
    propertyInfo.SetMethod.Invoke(obj, new object[] { value });
}

脚本本身:

var iterationCount = 100000;
var propertyName = "PropertyA";
var value = "PropertyValue";

Benchmark(() => Set(propertyName, value), iterationCount);
Benchmark(() => FastMember(propertyName, value), iterationCount);
Benchmark(() => SetValue(propertyName, value), iterationCount);
Benchmark(() => SetMethodInvoke(propertyName, value), iterationCount);

100,000次迭代结果:

默认设置器: 3毫秒

FastMember: 36毫秒

PropertyInfo.SetValue: 109毫秒

PropertyInfo.SetMethod: 91毫秒

现在你可以自行选择!!!


它不能在私人财产中使用。 - lindexi
2
谢谢更新,但私有属性不应该从类范围外部进行设置吧? - Thomas
1
当我发现这个答案时,我正在编写FastMember :-)。我使用BenchmakrDotNet将其与许多替代方案(如AutoMapper、CasltleDictionaryAdapter、缓存propertyInfo、缓存设置属性的操作以及手动编码的属性赋值)进行了比较。它只比直接属性分配慢36%,比下一个最快的替代方案快三倍。因此,我放弃了编写自己的代码,并将使用Marc的FastMember,感谢您!不错。 - Loudenvier

11

请确保你以某种方式缓存了 PropertyInfo,以便不要多次调用 type.GetProperty。除此之外,如果你创建一个委托到类型上的方法来执行递增的操作,或者像 Teoman 建议的那样使类型实现一个接口并使用它,可能会更快。


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