Есть ли способ создать делегата для получения и установки значений для FieldInfo?

Для свойств есть GetGetMethod и GetSetMethod, так что я могу сделать:

Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), 
                                             propertyInfo.GetGetMethod());

а также

Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), 
                                               propertyInfo.GetSetMethod());

Но как мне быть с FieldInfos?

Я не ищу делегатов для GetValue и SetValue (что означает, что я буду каждый раз вызывать отражение)

Getter = s => (T)fieldInfo.GetValue(s);
Setter = (s, t) => (T)fieldInfo.SetValue(s, t);

а если тут CreateDelegate подход? Я имею в виду поскольку присваивания возвращают значение, могу ли я обрабатывать назначения как метод? Если да, то есть ли для него дескриптор MethodInfo? Другими словами, как мне передать право MethodInfo установки и получения значения из поля-члена в метод CreateDelegate, чтобы я получил обратно делегата, с помощью которого я могу читать и писать в поля напрямую?

Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), fieldInfo.??);
Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), fieldInfo.??);

Я могу построить выражение и скомпилировать его, но мне нужно что-то попроще. В конце концов, я не против пойти по маршруту выражения, если на заданный вопрос нет ответа, как показано ниже:

var instExp = Expression.Parameter(typeof(S));
var fieldExp = Expression.Field(instExp, fieldInfo);
Getter = Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile();
if (!fieldInfo.IsInitOnly)
{
    var valueExp = Expression.Parameter(typeof(T));
    Setter = Expression.Lambda<Action<S, T>>(Expression.Assign(fieldExp, valueExp), instExp, valueExp).Compile();
}

Или я за несуществующим (поскольку такого я еще нигде не видел)?


person nawfal    schedule 18.04.2013    source источник
comment
Есть ли какая-то причина, по которой вам нужно позвонить Delegate.CreateDelegate? У вас уже есть делегат с getter. Простой вызов getter(myInstanceOfT) вызовет метод fieldInfo.GetValue и вернет вам значение.   -  person Chris Sinclair    schedule 18.04.2013
comment
@ChrisSinclair да, производительность - это ключ. См. этот поток: stackoverflow.com/questions/8087611/< /а>   -  person nawfal    schedule 18.04.2013
comment
Это Func<S, T> getter = s => (T)fieldInfo.GetValue(s); все, что вы можете сделать, потому что у поля нет метода setter\getter, как в свойстве. Если производительность является ключом, я рекомендую использовать Expression.   -  person Vyacheslav Volkov    schedule 18.04.2013
comment
@ vvs0205 Я знаю, что в конечном итоге мне придется использовать выражение, если я не могу передать информацию о методе для установки и получения значений в поле. Просто посмотреть, можно ли это смоделировать.   -  person nawfal    schedule 18.04.2013
comment
Вы также хотите, чтобы делегат работал с полями только для чтения и закрытыми полями?   -  person Martin Mulder    schedule 25.04.2013
comment
Поля @MartinMulder, доступные только для чтения, должны быть доступны только для чтения. Для закрытых полей да (но я буду использовать только в контексте private — я имею в виду внутри самого класса). В основном я буду уважать защиту/конфиденциальность/видимость   -  person nawfal    schedule 25.04.2013
comment
@newfal: взгляните на мой ответ. Вы можете изменить его в любом случае, если хотите, чтобы поля только для чтения оставались только для чтения, или вы можете принудительно перезаписать их. Эти две маленькие функции сэкономили мне много времени.   -  person Martin Mulder    schedule 26.04.2013
comment
@MartinMulder Я уже просмотрел ваш ответ с момента его публикации. И, как я уже сказал, это только то, что я уже разместил в своем вопросе.   -  person nawfal    schedule 26.04.2013
comment
@newfal: Верно... вы уже предлагали в этом направлении. Но вы просили что-то простое, например: '(Func‹S, T›)Delegate.CreateDelegate(typeof(Func‹S, T›), fieldInfo.?);' Таких функций не существует! Я просто предложил создать такую ​​«простую» функцию самостоятельно. Таким образом, вы предотвратите наличие выражений повсюду в коде, и вы можете просто вызвать эту простую функцию, именно то, что вы просили. Теперь, если ТЕЛО этой функции может быть проще, ответ прост: НЕТ! (имея мнение, что IL не проще). Возможно, я неправильно понимаю ваш вопрос: вам нужен более простой вызов или более простое тело?   -  person Martin Mulder    schedule 26.04.2013
comment
@MartinMulder Честно говоря, я искал пару методов get-set для информации о поле (аналогично свойствам), а вовсе не после функции с именем CreateDelegate, но внутренне идет по маршруту выражения. Это не было целью вопроса. Если вы так восприняли, извините. Я согласен, что, возможно, я не сформулировал вопрос так ясно, как вам хотелось бы. Я не задавал вопрос, не зная, как провести рефакторинг метода и дать ему имя. Уж точно не открывать за это награду.   -  person nawfal    schedule 26.04.2013
comment
@Newfal: Ну... в таком случае ответ прост: нет, для поля не существует (быстрых) методов Get/Set, есть только (медленные) GetValue/SetValue для FieldInfo.   -  person Martin Mulder    schedule 27.04.2013


