【问题标题】:CreateDelegate with unknown types具有未知类型的 CreateDelegate
【发布时间】:2010-03-22 08:42:45
【问题描述】:

我正在尝试创建 Delegate 以在运行时读取/写入未知类型类的属性。

我有一个泛型类 Main<T> 和一个看起来像这样的方法:

Delegate.CreateDelegate(typeof(Func<T, object>), get)

其中get 是应读取的属性的MethodInfo。问题是当属性返回int(我猜这发生在值类型上)时,上面的代码会抛出ArgumentException,因为无法绑定该方法。如果是字符串,它工作得很好。

为了解决这个问题,我更改了代码,以便使用MakeGenericType 生成相应的委托类型。所以现在的代码是:

Type func = typeof(Func<,>);
Type generic = func.MakeGenericType(typeof(T), get.ReturnType);
var result = Delegate.CreateDelegate(generic, get)

现在的问题是 generic 的创建委托实例,所以我必须使用 DynamicInvoke,这与使用纯反射读取字段一样慢。

所以我的问题是为什么代码的第一个 sn-p 会因值类型而失败。根据MSDN,它应该像它所说的那样工作

如果方法的返回类型比委托的返回类型更严格,则委托的返回类型与方法的返回类型兼容

以及如何在第二个 sn-p 中执行委托,使其比反射更快。

谢谢。

【问题讨论】:

    标签: c# .net generics reflection delegates


    【解决方案1】:

    这是解决问题的一种方法。创建一个泛型方法:

    public static Func<T, object> MakeDelegate<U>(MethodInfo @get)
    {
        var f = (Func<T, U>)Delegate.CreateDelegate(typeof(Func<T, U>), @get);
        return t => f(t);
    }
    

    这样,C# 的编译器负责插入必要的装箱(如果有)以将 f(t)U 类型)转换为 object。现在您可以使用反射来调用此MakeDelegate 方法,并将U 设置为@get.ReturnType,您将得到一个Func&lt;T, object&gt;,无需使用DynamicInvoke 即可调用它。

    【讨论】:

      【解决方案2】:

      您的原始代码仅适用于引用类型。这就是为什么字符串不是问题的原因,它直接派生自 System.Object。从 ValueType 和 Object 派生的值类型在纸面上是一个很好的错觉,但实际上需要代码。 C# 编译器会自动发出该代码,它需要装箱转换。这就是这里缺少的部分,没有BOX opcode,就没有从 int 到 object 的运行时转换。

      您可以在代码中获取该操作码,但您必须使用 System.Reflection.Emit。

      在你去那里之前,首先检查你现在得到的东西是否真的太慢了​​。反射的代价是从程序集中挖掘元数据。这是在您创建委托时完成的,之后会缓存类型信息。

      【讨论】:

      • 所以你的意思是在 sn-p 2 中生成的委托上使用 DynamicInvoke 很快还是我弄错了?目前我正在使用 SetValue/GetValue 来写入/读取属性,并希望使其更快。
      【解决方案3】:

      您的调用失败,因为您需要对象而不是值类型(如 INT)——显然Func&lt;T, int&gt; 不是Func&lt;T, Int&gt;——它不适用于任何 vt,如 double 或 bool。要么返回一个盒装的 Int (或任何你有的 vt) 。或者(也许更好)使用反射发射 API。

      通过使用反射发射类,您可以创建动态方法并将它们保存为委托,或者创建动态委托并将它们保存在您的某些结构中。您只能执行一次(可能每个运行时执行一次)将其存储在某个 Dict 中并在需要时调用。

      希望对您有所帮助。 卢克

      【讨论】:

      【解决方案4】:

      您是否可以将泛型方法限制为仅适用于引用类型,并创建另一个仅适用于值类型,并相应地决定使用哪种功能?

      【讨论】:

        【解决方案5】:

        我采用了类似于@kvb 的方法,但使用了包装类。它们工作得更快一些,因为使用了虚拟方法而不是代理操作。代理动作也使用不可见的 DisplayClass 捕获上下文。这是我的版本。

        using System;
        using System.Reflection;
        
        public abstract class DelegateWrapper
        {
            public static DelegateWrapper Create ( object target, MethodInfo method, Type arg ) =>
                Create( target, method, typeof( DelegateWrapper<> ), arg );
        
            public static DelegateWrapper Create ( object target, MethodInfo method, Type arg1, Type arg2 ) =>
                Create( target, method, typeof( DelegateWrapper<,> ), arg1, arg2 );
        
            private static DelegateWrapper Create ( object target, MethodInfo method, Type generic, params Type[] argTypes )
            {
                var wrapperType = generic.MakeGenericType( argTypes );
                var ctor = wrapperType.GetConstructor( new Type[] { typeof( object ), typeof( MethodInfo ) } );
                var wrapper = ctor.Invoke( new object[] { target, method } ) as DelegateWrapper;
                return wrapper;
            }
        
            public virtual void Invoke ( object arg )
            {
                throw new NotSupportedException();
            }
            
            public virtual void Invoke ( object arg1, object arg2 )
            {
                throw new NotSupportedException();
            }
        }
        
        public class DelegateWrapper<T> : DelegateWrapper
        {
            private Action<T> _delegate;
        
            public DelegateWrapper ( object target, MethodInfo method )
            {
                _delegate = ( Action<T> )Delegate.CreateDelegate( typeof( Action<T> ), target, method );
            }
        
            public override void Invoke ( object arg )
            {
                _delegate.Invoke( ( T )arg );
            }
        }
        
        public class DelegateWrapper<T1, T2> : DelegateWrapper
        {
            private Action<T1, T2> _delegate;
        
            public DelegateWrapper ( object target, MethodInfo method )
            {
                _delegate = ( Action<T1, T2> )Delegate.CreateDelegate( typeof( Action<T1, T2> ), target, method );
            }
        
            public override void Invoke ( object arg1, object arg2 )
            {
                _delegate.Invoke( ( T1 )arg1, ( T2 )arg2 );
            }
        }
        

        我实现了 DelegateWrapper 和 DelegateWrapper 但很容易扩展这个想法来实现任何人类合理数量的参数。需要在基类中实现相应的 Create() 和 Invoke() 方法,并且只覆盖一个合适的 Invoke()。

        可以如下使用:

        object target;
        var targetType = target.GetType();
        var methodInfo = targetType.GetMethod("MethodWith2Strings");
        
        var delegateWrapper = DelegateWrapper.Create(target, methodInfo, typeof(string), typeof(string));
        
        delegateWrapper.Invoke("String 1", "String 2");
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-01-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多