30

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

Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), 
                                             propertyInfo.GetGetMethod());

Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), 
                                               propertyInfo.GetSetMethod());

但是我该怎么做FieldInfo呢?

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

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

但如果这里有CreateDelegate办法?我的意思是由于 assignments 返回一个 value,我可以将 assignments 视为一种方法吗?如果是这样,有没有一个MethodInfo句柄?换句话说,我如何将MethodInfo设置和从成员字段获取值的权利传递给CreateDelegate方法,以便我得到一个可以直接读取和写入字段的委托?

Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), fieldInfo.??);
Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), fieldInfo.??);

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

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();
}

还是我在追求不存在(因为我还没有看到类似的东西)?

4

8 回答 8

30

正如 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<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>));
    }
}

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

于 2013-04-25T19:12:44.293 回答
20

[ 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来发出单独的gettersetter函数来访问该字段,您现在可以拥有一个返回托管指针类型对该字段的引用的单一方法,本质上是一个单一的访问器,它(反过来)可以方便地、广告-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实例以获取引用,该引用可让您读取、写入或监视其intprivate字段“ 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);
于 2017-07-12T01:02:39.597 回答
11

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

于 2013-04-20T17:16:50.150 回答
10

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

您必须编写自己的代码来提供该功能。我会建议共享库中的两个函数来提供这个。

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

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 类型):

Func<MyClass, int> getter = typeof(MyClass).GetField("MyField").CreateGetFieldDelegate<MyClass, int>();

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

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();
    }
}

这使它变得更加容易:

Func<MyClass, int> getter = typeof(MyClass).CreateGetFieldDelegate<MyClass, int>("MyField");

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

于 2013-04-21T22:25:26.033 回答
2

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

GetValueand他们SetValue自己是字段的get methodand set method,但它们不适用于任何特定的字段。

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

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

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

但如果你愿意,有一种受限制的方式:

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;
}

由于Getter仍然是Func<S, object>,您可能想看看:

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

于 2013-04-24T12:26:04.673 回答
2

ref return至少在 v4.0.30319 上DynamicMethod似乎没有限制。

Glenn Slayden使用来自微软文档的信息修改了这个答案:

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));
        }
    }
}

输出:

6
于 2019-08-06T13:48:16.380 回答
1

只是为了添加更多的方法:D

 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();
        }

这将创建一个返回值的方法..

测试用例

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()));

        }
于 2020-04-02T15:13:49.387 回答
1

这是在使用对象时创建委托的另一种选择(不知道字段的特定类型)。虽然如果 field 是一个结构(因为装箱),它会更慢。

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>));
    }
}
于 2016-05-16T18:18:38.307 回答