Ответы (8)


Доступ к полю осуществляется не через метод (например, геттеры и сеттеры) — он выполняется с помощью инструкции IL, поэтому вы ничего не можете назначить делегату. вам придется использовать маршрут выражения для создания «блока» кода (фактически IL), который может быть назначен делегату.

person Peter Ritchie    schedule 20.04.2013
comment
Вы правы, я думаю. И я считаю, что в этом нет ничего рефлексивного, поскольку присваивание и чтение с помощью скомпилированного выражения были невероятно быстрыми. - person nawfal; 20.04.2013
comment
Да, скомпилированное выражение должно быть таким же быстрым, как если бы вы написали собственный метод для доступа к полю и скомпилировали его нормально. - person Peter Ritchie; 20.04.2013
comment
Питер, я когда-то сравнивал скомпилированное выражение и createdelegate для свойств при получении и установке, и createdelegate был явным победителем, отсюда и этот вопрос. - person nawfal; 20.04.2013
comment
@nawfal Да, но вам нужен метод для использования CreateDelegate - без этого вам придется создавать свой собственный во время компиляции или во время выполнения (с скомпилированными выражениями или codegen - я не думаю, что скомпилированное выражение будет быстрее чем кодеген). - person Peter Ritchie; 21.04.2013
comment
Я думаю, что это подводит итог как ответ, но я подожду и других ответов, прежде чем присуждать награду. Я ждал окончательного мнения об этом от сообщества. - person nawfal; 21.04.2013

Как предложил Питер Ритчи, вы можете компилировать свой собственный код во время выполнения. Метод будет скомпилирован, как только вы вызовете делегат в первый раз. Таким образом, первый вызов будет медленным, но любой последующий вызов будет настолько быстрым, насколько это возможно в .NET без неуправляемых указателей/объединений. За исключением первого вызова, делегат примерно в 500 раз быстрее, чем непосредственно FieldInfo.

class DemoProgram
{
    class Target
    {
        private int value;
    }

    static void Main(string[] args)
    {
        FieldInfo valueField = typeof(Target).GetFields(BindingFlags.NonPublic| BindingFlags.Instance).First();
        var getValue = CreateGetter<Target, int>(valueField);
        var setValue = CreateSetter<Target, int>(valueField);

        Target target = new Target();

        setValue(target, 42);
        Console.WriteLine(getValue(target));
    }

