0

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

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

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

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

iluxa1810
  • 24,899
  • 2
    Если есть сомнения насчёт производительности - возьмите и померяйте. Но да, этот код быстрее рефлексии, т.к. он собирает с помощью linq обычный метод для доступа к полю, а не дёргает рефлексию кждый раз. –  Nov 15 '18 at 18:58
  • По сути это кодогенерация, см.: https://ru.stackoverflow.com/a/608238/218063 поэтому она быстрее рефлексии – Андрей NOP Nov 15 '18 at 19:13

1 Answers1

2

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

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

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

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

Modus
  • 8,885
  • 26
  • 66
  • В примере создается аксессор для Public'а=> я готов поверить, что в "как будто бы мы написали просто return fieldName". Однако, если речь идет о приватном поле? Каким образом Expression до него доберется? Или это не будет работать с приватным поле? – iluxa1810 Nov 16 '18 at 00:26
  • Эффективно вернуть Private может только сам класс владеющий этим полем, ИМХО. Если конечно внутри Expression не происходит вообще откровенная магия. – iluxa1810 Nov 16 '18 at 00:30
  • Или если IL код может начехать на инкапсуляцию... – iluxa1810 Nov 16 '18 at 00:49
  • IL код может начихать на инкапсуляцию, верно. Небольшой overhead от использования делегата - как раз плата за это. Я в своё время пользовался библиотекой DynamicMethodFactory для этого, которая была сделана через Reflection.Emit – Modus Nov 16 '18 at 08:46
  • Хм... Ну если рассматривать с такого ракурса, то тогда все понятно. Я думал с точки зрения инкапсуляции и не понимал, почему это быстрее. Получается, что всякие модификаторы доступа- это для языков высокого уровня, а в IL мы можем творить, что душе угодно? – iluxa1810 Nov 16 '18 at 08:53
  • Ну, не всё, MSIL тоже верифицируется, чем больше подобных трюков используется в коде, тем меньше у него будет привилегий. Например, вряд ли Вы сможете написать хранимую процедуру на С# с подобными махинациями. – Modus Nov 16 '18 at 08:57
  • А что вы подразумеваете под привелегиями? Например, я взял откомпилированные C# исходники и получил IL код. Модифицировал какой-то класс, что он теперь может обращаться куда не должен. Потом скопилировал DLL и подгрузил в C#. Это же по идее будет прозрачно для конечного языка, как я понимаю. Если C# конечно не верифицирует сборку, что там можно делать в конкретном языке. – iluxa1810 Nov 16 '18 at 09:02
  • Если шарите в этом вопросе, то можете сюда заглянуть https://ru.stackoverflow.com/questions/907255/%D0%A7%D1%82%D0%BE-%D0%B2%D0%BE%D0%B7%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE-%D0%B2-il-%D1%87%D0%B5%D0%B3%D0%BE-%D0%BD%D0%B5%D0%BB%D1%8C%D0%B7%D1%8F-%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C-%D0%BD%D0%B0-%D1%8F%D0%B7%D1%8B%D0%BA%D0%B5-%D0%B2%D1%8B%D1%81%D0%BE%D0%BA%D0%BE%D0%B3%D0%BE-%D1%83%D1%80%D0%BE%D0%B2%D0%BD%D1%8F-net – iluxa1810 Nov 16 '18 at 09:23