LINQ Expressions и Рефлексия

159
21 апреля 2019, 22:10

Наткнулся на очень интересный код:

using System.Linq.Expressions;
class FieldAccessor
{
    private static readonly ParameterExpression fieldParameter = Expression.Parameter(typeof(object));
    private static readonly ParameterExpression ownerParameter = Expression.Parameter(typeof(object));
    public FieldAccessor(Type type, string fieldName)
    {
        var field = type.GetField(fieldName,
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        if (field == null) throw new ArgumentException();
        var fieldExpression = Expression.Field(
            Expression.Convert(ownerParameter, type), field);
        Get = Expression.Lambda<Func<object, object>>(
            Expression.Convert(fieldExpression, typeof(object)),
            ownerParameter).Compile();
        Set = Expression.Lambda<Action<object, object>>(
            Expression.Assign(fieldExpression,
                Expression.Convert(fieldParameter, field.FieldType)), 
            ownerParameter, fieldParameter).Compile();
    }
    public Func<object, object> Get { get; }
    public Action<object, object> Set { get; }
}

Автор утверждает, что он производительный стандартной рефлекссии и даже ответ отмечен, как принятый.

Однако, не понимаю, чем он лучше стандартного кеширования поля. Или это плацебо?

Answer 1

Здесь кэшируется не само поле, а метод доступа к нему. Подразумевается, что для каждого поля класса, которое требуется динамически устанавливать или читать, создаётся свой экземпляр класса FieldAccessor. После этого созданный объект используется многократно. Если то же самое делать по старинке, выглядеть это будет так:

FieldInfo pi = type.GetField(fieldName, /* те же самые флаги */);
return pi.GetValue(object);

Этот вызов будет медленнее, чем динамически сгенерированный аксессор специально для указанного поля, т.к. через лямбда-выражение мы генерируем код, содержащий только обращение к полю так, как будто бы мы написали просто return fieldName. Правда, он будет упакован в делегат, что всё равно даст некоторый overhead по сравнению с обычным полем. В принципе, в .NET 1 или 2, где ещё не было интерпретируемых запросов, такое тоже можно было сделать посредством генерации MSIL на Reflection.Emit, но это гораздо сложнее. Возможность описания лямбда-выражения как структуры данных с последующей компиляцией делает то же самое гораздо проще.

Надо помнить, что получение поля по имени и тем более компиляция лямбда-выражения в MSIL - это операции не моментальные, поэтому выигрыш в производительности от использования такого класса мы получим только в том случае, когда мы создали один объект для каждого поля, а потом многократно его используем.

READ ALSO
Не удаляется часть строки (string) в C#

Не удаляется часть строки (string) в C#

Когда я копирую текст из TextBox(multiline) в строку, а потом пытаюсь из нее что-то удалить - со строкой НИЧЕГО не происходитВот часть кода:

127
Как добавить DataGrid в ячейку?

Как добавить DataGrid в ячейку?

На макете имеется DataGrid, у которого один столбец и одна ячейкаКак добавить ещё один (вложенный выходит) DataGrid в эту ячейку? Что-то такое:

238
C# Контроль времени выполнения Task и его отмена

C# Контроль времени выполнения Task и его отмена

получаю данный от Tcp сервера, если запрос длится более 1 сек, то завершаем Task ожидания получения ответа и выкидываем TimeoutException("

148