    static Func<S, T> CreateGetter<S, T>(FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName + ".get_" + field.Name;
        DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(T), new Type[1] { typeof(S) }, true);
        ILGenerator gen = setterMethod.GetILGenerator();
        if (field.IsStatic)
        {
            gen.Emit(OpCodes.Ldsfld, field);
        }
        else
        {
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldfld, field);
        }
        gen.Emit(OpCodes.Ret);
        return (Func<S, T>)setterMethod.CreateDelegate(typeof(Func<S, T>));
    }

    static Action<S, T> CreateSetter<S,T>(FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName+".set_"+field.Name;
        DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2]{typeof(S),typeof(T)},true);
        ILGenerator gen = setterMethod.GetILGenerator();
        if (field.IsStatic)
        {
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Stsfld, field);
        }
        else
        {
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Stfld, field);
        }
        gen.Emit(OpCodes.Ret);
        return (Action<S, T>)setterMethod.CreateDelegate(typeof(Action<S, T>));
    }
}

Имейте в виду, что структуры передаются по значению. Это означает, что Action<S, T> нельзя использовать для изменения членов структуры, если она передается по значению в качестве первого аргумента.

person Zotta    schedule 25.04.2013
comment
Между вашим ответом и ответом Ричи было 50 на 50. В конце концов я решил выбрать его ответ как правильный, поскольку он дал правильный ответ на вопрос; наградил вас наградой за усилия. - person nawfal; 27.04.2013
comment
Когда поле статично (нет указателя this), почему вы используете Ldarg_1 вместо Ldarg_0? - person vexe; 09.02.2015
comment
Потому что делегат всегда (S obj, T value), даже когда поле статично. Пользователь передаст null или что-то в качестве первого аргумента. - person Zotta; 16.02.2015
comment
Чтобы избежать ошибки времени выполнения, мне пришлось добавить параметр field.Module, например: var getMethod = new DynamicMethod(methodName, typeof(TMember), new[] { typeof(TObject) }, field.Module, true); - person Timo; 05.04.2017

[Редактирование 2019 г.: Поскольку этот пост всегда был одним из моих любимых, приятно и горько отметить, что подход, который я показываю здесь, был полностью заменен в моих собственных проектах более новым, совершенно другим, и гораздо более изящная техника, которую я подробно описываю в этом ответе].


Используя новый «ref return» в C# 7.0 может сделать процесс создания и использования динамически генерируемых во время выполнения методов доступа get/set намного проще и синтаксически прозрачным. Вместо использования DynamicMethod для создания отдельных функций getter и setter для доступа к полю, теперь у вас может быть один метод, возвращающий тип управляемого указателя. ссылка на поле, по сути, единственный метод доступа, который (в свою очередь) обеспечивает удобный, нерегламентированный доступ get a̲n̲d̲ set. Ниже я привожу вспомогательную вспомогательную функцию, которая упрощает создание функции-получателя ByRef для любого произвольного (т. е. закрытого) поля экземпляра в любом классе.

Для фразы «только код» перейдите к примечанию ниже.

В качестве рабочего примера предположим, что мы хотим получить доступ к частному полю экземпляра m_iPrivate, int определенному в классе OfInterestClass:

public class OfInterestClass
{
    private int m_iPrivate;
};

Далее давайте предположим, что у нас есть функция получения ссылки для статического поля, которая принимает экземпляр OfInterestClass и возвращает нужное значение поля по ссылке, используя новый C# 7< /strong> «ref return» (ниже я предоставлю код для создания таких функций во время выполнения через Динамический метод):

public static ref int __refget_m_iPrivate(this OfInterestClass obj)
{
     /// ...
}

Такая функция (скажем, ref-getter) — это все, что нам нужно для того, чтобы иметь полный доступ для чтения/записи к приватному полю. В следующих примерах обратите особое внимание на операцию вызова setter и демонстрацию использования (т.е.) операторов ++ и +=, поскольку эти операторы применяются непосредственно к вызову метода. может выглядеть немного необычно, если вы не в курсе C#7.

void MyFunction(OfInterestClass oic)
{
    int the_value = oic.__refget_m_iPrivate();      // 'get'
    oic.__refget_m_iPrivate() = the_value + 100;    // 'set'

    /// or simply...
    oic.__refget_m_iPrivate() += 100;                // <-- yes, you can

    oic.__refget_m_iPrivate()++;                     // <-- this too, no problem

    ref int prv = ref oic.__refget_m_iPrivate();     // via "ref-local" in C#7
    prv++;
    foo(ref prv);                                    // all of these directly affect…
    prv = 999;                                       // …field m_iPrivate 'in-situ'
}

