[ 2019 年编辑:由于这篇文章一直是我的最爱之一,令人苦乐参半的是,在我自己的项目中,我在这里展示的方法已被一种更新的、完全不同的、更时尚的技术完全取代,我此答案的详细信息]。
使用新的“<a href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7#ref-locals-and-returns" rel="noreferrer">ref C# 7.0中的return” 功能可以使创建和使用运行时动态生成的 get/set 访问器的过程更加简单和语法透明。不必使用DynamicMethod来发出单独的getter和setter函数来访问该字段,您现在可以拥有一个返回托管指针类型对该字段的引用的单一方法,本质上是一个单一的访问器,它(反过来)可以方便地、广告-hoc得到a̲n̲d̲集使用权。下面,我提供了一个辅助实用函数,它简化了为任何类中的任意(即私有)实例字段生成ByRef getter 函数。
➜对于“只是代码”,请跳到下面的注释。
作为一个运行示例,假设我们要访问一个私有实例字段m_iPrivate
,该字段int
在类中定义OfInterestClass
:
public class OfInterestClass
{
private int m_iPrivate;
};
接下来假设我们有一个静态字段“reference-getter”函数,它接受一个实例并使用新的C# 7 “<a href="https://docs.microsoft.com/en-us通过引用OfInterestClass
返回所需的字段值/dotnet/csharp/whats-new/csharp-7#ref-locals-and-returns" rel="noreferrer">ref return" 功能(下面,我将提供代码以在运行时通过DynamicMethod生成此类函数):
public static ref int __refget_m_iPrivate(this OfInterestClass obj)
{
/// ...
}
这样一个函数(比如说“ref-getter”)是我们对私有字段进行完全读/写访问所需要的。在以下示例中,请特别注意调用setter的操作以及使用 (ie)++
和运算符的演示,因为如果您不熟悉,+=
将这些运算符直接应用于方法调用可能看起来有点不寻常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
-typed 和本地声明的,但它的行为不像典型的“本地”变量。这对于并发代码尤其重要;不仅b̲e̲f̲o̲r̲e̲ MyFunction
已退出可见更改,而且现在使用C# 7,调用者能够保留ref 返回托管指针(作为ref local) 并因此在任意长的时间内继续修改目标a̲f̲t̲e̲r̲wards(尽管必须保持在重新获取堆栈帧的下方,即)。
当然,在这里和其他地方使用托管指针的一个主要和明显的优势是它继续保持有效(同样,在其堆栈帧的生命周期内),即使它本身是在GC堆中oic
分配的引用类型实例— 可能在垃圾收集期间四处移动。这是与本机指针的巨大差异。
如上所述,ref-getter 是一种static
扩展方法,可以在任何地方声明和/或使用。但是,如果您能够创建自己的派生类OfInterestClass
(即,如果OfInterestClass
不是密封的),则可以使它变得更好。在派生类中,您可以公开 C# 语法以使用基类的私有字段,就好像它是派生类的公共字段一样。为此,只需向您的类添加一个 C# 只读ref 返回属性,该属性将静态 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!
如您所见,因为property和前面的method一样,也有一个引用返回值,所以它的行为几乎与字段完全相同。
所以现在详细介绍。你如何创建我上面展示的静态ref-getter函数?使用DynamicMethod
,这应该是微不足道的。例如,下面是传统(按值)静态 getter 函数的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)
显然,这个函数没有得到 C#7 的备忘录和 ref-return。幸运的是,我找到了一个简单的解决方法,让它工作。如果您将非引用类型作为临时“虚拟”传递给构造函数,但随后立即在新创建的DynamicMethod
实例上使用反射将其m_returnType
私有字段更改为您实际想要的ByRef类型(原文如此) ,然后一切似乎都正常。
为了加快速度,我将切入已完成的通用方法,该方法通过为 type 的私有实例字段创建/返回静态 ref-getter 函数来自动化整个过程,该函数U
具有提供的名称,并在 class 中定义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 创建函数在运行时创建函数体并将其存储在静态委托类型字段(具有相同的签名)中,而不是直接用 C# 编写的静态函数。在实例属性(如上所示,并在下面重复)或其他地方调用它的语法与编译器能够编写函数相同。
最后,要缓存动态创建的 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);