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

Posted

技术标签:

【中文标题】C# 反射:更新属性值的最快方法?【英文标题】:C# Reflection: Fastest Way to Update a Property Value? 【发布时间】:2011-09-03 18:22:05 【问题描述】:

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

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

【问题讨论】:

我看到您将 this 作为实例传递。为什么不直接设置属性呢? 最快的方法是这样的类实现一个 IIncrementable 接口并使用它而不是反射 【参考方案1】:

当您知道类型参数时,我做了一些benchmarking here(非通用方法不会有很大不同)。 CreateDelegate 如果您不能直接访问它,将是最快的方法。使用CreateDelegate,您可以直接处理PropertyInfo 中的GetGetMethodGetSetMethod,因此并非每次都使用反射。

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”和“Set”方法替换它,但对我来说非常直观:)

为了避免每次都指定类型,

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

我使基础Accessor&lt;&gt; 类可实例化,这意味着您可以这样做

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&lt;&gt; 基类意味着您可以将它们视为一种类型,例如,

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

【讨论】:

这太棒了!它非常接近我正在寻找的东西,但我对所有这些仍然很模糊,我看不到解决方案的方法。我编写了一个通用 ToString 方法,它可以传递任何对象,它会返回一个字符串,该字符串由对象的所有公共属性的值组成。它使用 PropertyInfo 和 GetValue 来获取每个属性。它是。我想在这里使用你的方法,但似乎我必须在编译时知道类型的名称。我不。我该如何适应它? 是的,就我而言,对象必须是typed,而不是通用object。但是你真的没有type吗?他们都只是 object 吗?如果你真的必须处理 object,那么你将不得不依赖表达式树(我使用 CreateDelegate)和中间类型转换等,但它仍然会非常快。像这里显示的东西geekswithblogs.net/Madman/archive/2008/06/27/… 我不确定我是否能理解您在 cmets 范围内的确切要求,甚至在这里回答您,但如果有必要,您可以提出问题。不要忘记我在这里通知以防万一;) 我认为您在第一条评论中的链接正是我想要的。我想要一个在编译时我不知道其类型的对象的快速获取器。谢谢! 如果我只有一个具有完整类名的字符串,以及像 MyNameSpace1.X.Y.Z.ClassName 和 PropertyName 这样的属性名称?【参考方案2】:

你应该看看FastMember (nuget, source code],它比反射快。

我已经测试了这 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: 0ms", 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 次迭代的结果:

默认设置器:3ms

FastMember:36 毫秒

PropertyInfo.SetValue:109 毫秒

PropertyInfo.SetMethod: 91ms

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

【讨论】:

不能用于私有财产。 感谢更新,但私有属性不应该从类范围之外设置???【参考方案3】:

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

【讨论】:

以上是关于C# 反射:更新属性值的最快方法?的主要内容,如果未能解决你的问题,请参考以下文章

生成随机布尔值的最快方法

使用反射将数据读取器转换为列表的最快方法

在 C# 中查找两个集合的补集的最快方法

获取 8101 行值的最快方法?

c#去除多余空格的最快方法

钳制真实(固定/浮点)值的最快方法?