[Редактирование 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
Delegate.CreateDelegate
? У вас уже есть делегат сgetter
. Простой вызовgetter(myInstanceOfT)
вызовет методfieldInfo.GetValue
и вернет вам значение. - person Chris Sinclair   schedule 18.04.2013Func<S, T> getter = s => (T)fieldInfo.GetValue(s);
все, что вы можете сделать, потому что у поля нет метода setter\getter, как в свойстве. Если производительность является ключом, я рекомендую использовать Expression. - person Vyacheslav Volkov   schedule 18.04.2013get-set
для информации о поле (аналогично свойствам), а вовсе не после функции с именемCreateDelegate
, но внутренне идет по маршруту выражения. Это не было целью вопроса. Если вы так восприняли, извините. Я согласен, что, возможно, я не сформулировал вопрос так ясно, как вам хотелось бы. Я не задавал вопрос, не зная, как провести рефакторинг метода и дать ему имя. Уж точно не открывать за это награду. - person nawfal   schedule 26.04.2013