Подтвердить что ты не робот

Есть ли способ создать делегат для получения и установки значений для 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());

Но как мне обойти FieldInfo s?

Я не ищу делегатов для 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();
}

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

4b9b3361

Ответ 1

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

Ответ 2

Как предложил Питер Ритчи, вы можете скомпилировать свой собственный код во время выполнения. Метод будет скомпилирован сразу же после вызова делегата в первый раз. Таким образом, первый вызов будет медленным, но любой последующий вызов будет таким же быстрым, как вы можете получить в .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> не может использоваться для изменения членов структуры, если он передан значением в качестве первого аргумента.

Ответ 3

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


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

      ➜ Для "просто код" перейдите к примечанию ниже.

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

public class OfInterestClass
{
    private int m_iPrivate;
};

Теперь предположим, что у нас есть функция статического поля 'reference-getter', которая принимает экземпляр OfInterestClass и возвращает желаемое значение поля по ссылке, используя новый С# 7 'ref return'(ниже я предоставлю код для генерации таких функций во время выполнения через DynamicMethod):

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

Такая функция (скажем, ref-getter) - это все, что нам нужно для того, чтобы иметь полный доступ на чтение/запись к приватному полю. В следующих примерах обратите особое внимание на операцию вызова сеттера - и демонстрацию использования операторов (т.е.) ++ и += - поскольку применение этих операторов непосредственно к вызову метода может выглядеть немного необычно, если вы не на высокой скорости на С# 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 in situ (то есть, непосредственно внутри содержащего его экземпляра oic), так что любые и все изменения сразу становятся там публично видимыми. Важно понимать, что это означает, что prv, несмотря на то, что он int -typed и объявлен локально, не ведет себя как ваша типичная локальная переменная. Это особенно важно для параллельного кода; не только видимые изменения b̲e̲f̲o̲r̲e̲ MyFunction вышли, но теперь с С# 7 вызывающие абоненты могут сохранять управляемый указатель ref return (как ref local) и thus continue modifying the target for an arbitrarily long time a̲f̲t̲e̲r̲wards (albeit necessarily remaining below the ref-obtaining stack frame, that is).

) и, таким образом, продолжайте модифицировать цель в течение сколь угодно длительного времени "вперёд" (хотя и обязательно оставаясь ниже фрейма стека получения рефералов, т.е.).Конечно, главное и очевидное преимущество использования управляемого указателя здесь - и в других местах в целом - состоит в том, что он продолжает оставаться действительным (опять же, в пределах времени жизни своего стека), даже как oic - сам экземпляр ссылочного типа, выделенный в куча GC - может перемещаться во время сбора мусора. Это гигантская разница по сравнению с нативными указателями.

Как показано выше, ref-getter - это метод расширения static extension method, который может быть объявлен и/или использован где угодно. Но если вы можете создать свой собственный класс, производный от OfInterestClass (то есть, если OfInterestClass не является запечатанным), вы можете сделать это еще лучше. В производном классе вы можете предоставить синтаксис С# для использования частного поля базового класса, как если бы оно было открытым полем вашего производного класса. Для этого просто добавьте свойство С# только для чтения 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 для традиционной (по значению) статической функции получения:

// 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)

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

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


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


Сначала мы должны определить делегат, который представляет ref-getter, так как делегат 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, с которой все началось. Вместо статической функции, написанной непосредственно на С#, мы будем использовать функцию создания статического ref-getter, чтобы создать тело функции во время выполнения и сохранить его в поле статического делегата -typed (с той же сигнатурой). Синтаксис для вызова его в свойстве экземпляра (как показано выше и повторяется ниже) или в другом месте такой же, как если бы компилятор смог написать функцию.

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

Ответ 4

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

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

Используя ваш код (в этом примере я показываю только создание 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, но этот код будет более сложным и не имеет большей производительности, если таковой имеется.

Ответ 5

Я не знаю, если бы вы использовали 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>, вам может потребоваться посмотреть:

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

Ответ 6

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

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

Ответ 7

Ограничение ref return в DynamicMethod, похоже, исчезло по крайней мере с v4.0.30319.

Изменен этот ответ гленна Слэйдена с информацией из документации Microsoft:

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace ConsoleApp {
    public class MyClass {
        private int privateInt = 6;
    }

    internal static class Program {
        private delegate ref TReturn OneParameter<TReturn, in TParameter0>(TParameter0 p0);

        private static void Main() {
            var myClass = new MyClass();

            var method = new DynamicMethod(
                "methodName",
                typeof(int).MakeByRefType(), // <- MakeByRefType() here
                new[] {typeof(MyClass)}, 
                typeof(MyClass), 
                true); // skip visibility


            const BindingFlags bindings = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

            var il = method.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldflda, typeof(MyClass).GetField("privateInt", bindings));
            il.Emit(OpCodes.Ret);

            var getPrivateInt = (OneParameter<int, MyClass>) method.CreateDelegate(typeof(OneParameter<int, MyClass>));

            Console.WriteLine(typeof(string).Assembly.ImageRuntimeVersion);
            Console.WriteLine(getPrivateInt(myClass));
        }
    }
}

Выходы:

6