具有未知类型的CreateDelegate

时间:2010-03-22 08:42:46

标签: c# .net generics reflection delegates

我正在尝试创建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,这与使用纯反射来读取字段一样慢。

所以我的问题是为什么第一个代码片段失败了值类型。根据{{​​3}},它应该按照它说的那样工作

  

如果方法的返回类型比委托的返回类型更具限制性,则委托的返回类型与方法的返回类型兼容

以及如何在第二个片段中执行委托,使其快于反射。

感谢。

5 个答案:

答案 0 :(得分:10)

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

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<T, object>,无需借助即可调用DynamicInvoke使用{{1}}。

答案 1 :(得分:3)

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

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

在你去那里之前,首先要检查你现在所拥有的是否真的太慢了​​。反射的代价是将元数据从组件中挖出。这是在创建委托时完成的,之后缓存了类型信息。

答案 2 :(得分:2)

您的调用失败,因为您需要的对象不是值类型(如INT) - 显然Func<T, int>不是Func<T, Int> - 它不适用于任何vt,如double或bool。要么返回一个盒装的Int(或者你得到的任何vt)。或者(可能更好)使用反射发射API。

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

希望它有所帮助。 路加

答案 3 :(得分:0)

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

答案 4 :(得分:0)

我使用类似于@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");