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