C# Reflection: Fastest Way to Update a Property Value?

前端 未结 3 1510
醉梦人生
醉梦人生 2020-12-08 17:11

Is this the fastest way to update a property using reflection? Assume the property is always an int:

PropertyInfo counterPropertyInfo = GetProperty();
int va         


        
相关标签:
3条回答
  • 2020-12-08 17:13

    You should look at FastMember (nuget, source code], it's really fast comparing to reflection.

    I've tested these 3 implementations:

    • PropertyInfo.SetValue
    • PropertyInfo.SetMethod
    • FastMember

    The benchmark needs a benchmark function:

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

    A fake class:

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

    Some test methods:

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

    The script itself:

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

    Results for 100 000 iterations:

    Default setter : 3ms

    FastMember: 36ms

    PropertyInfo.SetValue: 109ms

    PropertyInfo.SetMethod: 91ms

    Now you can choose yours !!!

    0 讨论(0)
  • 2020-12-08 17:26

    Just be sure that you are caching the PropertyInfo somehow, so that you aren't repeatably calling type.GetProperty. Other than that it would probably be faster if you created a delegate to a method on the type that performed the increment, or like Teoman suggested make the type implement an interface and use that.

    0 讨论(0)
  • 2020-12-08 17:37

    I did some benchmarking here when you know the type arguments (a non generic approach wont be very different). CreateDelegate would be the fastest approach for a property if you can't directly access it. With CreateDelegate you get a direct handle to GetGetMethod and GetSetMethod of the PropertyInfo, hence reflection is not used every time.

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

    So now you call:

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

    Or even better you can encapsulate the logic in a dedicated class to have a get and set methods on it.

    Something like:

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

    Short and simple. You can carry around an instance of this class for every "class-property" pair you wish to get/set.

    Usage:

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

    Bit bad use of indexers here, you may replace it with dedicated "Get" and "Set" methods, but very intuitive to me :)

    To avoid having to specify type each time like,

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

    I made the base Accessor<> class instantiable, which means you can do

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

    Having a base Accessor<> class means you can treat them as one type, for eg,

    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);
                              };
    
    0 讨论(0)
提交回复
热议问题