C#反射:更新属性值的最快方法

本文关键字:方法 属性 反射 更新 | 更新日期: 2023-09-27 17:58:02

这是使用反射更新属性的最快方法吗?假设属性始终是int:

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

C#反射:更新属性值的最快方法

当你知道类型参数时,我在这里做了一些基准测试(非通用方法不会有很大不同)。如果不能直接访问属性,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);

或者更好的是,您可以将逻辑封装在一个专用类中,以便在上面有一个get和set方法

类似于:

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

你应该看看FastMember(nuget,源代码),它与反射相比真的很快。

我测试了这3种实现:

  • PropertyInfo.SetValue
  • PropertyInfo.SetMethod
  • FastMember

基准测试需要一个基准测试功能:

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

100000次迭代的结果:

默认设置:3ms

FastMember:36ms

PropertyInfo.SetValue:109ms

PropertyInfo.SetMethod:91ms

现在你可以选择你的了!!!

只需确保以某种方式缓存PropertyInfo,这样就不会重复调用类型。GetProperty。除此之外,如果您在执行增量的类型上创建一个方法的委托,或者像Teoman建议的那样,让该类型实现一个接口并使用它,可能会更快。