Суть в том, что каждая операция, показанная в этих примерах, манипулирует m_iPrivate на месте (т. е. непосредственно в содержащем его экземпляре oic), так что любые и все изменения сразу становятся общедоступными. Важно понимать, что это означает, что prv, несмотря на то, что она имеет тип int и локально объявлена, ведет себя не так, как ваша типичная «локальная» переменная. Это особенно важно для параллельного кода; Мало того, что изменения видны, b̲e̲f̲o̲r̲e̲ MyFunction завершился, но теперь с C# 7 у вызывающих абонентов есть возможность сохранять ссылку return< /strong> управляемый указатель (как ref local) и, таким образом, продолжать изменять цель в течение сколь угодно долгого времени a̲f̲t̲e̲r̲wards (хотя и обязательно оставаясь ниже кадра стека получения ссылки, то есть).

Конечно, основное и очевидное преимущество использования управляемого указателя здесь — и вообще везде — заключается в том, что он продолжает оставаться действительным (опять же, в течение времени жизни своего стекового фрейма), даже если oic — сам экземпляр ссылочного типа, размещенный в < куча href="https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection" rel="noreferrer">GC — может перемещаться во время сборки мусора. Это гигантская разница по сравнению с нативными указателями.

Как показано выше, получатель ссылок — это static метод расширения, который можно объявить и/или использовать где угодно. Но если вы можете создать свой собственный класс, производный от OfInterestClass (то есть, если OfInterestClass не sealed), вы можете сделать это еще лучше. В производном классе вы можете предоставить синтаксис C# для использования закрытого поля базового класса, как если бы оно было общедоступным полем вашего производного класса. Для этого просто добавьте в свой класс свойство C# ref return, доступное только для чтения, которое связывает статический метод ref-getter с текущим экземпляром this:

public ref int m_iPrivate => ref __refget_m_iPrivate(this);

Здесь свойство сделано public, поэтому любой может получить доступ к полю (через ссылку на наш производный класс). По сути, мы публично опубликовали приватное поле из базового класса. Теперь в производном классе (или в любом другом месте, если это уместно) вы можете выполнять одно или все из следующих действий:

int v = m_iPrivate;                             // get the value

m_iPrivate = 1234;                              // set the value

m_iPrivate++;                                   // increment it

ref int pi = ref m_iPrivate;                    // reference as C# 7 ref local

v = Interlocked.Exchange(ref m_iPrivate, 9999); // even do in-situ atomic operations on it!

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

Итак, теперь подробности. Как создать статическую функцию ref-getter, показанную выше? Используя DynamicMethod, это должно быть тривиально. Например, вот код IL для традиционного (по -value) статическая функция получения:

// static int get_iPrivate(OfInterestClass oic) => oic.m_iPrivate;
IL_0000: ldarg.0    
IL_0001: ldfld Int32 m_iPrivate/OfInterestClass
IL_0006: ret       

А вот код IL, который нам нужен вместо этого (ref-return):

// static ref int refget_iPrivate(OfInterestClass oic) => ref oic.m_iPrivate;
IL_0000: ldarg.0    
IL_0001: ldfld̲a Int32 m_iPrivate/OfInterestClass
IL_0006: ret     

Единственное отличие от геттера по значению заключается в том, что мы используем код операции ldflda (адрес поля загрузки) вместо ldfld (поле загрузки). Так что, если вы хорошо попрактиковались с DynamicMethod, это не должно быть проблемой, верно?

Неверно!...
к сожалению, DynamicMethod не позволяет возвращать значение по ссылке!

Если вы попытаетесь вызвать конструктор DynamicMethod, указав тип ByRef в качестве возвращаемого значения...

