有没有办法创建一个委托来获取和设置FieldInfo的值?

对于属性,有GetGetMethodGetSetMethod以便我可以这样做:

 Getter = (Func)Delegate.CreateDelegate(typeof(Func), propertyInfo.GetGetMethod()); 

 Setter = (Action)Delegate.CreateDelegate(typeof(Action), propertyInfo.GetSetMethod()); 

但是我该如何处理FieldInfo

我不是在寻找GetValueSetValue代表(这意味着我每次都会调用reflection)

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

但是如果这里有CreateDelegate方法吗? 我的意思是, 因为赋值返回一个值 ,我可以将赋值视为一种方法吗? 如果有的话,它有一个MethodInfo句柄吗? 换句话说,我如何传递正确的MethodInfo设置并从成员字段获取值到CreateDelegate方法,以便我得到一个委托,我可以直接读取和写入字段?

 Getter = (Func)Delegate.CreateDelegate(typeof(Func), fieldInfo.??); Setter = (Action)Delegate.CreateDelegate(typeof(Action), fieldInfo.??); 

我可以构建表达式并编译它,但我正在寻找更简单的东西。 最后,如果问题没有答案,我不介意去表达路线 ,如下所示:

 var instExp = Expression.Parameter(typeof(S)); var fieldExp = Expression.Field(instExp, fieldInfo); Getter = Expression.Lambda<Func>(fieldExp, instExp).Compile(); if (!fieldInfo.IsInitOnly) { var valueExp = Expression.Parameter(typeof(T)); Setter = Expression.Lambda<Action>(Expression.Assign(fieldExp, valueExp), instExp, valueExp).Compile(); } 

或者我是在不存在之后(因为我还没有看到类似的东西)?

字段访问不是通过方法(如getter和setter)执行的 – 它是使用IL指令执行的 – 因此您无法分配给委托。 你必须使用表达式路由来创建一个可以分配给委托的代码“块”(实际上是IL)。

正如Peter Ritchie建议的那样,您可以在运行时编译自己的代码。 第一次调用委托时,将立即编译该方法。 所以第一次调用会很慢,但是任何后续调用都会像在没有非托管指针/联合的.NET中那样快。 除第一次调用外,委托直接比FieldInfo快约500倍。

 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(valueField); var setValue = CreateSetter(valueField); Target target = new Target(); setValue(target, 42); Console.WriteLine(getValue(target)); } static Func CreateGetter(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)setterMethod.CreateDelegate(typeof(Func)); } static Action CreateSetter(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)setterMethod.CreateDelegate(typeof(Action)); } } 

请记住结构是按值传递的。 这意味着如果通过值作为第一个参数传递,则Action不能用于更改结构的成员。

使用C#7.0中新的“ ref return ”function可以使创建和使用运行时动态生成的get / set访问器的过程更加简单和语法透明。 您现在可以使用单个方法返回对该字段的托管指针类型引用,而不是必须使用DynamicMethod发出单独的gettersetter函数来访问该字段,实际上是一个单一的访问者(反过来)可以方便地使用广告-hoc 获取设置访问权限。 下面,我提供了一个帮助器实用程序函数,它简化了为任何类中的任意(即私有)实例字段生成ByRef getter函数。

对于“只是代码”,请跳过下面的注释。

作为一个运行的例子,假设我们想访问一个私有实例字段m_iPrivate ,一个在OfInterestClass类中定义的int

 public class OfInterestClass { private int m_iPrivate; }; 

接下来让我们假设我们有一个静态字段“reference-getter”函数,它接受一个OfInterestClass实例,并使用新的C#7 “ ref return ”function通过引用返回所需的字段值(下面,我将提供代码来生成这样的函数)运行时,通过DynamicMethod ):

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

这样的function(“ref-getter”,让我们说)就是我们需要的全部读/写访问私有字段。 在下面的例子中,特别注意setter -invoking操作 – 以及使用(ie) +++=运算符的演示 – 因为如果你不是up-那么将这些运算符直接应用于方法调用可能看起来有点不寻常在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 在原位操作m_iPrivate (即, 直接在其包含的实例中),以便任何和所有更改立即在那里公开可见。 重要的是要意识到这意味着prv尽管是int -typed和本地声明的,但它的行为并不像典型的“本地”变量。 这对并发代码尤其重要; 不仅 MyFunction退出之前可见变化,而且现在使用C#7 ,调用者能够保留 ref返回管理指针(作为ref本地 ),从而在之后的任意长时间内继续修改目标。

当然,在这里(以及一般的任何地方)使用托管指针的一个主要和明显的优点是它始终保持有效,即使在垃圾收集期间可以在GC堆中分配的引用类型实例。 与原生指针相比,这是一个巨大的差异。

如上所述,ref-getter是一种static 扩展方法 ,可以在任何地方声明和/或使用。 但是如果你能够创建自己的类来自OfInterestClass (也就是说,如果OfInterestClass没有被密封 ),你可以使它更好。 在派生类中,您可以公开C#语法以使用基类的私有字段,就好像它是派生类的公共字段一样。 为此,只需向类中添加一个C#只读ref return属性 ,它将静态ref-getter方法绑定到当前实例:

 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 ,这应该是微不足道的。 例如,以下是传统(按值)静态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 

与by-value getter的唯一区别是我们使用的是ldflda (加载字段地址)操作码而不是ldfld (加载字段)。 所以,如果你对DynamicMethod做得很好,那应该没问题,对吧?

