Отражение C #: самый быстрый способ обновить значение свойства? - PullRequest
16 голосов
/ 28 мая 2011

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

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

Ответы [ 3 ]

24 голосов
/ 18 апреля 2013

Я провел несколько сравнительных тестов здесь , когда вы знаете аргументы типа (не универсальный подход не будет сильно отличаться).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<> класс instantiable, что означает, что вы можете сделать

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);
                          };
11 голосов
/ 10 декабря 2015

Вы должны взглянуть на 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

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

10 голосов
/ 28 мая 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...