var dm = new DynamicMethod(
        "",                                 // method name
        typeof(int).MakeByRefType(),        // by-ref return type   <-- ERROR
        new[] { typeof(OfInterestClass) },  // argument type(s)
        typeof(OfInterestClass),            // owner type
        true);                              // private access

...функция выдает NotSupportedException со следующим сообщением:

Тип возвращаемого значения содержит недопустимый тип (например, null, ByRef).

Судя по всему, эта функция не получила памятки по C#7 и ref-return. К счастью, я нашел простой обходной путь, который заставляет его работать. Если вы передаете в конструктор тип, не относящийся к ссылке, как временный «фиктивный», но затем сразу после этого используете отражение для вновь созданного экземпляра DynamicMethod, чтобы изменить его закрытое поле m_returnType на ByRef-type тип (sic.), который вам действительно нужен, то все работает нормально.

Чтобы ускорить процесс, я перейду к законченному общему методу, который автоматизирует весь процесс, создавая/возвращая статическую функцию получения ссылки для частного поля экземпляра типа U, имеющего предоставленное имя и определенного в классе T.


Если вам просто нужен полный рабочий код, скопируйте этот пункт снизу до конца.


Сначала мы должны определить делегата, который представляет получатель ссылок, поскольку делегат Func<T,TResult> с использованием ByRef не может быть объявлен. К счастью, старый синтаксис delegate работает для этого (фью!).

public delegate ref U RefGetter<T, U>(T obj);

Поместите делегат вместе со следующей статической функцией в централизованный служебный класс, где к ним можно будет получить доступ в рамках всего проекта. Вот окончательная функция создания ref-getter, которую можно использовать для создания статического ref-getter для так называемого поля экземпляра в любом классе.

public static RefGetter<T, U> create_refgetter<T, U>(String s_field)
{
    const BindingFlags bf = BindingFlags.NonPublic |
                            BindingFlags.Instance |
                            BindingFlags.DeclaredOnly;

    var fi = typeof(T).GetField(s_field, bf);
    if (fi == null)
        throw new MissingFieldException(typeof(T).Name, s_field);

    var s_name = "__refget_" + typeof(T).Name + "_fi_" + fi.Name;

    // workaround for using ref-return with DynamicMethod:
    //   a.) initialize with dummy return value
    var dm = new DynamicMethod(s_name, typeof(U), new[] { typeof(T) }, typeof(T), true);

    //   b.) replace with desired 'ByRef' return value
    dm.GetType().GetField("m_returnType", bf).SetValue(dm, typeof(U).MakeByRefType());

    var il = dm.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldflda, fi);
    il.Emit(OpCodes.Ret);

    return (RefGetter<T, U>)dm.CreateDelegate(typeof(RefGetter<T, U>));
}

Возвращаясь к началу этой статьи, мы можем легко предоставить функцию __refget_m_iPrivate, с которой все началось. Вместо статической функции, написанной непосредственно на C#, мы будем использовать статическую функцию создания ref-getter для создания тела функции во время выполнения и сохранения его в статическом поле типа делегата (с той же сигнатурой). Синтаксис для ее вызова в свойстве экземпляра (как показано выше и повторено ниже) или в другом месте такой же, как если бы компилятор мог написать функцию.

Наконец, чтобы кэшировать динамически созданный делегат ref-getter, поместите следующую строку в любой класс static по вашему выбору. Замените OfInterestClass на тип базового класса, int на тип поля частного поля и измените строковый аргумент, чтобы он соответствовал имени частного поля. Если вы не можете создать свой собственный класс, производный от OfInterestClass (или не хотите этого делать), все готово; просто сделайте это поле public, и вы можете вызывать его как функцию, передавая любой экземпляр OfInterestClass для получения ссылки, которая позволяет вам читать, записывать или отслеживать его int-значное private поле "m_iPrivate".

// Static delegate instance of ref-getter method, statically initialized.
// Requires an 'OfInterestClass' instance argument to be provided by caller.
static RefGetter<OfInterestClass, int> __refget_m_iPrivate = 
                                create_refgetter<OfInterestClass, int>("m_iPrivate");