错误! ...
不幸的是, DynamicMethod不允许使用by-ref返回值!

如果您尝试调用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并显示以下消息:

返回Type包含一些无效类型(即null,ByRef)

显然,这个函数没有得到关于C#7和ref-return的备忘录。 幸运的是,我找到了一个简单的解决方法,让它工作。 如果您将非ref类型作为临时“虚拟”传递给构造函数,但之后立即在新创建的DynamicMethod实例上使用reflection将其m_returnType私有字段更改为您的ByRef类型( sic。 )实际上想要,然后一切似乎工作得很好。

为了加快速度,我将切入完整的generics方法,该方法通过为类型为U的私有实例字段创建/返回静态ref-getter函数来自动完成整个过程,该函数具有提供的名称,并在类T定义。


如果您只想要完整的工作代码 ,请从此点下方复制到最后


首先,我们必须定义一个代表ref-getter的委托,因为不能声明具有ByRef用法的Func委托。 幸运的是,较旧的delegate语法确实可以这样做( p! )。

 public delegate ref U RefGetter(T obj); 

将代理以及以下静态函数放在集中的实用程序类中,在这两个实用程序类中,可以在整个项目中访问它们。 这是最终的ref-getter创建函数,可用于为任何类中的所谓实例字段创建静态ref-getter。

 public static RefGetter create_refgetter(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)dm.CreateDelegate(typeof(RefGetter)); } 

现在回到本文的__refget_m_iPrivate ,我们可以轻松地提供__refget_m_iPrivate函数,使所有内容__refget_m_iPrivate启动。 我们将使用静态ref-getter创建函数在运行时创建函数体,并将其存储在静态委托类型字段(具有相同的签名)中,而不是直接在C#中编写的静态函数。 在实例属性中调用它的语法(如上所示,并在下面重复)或其他地方与编译器能够编写函数的语法相同。

最后,要缓存动态创建的ref-getter委托,请将以下行放在您选择的任何static类中。 将OfInterestClass替换为基类的类型,将int替换为private字段的字段类型,并更改字符串参数以匹配私有字段的名称。 如果你无法创建自己派生自OfInterestClass的类(或者不想),那么你就完成了; 只需将此字段OfInterestClass public ,您可以将其称为函数,传递任何OfInterestClass实例以获取一个引用,该引用允许您读取,写入或监视其intprivate字段“ m_iPrivate

 // Static delegate instance of ref-getter method, statically initialized. // Requires an 'OfInterestClass' instance argument to be provided by caller. static RefGetter __refget_m_iPrivate = create_refgetter("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); 

没有没有简单的方法来创建委托来获取/设置字段。

您必须制作自己的代码才能提供该function。 我建议共享库中的两个函数来提供它。

使用您的代码(在此示例中,我仅显示get-delegate的创建):

 static public class FieldInfoExtensions { static public Func CreateGetFieldDelegate(this FieldInfo fieldInfo) { var instExp = Expression.Parameter(typeof(S)); var fieldExp = Expression.Field(instExp, fieldInfo); return Expression.Lambda>(fieldExp, instExp).Compile(); } } 

这样可以很容易地从FieldInfo创建一个get-delegate(假设该字段的类型为int):

 Func getter = typeof(MyClass).GetField("MyField").CreateGetFieldDelegate(); 

或者,如果我们稍微更改您的代码:

 static public class TypeExtensions { static public Func CreateGetFieldDelegate(this Type type, string fieldName) { var instExp = Expression.Parameter(type); var fieldExp = Expression.Field(instExp, fieldName); return Expression.Lambda>(fieldExp, instExp).Compile(); } } 

这使它更容易:

 Func getter = typeof(MyClass).CreateGetFieldDelegate("MyField"); 

也可以使用IL创建这些委托,但该代码会更复杂,并且没有更多的性能(如果有的话)。

我不知道你是否会使用Expression ,那么为什么要避免反思呢? Expression大多数操作都依赖于reflection。

GetValueSetValue本身是字段的get methodset method ,但它们不适用于任何特定字段。

字段不像属性,它们是字段,并且没有理由为每个字段生成get / set方法。 但是,类型可能随着不同的字段而变化,因此GetValueSetValue被定义为parameter/return value作为方差的objectGetValue甚至是一个抽象方法,也就是说,对于覆盖它的每个类(仍然是reflection),必须在相同的签名内。

如果您不键入它们,则以下代码应该:

 public static void SomeMethod(FieldInfo fieldInfo) { var Getter=(Func)fieldInfo.GetValue; var Setter=(Action)fieldInfo.SetValue; } 

但如果你愿意,那就是一种有限的方式:

 public static void SomeMethod(FieldInfo fieldInfo) where S: class where T: class { var Getter=(Func)fieldInfo.GetValue; var Setter=(Action)fieldInfo.SetValue; } 

由于Getter仍然是Func ,您可能希望看一下:

C#中的协方差和逆变,第三部分: Lippert先生博客上的方法组转换差异 。

以下是在处理对象时创建委托的另一个选项(不知道字段的特定类型)。 如果田地是一个结构(因为拳击),它会慢一些。

 public static class ReflectionUtility { public static Func 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)setterMethod.CreateDelegate(typeof(Func)); } public static Action 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)setterMethod.CreateDelegate(typeof(Action)); } }