Is there a way to create a delegate to get and set values for a FieldInfo?
对于属性有
1 2 |
和
1 2 |
但是我该如何处理
我不是在寻找
1 2 | Getter = s => (T)fieldInfo.GetValue(s); Setter = (s, t) => (T)fieldInfo.SetValue(s, t); |
但是如果这里有
1 2 |
我可以构建表达式并编译它,但我正在寻找更简单的东西。最后如果问的问题没有答案我不介意走表达路线,如下图:
1 2 3 4 5 6 7 8 | 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(); } |
或者我是在追求不存在(因为我还没有看到类似的东西)?
正如 Peter Ritchie 所建议的,您可以在运行时编译自己的代码。一旦您第一次调用委托,该方法就会被编译。所以第一次调用会很慢,但是任何后续调用都将尽可能快地在没有非托管指针/联合的情况下进入 .NET。除了第一次调用外,delegate 的速度比 FieldInfo 直接快 500 倍左右。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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>)); } } |
请记住,结构是按值传递的。这意味着如果
[2019 年编辑:由于这篇文章一直是我的最爱之一,因此我在我自己的项目中展示的方法已被一种更新、完全不同且更时尚的技术完全取代,这令人苦恼。 ,我在这个答案中详细说明].
使用新的a€?ref returna€? C# 7.0 中的功能可以使创建和使用运行时动态生成的 get/set 访问器的过程更加简单且语法透明。不必使用 DynamicMethod 来发出单独的 getter 和 setter 函数来访问该字段,您现在可以拥有一个返回对该字段的托管指针类型引用的单一方法,即?-本质上是一个(反过来)启用的单一访问器方便,即席获得 aì2nì2dì2 套访问权限。下面,我提供了一个辅助实用程序函数,它简化了为任何类中的任意(即私有)实例字段生成 ByRef getter 函数。
一个??对于一个€?只是代码,a€?跳到下面的注释。
作为一个运行示例,假设我们要访问一个私有实例字段
1 2 3 4 | public class OfInterestClass { private int m_iPrivate; }; |
接下来假设我们有一个静态字段a€?reference-gettera€?采用
1 2 3 4 | public static ref int __refget_m_iPrivate(this OfInterestClass obj) { /// ... } |
这样一个函数(a€?ref-getter,a€? let\\'s say)是我们对私有字段拥有完全读/写访问权限所需要的全部。在以下示例中,请特别注意调用 setter 的操作和使用 (ie)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 affecta€| prv = 999; // a€|field m_iPrivate 'in-situ' } |
重点是,这些示例中显示的每个操作都在原位(即直接在其包含实例
当然,在此处和在 gen?-erala 中的其他地方使用托管指针的一个主要且明显的优势是它继续保持有效(再次,在其堆栈帧的生命周期内),即使
如上所示,ref-getter 是一个
1 | public ref int m_iPrivate => ref __refget_m_iPrivate(this); |
这里,属性是
1 2 3 4 5 6 7 8 9 | 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 函数?使用
1 2 3 4 | // 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):
1 2 3 4 | // static ref int refget_iPrivate(OfInterestClass oic) => ref oic.m_iPrivate; IL_0000: ldarg.0 IL_0001: ldfldì2a Int32 m_iPrivate/OfInterestClass IL_0006: ret |
与按值获取器的唯一区别是我们使用
Wrong!... unfortunately
DynamicMethod does not allow a by-ref return value!
如果您尝试调用指定
1 2 3 4 5 6 |
...函数抛出
The return Type contains some invalid type (i.e. null, ByRef)
显然,这个函数没有得到 C#7 上的备忘录和 ref-return。幸运的是,我找到了一个简单的解决方法,让它工作。如果您将非 ref 类型作为临时"dummy"传递给构造函数,但随后立即在新创建的
为了加快速度,我将切入已完成的通用方法,该方法通过为具有提供的名称的
中定义
如果您只想要完整的工作代码,请从此处复制到末尾
首先我们必须定义一个代表 ref-getter 的委托,因为不能声明具有 ByRef 用法的
1 | public delegate ref U RefGetter<T, U>(T obj); |
将委托以及以下静态函数放在一个集中的实用程序类中,在整个项目中都可以访问它们。这是最终的 ref-getter 创建函数,可用于为任何类中的所谓实例字段创建静态 ref-getter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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>)); } |
现在回到本文的开头,我们可以轻松地提供启动一切的
最后,要缓存动态创建的 ref-getter 委托,请将以下行放在您选择的任何
1 2 3 4 | // 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"); |
可选地,如果您想使用更简洁或更自然的语法发布隐藏字段,您可以定义自己的(非静态)代理类,它包含一个实例,或者甚至更好(如果可能的话) ),派生自字段隐藏类
1 2 | // optional: ref-getter as an instance property (no 'this' argument required) public ref int m_iPrivate => ref __refget_m_iPrivate(this); |
字段访问不是通过方法(如 getter 和 setter)执行的——它是通过 IL 指令执行的——所以没有什么可以分配给委托的。您必须使用表达式路由来创建可以分配给委托的代码"块"(实际上是 IL)。
不,没有简单的方法可以创建一个委托来获取/设置一个字段。
您必须编写自己的代码来提供该功能。我建议在共享库中使用两个函数来提供此功能。
使用你的代码(在这个例子中我只展示了 get-delegate 的创建):
1 2 3 4 5 6 7 8 9 | 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(); } } |
这使得从 FieldInfo 创建一个 get-delegate 变得很容易(假设该字段是 int 类型):
1 | Func<MyClass, int> getter = typeof(MyClass).GetField("MyField").CreateGetFieldDelegate<MyClass, int>(); |
或者如果我们稍微修改一下你的代码:
1 2 3 4 5 6 7 8 9 | 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(); } } |
这使它变得更加容易:
1 |
也可以使用 IL 创建这些委托,但如果有的话,代码会更复杂,性能也不高。
Glenn Slayden 使用来自微软文档的信息修改了这个答案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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)); } } } |
输出:
1 | 6 |
我不知道你是否会使用
字段与属性不同,它们是字段,没有理由为每个字段生成 get/set 方法。但是,类型可能会随着不同的字段而变化,因此
如果你不输入它们,那么下面的代码应该可以:
1 2 3 4 | public static void SomeMethod(FieldInfo fieldInfo) { var Getter=(Func<object, object>)fieldInfo.GetValue; var Setter=(Action<object, object>)fieldInfo.SetValue; } |
但如果你愿意,有一个受限的方式:
1 2 3 4 5 6 | 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; } |
因为
C# 中的协变和逆变,第三部分:Lippert 先生博客上的方法组转换方差。
只是为了添加更多的方法:D
1 2 3 4 5 | public static Func<T, TResult> CreatePropertyOrFieldReaderDelegate<T, TResult>(string field) { var input = Expression.Parameter(typeof(T)); return Expression.Lambda<Func<T, TResult>>(Expression.PropertyOrField(input, field), input).Compile(); } |
这将创建一个返回值的方法..
测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Testing { public int Data = 2; public string PropData { get; } ="Default"; } [Fact] public void CreateSingleFieldReader() { var a = ReflectionHelper.CreatePropertyOrFieldReaderDelegate<Testing, int>("Data"); Assert.Equal(2, a(new Testing())); } |
这是在处理对象时创建委托的另一种选择(不知道字段的特定类型)。虽然如果 field 是一个结构(因为装箱),它会更慢。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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>)); } } |