При желании, если вы хотите опубликовать скрытое поле с более чистым или естественным синтаксисом, вы можете определить собственный (нестатический) прокси-класс, который либо содержит экземпляр — или, возможно, даже лучше (если возможно), происходит от — класса скрытия поля OfInterestClass. Вместо развертывания строки кода, показанной ранее глобально в классе static, вместо этого поместите ее в свой прокси-класс, а затем также добавьте следующую строку :

// optional: ref-getter as an instance property (no 'this' argument required)
public ref int m_iPrivate => ref __refget_m_iPrivate(this);
person Glenn Slayden    schedule 12.07.2017
comment
...это потрясающе!! Я бы хотел, чтобы у StackOverflow был способ отправить людям некоторые из их баллов в качестве благодарности, смеется. Гениальный взлом ограничения возврата ссылки в DynamicMethod... Я бы определенно сдался в тот момент. Вы где-нибудь сообщали о недостатке, чтобы он был устранен? - person Mike Marynowski; 24.11.2017
comment
@MikeMarynowski Спасибо! Хотя не идеально, обычно люди используйте награды для этой цели. - person Glenn Slayden; 24.11.2017
comment
Я не понимал, что могу назначить награду за другой вопрос, на который уже был ответ, и присудить его любому из ответов, которые я хотел. Вы получили +50 за 23 часа за то, что сделали мою жизнь НАСТОЛЬКО проще, решив проблему с этим. - person Mike Marynowski; 24.11.2017
comment
Блестящий ответ, это то, что делает НАСТОЛЬКО замечательным! Я пытался получить указатель на примитивное поле с помощью отражения, и этот подход позволил мне сделать именно это. Спасибо. - person user425678; 09.06.2018
comment
Просто небольшое исправление для .NET 3.5 (для тех из нас, кто использует Unity и моно). В DynamicMethod нет поля m_returnType, но вместо этого оно называется returnType. Поэтому я бы реорганизовал это в var f_returnType = dm.GetType().GetField("m_returnType", bf); if (f_returnType == null) f_returnType = dm.GetType().GetField("returnType", bf); f_returnType.SetValue(dm, typeof(U).MakeByRefType()); - person Andreas Pardeike; 06.08.2018
comment
@GlennSlayden Аккуратно :) Но есть ли способ сделать это в среде Aot (например, IL2CPP)? Можно ли как-то эмулировать Ldflda? Может быть, использовать что-то вроде System.Runtime.CompilerServices.Unsafe? - person Riki; 25.05.2019
comment
Есть ли какие-либо преимущества в использовании ref, когда вы хотите получить доступ только для чтения к полю, в котором хранится ссылочный тип? - person Dai; 15.08.2019
comment
@ Дай Нет, не совсем. IL для доступа к ссылке будет использовать избыточную косвенность, которая была бы не нужна для простого захвата значения поля с дескриптором, если вам никогда не нужно изменять это значение поля. - person Glenn Slayden; 16.08.2019
comment
@GlennSlayden P.S. Хак отражения не нужен в более новых средах выполнения .NET Core. См. github.com/dotnet/corefx/issues/30753. - person Mike Marynowski; 02.09.2019

Нет простого способа создать делегата для получения/установки поля.

Вам нужно будет сделать свой собственный код, чтобы обеспечить эту функциональность. Я бы предложил две функции в общей библиотеке, чтобы обеспечить это.

Используя ваш код (в этом примере я показываю только создание get-delegate):

static public class FieldInfoExtensions
{
    static public Func<S, T> CreateGetFieldDelegate<S, T>(this FieldInfo fieldInfo)
    {
        var instExp = Expression.Parameter(typeof(S));
        var fieldExp = Expression.Field(instExp, fieldInfo);
        return Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile();
    }
}

Это упрощает создание get-delegate из FieldInfo (при условии, что поле имеет тип int):

Func<MyClass, int> getter = typeof(MyClass).GetField("MyField").CreateGetFieldDelegate<MyClass, int>();

