Отражение C#: самый быстрый способ обновить значение свойства?

Это самый быстрый способ обновить свойство с помощью отражения? Предположим, что свойство всегда int:

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

3 ответа

Решение

Просто убедитесь, что вы как-то кешируете PropertyInfo, чтобы не вызывать повторяющийся вызов type.GetProperty. Кроме этого, вероятно, будет быстрее, если вы создадите делегат для метода типа, который выполнил инкремент, или, как предложил Теоман, заставьте тип реализовать интерфейс и использовать его.

Я провел некоторые тесты здесь, когда вы знаете аргументы типа (не универсальный подход не будет сильно отличаться). CreateDelegate было бы самым быстрым подходом для свойства, если вы не можете получить к нему прямой доступ. С CreateDelegate вы получаете прямое управление GetGetMethod а также GetSetMethod из PropertyInfoследовательно, отражение не используется каждый раз.

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<> класс экземпляра, что означает, что вы можете сделать

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 реализации:

Эталону нужна функция эталона:

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: 109ms

PropertyInfo.SetMethod: 91ms

Теперь вы можете выбрать свой!!!

Другие вопросы по тегам