2013-04-18 2 views
17

Для свойств существуют GetGetMethod и GetSetMethod, так что я могу сделать:Есть ли способ создать делегат для получения и установки значений для FieldInfo?

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 (что означает я буду ссылающихся Reflection каждый раз)

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

, но если есть CreateDelegate подход здесь? Я имею в виду since assignments return a value, могу ли я рассматривать задания как метод? Если для этого есть ручка 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(); 
} 

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

+0

есть ли какая-то причина, почему вам нужно называть 'Delegate.CreateDelegate'? У вас уже есть делегат с 'getter'. Просто вызов 'getter (myInstanceOfT)' будет вызывать метод 'fieldInfo.GetValue' и возвращает вам значение. –

+0

@ChrisSinclair да исполнение - это ключ. См. Эту тему: http://stackoverflow.com/questions/8087611/delegate-for-generic-property-getsetmethod – nawfal

+0

Это 'Func getter = s => (T) fieldInfo.GetValue (s);' все, что вы можете сделать , потому что в поле нет метода setter \ getter, поскольку он находится в свойстве. Если производительность является ключевым, я рекомендую использовать выражение. –

ответ

10

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

+0

Ты прав, я думаю. И я ничего не думаю об этом отражении *, потому что назначение и чтение через скомпилированное выражение были невероятно быстрыми. – nawfal

+0

Да, скомпилированное выражение должно быть таким же быстрым, как если бы вы написали собственный метод доступа к полю и скомпилировали это нормально. –

+2

Питер, я когда-то сравнивал скомпилированное выражение vs createdelegate для свойств в получении и настройке, а createdelegate был явным победителем, поэтому этот вопрос – nawfal

7

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

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

Использование кода (в этом примере я только показать создание Get-делегата):

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

Это позволяет легко создать ПОЛУЧИТЬ-делегата от FieldInfo (предполагается, что поле имеет тип ИНТ):

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

Это м AKES еще более легко:

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

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

+0

'Нет, нет простого способа создать делегат для получения/установки поля. ', Который отвечает на мой вопрос (уже добавлен Ричи). Остальное - это просто рефакторинг кода, который я уже опубликовал в вопросе, к полезным методам. Тем не менее, спасибо за то, что вы входите. Btw, вы можете избежать '(Func )' бросает в ваши вспомогательные функции. ничего, просто читаемость – nawfal

+0

@newfal: Вы правы! Я удалил броски. –

1

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

Covariance and Contravariance in C#, Part Three: Method Group Conversion Variance на Mr Блог Липперта.

+1

Вы: «Тогда почему избежать отражения?» Ответ: Да, выражения также используют отражение. Отражение происходит медленно, поэтому мы должны избегать этого. Но вы можете использовать отражение и/или выражения для создания делегата. После создания делегата, который DIRECTLY вызывает метод или DIRECTLY модифицирует поле, без использования отражения, вы получаете очень быстрое решение. Таким образом, создание делегата таким образом происходит медленно, но назвать его миллион раз - нет. –

+1

@MartinMulder: Я считаю, что нет такого делегата, который мы можем создать для полей .. и, возможно, обходного пути. 'OpCodes.Callvirt' –

+0

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

22

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

+0

Его ответ был 50-50 между вами и Ричи. В конце концов я решил выбрать правильный ответ, поскольку дал правильный ответ на вопрос; наградил вас щедростью за усилия. – nawfal

+0

Когда поле статично (нет указателя «this»), почему вы используете Ldarg_1 вместо Ldarg_0? – vexe

+0

Поскольку делегат всегда (S obj, значение T), даже если поле является статическим. Пользователь будет передавать значение null или что-то в качестве первого аргумента. – Zotta

1

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

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

Разве это не то же самое, что ответ Зотты, кроме не общего? – nawfal

+0

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

4

Использование нового «ref return» особенности в C# 7,0 могут сделать процесс создания и использования среды выполнения динамически сгенерированным получить/комплект аксессоров намного проще и синтаксический прозрачными. Вместо того, чтобы использовать DynamicMethod излучать отдельный геттерный и SETTER функции для доступа к области, теперь вы можете иметь один метод, который возвращает управляемый указатель ссылку -типа на поле, по существу, единственный аксессор, что (в поворот) позволяет удобно, ad-hoc получить и установить доступ. Ниже я предоставляю вспомогательную функцию полезности, которая упрощает генерацию функции Getter для любого произвольного (например, частного) экземпляра в любом классе.

      ➜ Для «просто код,» пропустить примечание ниже.

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

public class OfInterestClass 
{ 
    private int m_iPrivate; 
}; 

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

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

Такая функция («реф-добытчик,» скажем) все, что нужно для того, чтобы иметь полный доступ для чтения/записи в частное поле. В следующих примерах обратите внимание, в частности, на программу seter - и демонстрации использования операторов (то есть) и +=, поскольку применение этих операторов непосредственно к методу вызывает, может показаться немного необычным, если вы не скорость до 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); 
    prv = 999; 
} 

Как это точка, каждая операция показано в этих примерах манипулирует m_iPrivatein situ (т.е. непосредственно в пределах его содержащего, например oic) таким образом, что любые и все изменения общедоступны туда немедленно. Важно понимать, что это означает, что prv, несмотря на то, что он был int, и был объявлен локально, не ведет себя как ваша типичная «локальная» переменная. Это особенно важно для параллельного кода; не только изменения видны доMyFunction завершились, но теперь с C# 7, абоненты имеют возможность сохранитьиого возвращения управляемого указатель (как ref local) и, таким образом, продолжить изменение цели при сколь угодно долго время впоследствии.

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

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

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

Итак, теперь для деталей. Как создать статическую функцию 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, который мы хотим вместо (реф-возврат):

// static ref int refget_iPrivate(OfInterestClass oic) => ref oic.m_iPrivate; 
IL_0000: ldarg.0  
IL_0001: ldflda 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. К счастью, я нашел простую обходную проблему, которая заставляет ее работать. Если вы передадите тип non-ref в конструктор в качестве временного «манекена», но затем сразу же после этого используйте отражение на вновь созданном экземпляре DynamicMethod, чтобы изменить его собственное поле m_returnType как тип ByRef типа (sic.), который вам действительно нужен, тогда все, кажется, работает нормально.

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


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


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

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

Поместите делегат, вместе со следующей статической функции в централизованном служебный класс, где и могут быть доступны на протяжении всего проекта. Вот окончательная функция создания рефлексии, которая может быть использована для создания статического 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"); 

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

// optional: ref-getter instance property (no 'this' argument required) 
public ref int m_iPrivate => ref __refget_m_iPrivate(this); 
+0

... это ... есть .... AMAZING !! Я хочу, чтобы у StackOverflow был способ отправить людям некоторые из своих баллов в качестве благодарности. Genius взломает ограничение возврата ref в DynamicMethod ... Я бы определенно отказался от этого момента. Вы сообщали о недостатке в любом месте, чтобы он обращался? –

+0

@MikeMarynowski Спасибо! Хотя [not ideal] (https://meta.stackexchange.com/questions/192240), обычно [люди используют щедрости] (https://meta.stackexchange.com/questions/144028) для этой цели. –

+0

Я не понимал, что могу поставить щедрость на другой вопрос, который уже получил ответ и присудил его любому из ответов, которые я хотел. У вас есть +50 входящих за 23 часа, чтобы сделать мою жизнь СООО намного легче решить проблему с этим. –