Или, если мы немного изменим ваш код:

static public class TypeExtensions
{
    static public Func<S, T> CreateGetFieldDelegate<S, T>(this Type type, string fieldName)
    {
        var instExp = Expression.Parameter(type);
        var fieldExp = Expression.Field(instExp, fieldName);
        return Expression.Lambda<Func<S, T>>(fieldExp, instExp).Compile();
    }
}

Это делает его еще проще:

Func<MyClass, int> getter = typeof(MyClass).CreateGetFieldDelegate<MyClass, int>("MyField");

Также можно создать эти делегаты с помощью IL, но такой код будет более сложным и не будет иметь намного большей производительности, если таковая имеется.

person Martin Mulder    schedule 21.04.2013
comment
No there is no easy way to create a delegate to get/set a field., который отвечает на мой вопрос (уже добавлен Ричи). Остальное - это просто рефакторинг кода, который я уже разместил в вопросе, на полезные методы. Тем не менее, спасибо за участие. Кстати, вы можете избежать (Func<S,T>) приведения в своих вспомогательных функциях. ничего особенного, просто читабельность - person nawfal; 22.04.2013

Я не знаю, если бы вы использовали Expression, то почему избегали отражения? Большинство операций Expression полагаются на отражение.

GetValue и SetValue сами по себе являются get method и set method для полей, но не для какого-либо конкретного поля.

Поля — это не свойства, это поля, и нет причин генерировать методы get/set для каждого из них. Однако тип может различаться в зависимости от поля, и поэтому GetValue и SetValue определяются как parameter/return value как object для дисперсии. GetValue - это даже абстрактный метод, то есть для каждого класса (все еще отражения), переопределяющего его, должна быть идентичная сигнатура.

Если вы не вводите их, то должен работать следующий код:

public static void SomeMethod(FieldInfo fieldInfo) {
    var Getter=(Func<object, object>)fieldInfo.GetValue;
    var Setter=(Action<object, object>)fieldInfo.SetValue;
}

но если вы хотите, есть ограниченный способ:

public static void SomeMethod<S, T>(FieldInfo fieldInfo)
    where S: class
    where T: class {
    var Getter=(Func<S, object>)fieldInfo.GetValue;
    var Setter=(Action<S, T>)fieldInfo.SetValue;
}

По той причине, что Getter по-прежнему равно Func<S, object>, вы можете посмотреть:

Ковариантность и контравариантность в C#, часть третья: дисперсия группового преобразования методов в блоге г-на Липперта.

person Ken Kin    schedule 24.04.2013
comment
Вы: тогда почему избегаете размышлений? Ответ: Да, выражения также используют отражение. Отражение происходит медленно, поэтому мы должны избегать его. Но вы можете использовать отражение и/или выражения для создания делегата. После создания делегата, который НЕПОСРЕДСТВЕННО вызывает метод или НЕПОСРЕДСТВЕННО изменяет поле без использования отражения, вы получаете очень быстрое решение. Таким образом, создание делегата таким образом является медленным, но вызов его миллион раз — нет. - person Martin Mulder; 26.04.2013
comment
@MartinMulder: Ну, я считаю, что нет такого делегата, который мы могли бы создать для полей ... и, возможно, какой-то обходной путь ... OpCodes.Callvirt - person Ken Kin; 26.04.2013
comment
Зависит от того, как вы на это смотрите. Поле всегда записывается фрагментом кода. Так что да, часть кода должна присутствовать или должна быть создана. Когда фрагмент кода известен, вы можете создать для него делегат. Создание этого кода (с выражениями) довольно медленное, но как только оно будет на месте, оно будет быстрым. - person Martin Mulder; 26.04.2013
comment
@MartinMulder: как я уже упоминал в ответе, я не могу найти больше, чем GetValue и SetValue; но эти методы не привязаны к конкретным классам. Так что это не зависит от how I .., это зависит от того, как реализован язык.. - person Ken Kin; 26.04.2013
comment
GetValue и SetValue являются частью мира отражений. Создание делегата для этой функции выполняется быстро. Вызов их по-прежнему медленный, потому что они общие (для всех видов полей). Звонить им миллион раз — это в 1 000 000 раз медленнее. И тут я даже не говорю о боксе и распаковке. Если вы создаете кусок кода с дженериком, вы на самом деле создаете функции, КАК GetValue и SetValue, но только специализированные и высокоскоростные. Итак, в обоих случаях у вас есть фрагмент кода, но какой фрагмент кода вы бы предпочли? - person Martin Mulder; 26.04.2013
comment
@MartinMulder: ... кажется, я не совсем ясно выразился, я говорю, что, насколько я знаю, для полей нет ничего подобного ... однако сейчас я пытаюсь глубже проследить размышления, чтобы найти. - person Ken Kin; 26.04.2013
comment
@KenKin, каждый раз это призыв к размышлению. Хотя технически это отвечает на мой вопрос, это противоречило духу моего вопроса, то есть производительности :) - person nawfal; 27.04.2013
comment
@nawfal: Я знаю ... как сказали ответы, для полей нет ничего, что касается свойств, естественно, вам нужно будет написать код. - person Ken Kin; 27.04.2013


