Использование нового «ref return» особенности в C# 7,0 могут сделать процесс создания и использования среды выполнения динамически сгенерированным получить/комплект аксессоров намного проще и синтаксический прозрачными. Вместо того, чтобы использовать DynamicMethod излучать отдельный геттерный и SETTER функции для доступа к области, теперь вы можете иметь один метод, который возвращает управляемый указатель ссылку -типа на поле, по существу, единственный аксессор, что (в поворот) позволяет удобно, ad-hoc получить и установить доступ. Ниже я предоставляю вспомогательную функцию полезности, которая упрощает генерацию функции Getter для любого произвольного (например, частного) экземпляра в любом классе.
➜ Для «просто код,» пропустить примечание ниже.
В текущем примере, скажем, мы хотим получить доступ частного поля экземпляра m_iPrivate
, в int
, определенный в классе OfInterestClass
:
public class OfInterestClass
{
private int m_iPrivate;
};
Далее давайте предположим, что мы имеем статическое поле «Эталон-добытчик »функция, которая принимает OfInterestClass
экземпляр и возвращает нужное значение поля по ссылке с использованием нового C# 7„ref 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_iPrivate
in situ (т.е. непосредственно в пределах его содержащего, например oic
) таким образом, что любые и все изменения общедоступны туда немедленно. Важно понимать, что это означает, что prv
, несмотря на то, что он был int
, и был объявлен локально, не ведет себя как ваша типичная «локальная» переменная. Это особенно важно для параллельного кода; не только изменения видны доMyFunction
завершились, но теперь с C# 7, абоненты имеют возможность сохранитьиого возвращения управляемого указатель (как ref local) и, таким образом, продолжить изменение цели при сколь угодно долго время впоследствии.
Конечно, основным и очевидным преимуществом использования управляемого указателя здесь (и в любом месте в целом) является то, что он всегда остается в силе, даже если oic
-это экземпляр ссылочного типа, выделенный в куче GC, может перемещаться во время сбора мусора. Это гигантская разница по сравнению с собственными указателями.
Как указано выше, ref-getter является static
extension 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);
есть ли какая-то причина, почему вам нужно называть 'Delegate.CreateDelegate'? У вас уже есть делегат с 'getter'. Просто вызов 'getter (myInstanceOfT)' будет вызывать метод 'fieldInfo.GetValue' и возвращает вам значение. –
@ChrisSinclair да исполнение - это ключ. См. Эту тему: http://stackoverflow.com/questions/8087611/delegate-for-generic-property-getsetmethod – nawfal
Это 'Func
getter = s => (T) fieldInfo.GetValue (s);' все, что вы можете сделать , потому что в поле нет метода setter \ getter, поскольку он находится в свойстве. Если производительность является ключевым, я рекомендую использовать выражение. –