【问题标题】:Creating a delegate with unknown parameters and return type创建具有未知参数和返回类型的委托
【发布时间】:2016-02-11 12:22:01
【问题描述】:

我正在尝试创建一个方法,该方法返回给定类型的委托,其主体运行函数中指定的某些代码。即

    public Delegate GenerateDelegate(Type delegateType)
    {
        // create a delegate of type 'Type'
        // specify the code of this delegate
        // return the delegate object
    }

如果我事先知道类型,或者至少知道参数和返回类型,我显然可以只返回该类型的委托,一个匿名委托,使用我想要的任何代码。但是,问题是我希望它适用于几乎任何类型的非泛型委托,并且我希望能够利用委托类型所具有的参数,并返回正确的类型。

我查看了使用 ILGenerator.Emit()CodeDOM 来实现此目的的不同选项。但是,我可能想在返回的委托中做一些相当复杂的事情,并且希望能够直接在 C# 中编写代码,因为我不确定是否可以使用 IL 或 CodeDOM 来实现这一点。作为一个伪解决方案,就像 Delegate.DynamicInvoke(params object[]) 可以采用匿名参数一样,我想我希望能够做这样的事情。

    public Delegate GenerateDelegate(Type delegateType)
    {
        return Delegate.CreateDelegate(delegateType, GetType().GetMethod("RunEvent"));
    }

    public static object RunEvent(params object[] parameters)
    {
        // some specified code, depending on contents of parameters
        return null;
    }

RunEvent() 可以将任意数量的参数作为对象数组,并返回对象。遗憾的是,这不起作用并引发以下异常:

无法绑定到目标方法,因为它的签名或安全透明度与委托类型的不兼容。

不知何故,我认为对象数组参数应该与委托可能具有的任何参数组合兼容,但作为对象的返回类型可能会返回一些与给定 delegateType 不兼容的对象,或者如果 delegateType 返回 void。这可能会导致一些问题。

对于我自己的使用,我在运行时知道所有参数和返回类型,并且可以检查这一点以确保一切安全。有什么办法可以让我这样工作吗?

【问题讨论】:

  • “对象数组参数应该与委托可能具有的任何参数组合兼容” – 并非如此。它们不匹配ref/out 参数,它们不匹配值类型参数(当您正常调用此类方法时未应用隐式装箱)。对要调用的方法一无所知似乎是一种非常错误的方法。
  • 你是对的。我没有考虑 ref/out 参数。支持该功能也很好,但对于我目前的使用,我不需要 ref/out 使用,如果这会以任何方式简化解决方案。

标签: c# delegates


【解决方案1】:

我找到了解决问题的方法,但它确实需要我使用泛型。在某种程度上,它是一个更安全的解决方案,因为我需要为生成器提供请求的委托及其类型。

虽然解决方案不是我所希望的,但它确实为我解决了目前的问题并回答了我最初的问题。但是,我将来可能不得不转向更复杂的解决方案。

如果有人可以看到进一步的简化,请告诉我。

(我发现这要归功于 Anis 关于探索表达式树的建议,因为它采用了类似的方法。)

public class DelegateGenerator<TDelegate, TReturn> 
    : GenBase<TDelegate, TReturn> where TDelegate : class
{
    public TReturn RunEvent() => runEvent();
}
public class DelegateGenerator<TDelegate, TReturn, T> 
    : GenBase<TDelegate, TReturn> where TDelegate : class
{
    public TReturn RunEvent(T a) => runEvent(a);
}
public class DelegateGenerator<TDelegate, TReturn, T1, T2> 
    : GenBase<TDelegate, TReturn> where TDelegate : class
{
    public TReturn RunEvent(T1 a, T2 b) => runEvent(a, b);
}
// etc

public abstract class GenBase<TDelegate, TReturn> where TDelegate : class
{
    public TDelegate GenerateDelegate()
    {
        var method = GetType().GetMethod("RunEvent");
        return Delegate.CreateDelegate(typeof(TDelegate), this, method) as TDelegate;
    }

    protected TReturn runEvent(params object[] objs)
    {
        // some code
        return (TReturn)result;
    }
}

及用法:

public delegate bool myDelegate(int a, bool b);

var gen = new DelegateGenerator<myDelegate, bool, int, bool>();
var del = gen.GenerateDelegate();

var result = del(5, true);

【讨论】:

  • @daniherrera 您的两个链接是相同的,一个是其他人问题的答案。对于我将自己的答案标记为解决方案的问题,这是没有完整答案的问题,我发现了问题,只是为了帮助其他找到问题的人。另外,你链接的那个我标记了我自己的我提供了在我找到的正确解决方案中进行编辑,我会将他们的解决方案标记为解决方案。不知道你的意思是什么,但如果我做错了什么让我知道。 :)
  • 知道了。问候。
【解决方案2】:

是的,您可以走“原始”IL 生成路线。但如果方法体允许,请考虑使用expression-trees

这是一个简单的示例,它只是返回返回类型的 default 值,忽略所有参数(这只是一个玩具示例;真正的实现需要复杂得多 -处理 out 参数例如):

static Delegate CreateDelegate(Type[] parameterTypes, Type returnType)
{
   var parameters = parameterTypes.Select(Expression.Parameter)
                                  .ToArray();    

    var body = Expression.Default(returnType);    
    var lambda = Expression.Lambda(body, false, parameters);    
    return lambda.Compile();
}

用法:

Type[] parameterTypes = { typeof(string), typeof(double) };
var returnType = typeof(int);   

var del = CreateDelegate(parameterTypes, returnType);

//Output: System.Func`3[System.String,System.Double,System.Int32]
del.GetType().Dump();

//Output: 0
del.DynamicInvoke("abc", 5D).Dump();

【讨论】:

  • 感谢您的建议。我对此进行了广泛的尝试(花了一些时间来尝试),并最终得到了一个采用类似方法的不同解决方案,但它不需要使用表达式树。不确定它是最好的还是最终的解决方案,但它现在可以工作并且确实回答了我的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-09
  • 1970-01-01
  • 1970-01-01
  • 2010-10-29
  • 1970-01-01
相关资源
最近更新 更多