Вот еще один вариант создания делегата, когда вы работаете с объектами (не знаете конкретный тип поля). Хотя это медленнее, если поле является структурой (из-за бокса).

public static class ReflectionUtility
{
    public static Func<object, object> CompileGetter(this FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName + ".get_" + field.Name;
        DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(object), new[] { typeof(object) }, true);
        ILGenerator gen = setterMethod.GetILGenerator();
        if (field.IsStatic)
        {
            gen.Emit(OpCodes.Ldsfld, field);
            gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Box, field.FieldType);
        }
        else
        {
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Castclass, field.DeclaringType);
            gen.Emit(OpCodes.Ldfld, field);
            gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Box, field.FieldType);
        }
        gen.Emit(OpCodes.Ret);
        return (Func<object, object>)setterMethod.CreateDelegate(typeof(Func<object, object>));
    }

    public static Action<object, object> CompileSetter(this FieldInfo field)
    {
        string methodName = field.ReflectedType.FullName + ".set_" + field.Name;
        DynamicMethod setterMethod = new DynamicMethod(methodName, null, new[] { typeof(object), typeof(object) }, true);
        ILGenerator gen = setterMethod.GetILGenerator();
        if (field.IsStatic)
        {
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Unbox_Any, field.FieldType);
            gen.Emit(OpCodes.Stsfld, field);
        }
        else
        {
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Castclass, field.DeclaringType);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(field.FieldType.IsClass ? OpCodes.Castclass : OpCodes.Unbox_Any, field.FieldType);
            gen.Emit(OpCodes.Stfld, field);
        }
        gen.Emit(OpCodes.Ret);
        return (Action<object, object>)setterMethod.CreateDelegate(typeof(Action<object, object>));
    }
}
person Nickolay Andreychuk    schedule 16.05.2016
comment
Разве это не то же самое, что и ответ Зотты, за исключением не общего? - person nawfal; 17.05.2016
comment
Да, я изменил ответ Зотты для случая, когда вы не можете использовать дженерики. - person Nickolay Andreychuk; 17.05.2016

Просто чтобы добавить больше способов сделать это: D

 public static Func<T, TResult> CreatePropertyOrFieldReaderDelegate<T, TResult>(string field)
        {
            var input = Expression.Parameter(typeof(T));
            return Expression.Lambda<Func<T, TResult>>(Expression.PropertyOrField(input, field), input).Compile();
        }

Это создаст метод, который возвращает значение.

ПРЕЦЕДЕНТ

class Testing {
  public int Data = 2;
  public string PropData { get; } = "Default";
 }


  [Fact]
  public void CreateSingleFieldReader()
        {
            var a = ReflectionHelper.CreatePropertyOrFieldReaderDelegate<Testing, int>("Data");
            Assert.Equal(2, a(new Testing()));

        }
person PEtter    schedule 02.04.2020