c# 使用运行时泛型类型调用委托

Posted

技术标签:

【中文标题】c# 使用运行时泛型类型调用委托【英文标题】:c# Call delegate with runtime generic types 【发布时间】:2021-12-19 03:31:09 【问题描述】:

我有一个在运行时创建的委托,因为我在设计时不知道它的参数类型。代码如下:

var delegMethodType = typeof(Func<,,,>).MakeGenericType(type1, type2, type3, returnType);

如您所见,我使用 Func 来表示 Func。效果很好。

之后,我以这种方式创建我的委托:

var deleg = Delegate.CreateDelegate(delegMethodType, obj, methodInfo);

其中“delegMethodType”是我之前创建的委托的类型,“obj”是接收调用的对象,“methodInfo”是“obj”的反射方法。这非常有效。

现在,我需要调用委托,但由于“Delegate.CreateDelegate”返回一个基本委托,我无法使用参数调用它:

// due to this delegate was created in runtime VS.NET doesn't recognize
// that the delegate allows three parameters and returns a value
var result = deleg(val1, val2, val3);

我可以将委托与“deleg.DynamicInvoke()”一起使用,但它比使用其参数调用委托要慢。

如何用三个参数调用这个委托并获得它的返回值?

【问题讨论】:

除非您可以限制参数的类型,否则您必须使用反射。这已经有多慢了?也许您应该使用已编译的Expression 或动态方法 一点测试表明,在缓存的delegMethodType.GetMethod("Invoke") 上调用Invoke 实际上更快,请参阅dotnetfiddle.net/VgJpqo。故事寓意:race your horses 【参考方案1】:

可以直接调用 methodInfo,或者更复杂地使用 Linq 表达式库来创建一个 Func&lt;object,object,...&gt;,可以调用它来执行所有必需的转换。

请注意,我假设您在编译时确实知道参数的数量,正如您的问题所表明的 Func&lt;,,,&gt; 用法。

这有点复杂,但基本上归结为使用Expression.Lambda(...).Compile() 创建将被调用的最终函数,Expression.Convert() 将对象转换为实际类型,以便可以使用Expression.Call() 调用方法在我们的对象Expression.Parameters中具有正确类型的对象上

这个例子展示了如何实现这两种方法。

using System;
using System.Linq.Expressions;

namespace SO

    class SO69847110
    
        public TR fn<T1,T2,T3,TR>(T1 p1, T2 p2, T3 p3)
        
            if(typeof(TR) == typeof(string))
            
                return (TR)(object)"Hello World";
            
            return default(TR);
        

        static void Main(string[] args)
        

            var returnType = typeof(string);
            var paramType = typeof(int);
            var delegMethodType = typeof(Func<,,,>).
                MakeGenericType(
                    paramType,
                    paramType,
                    paramType,
                    returnType
                );
            var methodInfo = typeof(SO69847110).GetMethod("fn")
                .MakeGenericMethod(
                    paramType,
                    paramType,
                    paramType,
                    returnType
                );

            var obj = new SO69847110();

            //var deleg = Delegate.CreateDelegate(delegMethodType, obj, methodInfo);

            var methodInfoInvoke = methodInfo.Invoke(obj, new object[]  0, 1, 2 );
            Console.WriteLine(methodInfoInvoke);

            var param1 = Expression.Parameter(typeof(object));
            var param2 = Expression.Parameter(typeof(object));
            var param3 = Expression.Parameter(typeof(object));
            var convertedParameterExpressions =
            new Expression[]
            
                Expression.Convert(param1,paramType),
                Expression.Convert(param2,paramType),
                Expression.Convert(param3,paramType),
            ;

            var expressed = Expression.Lambda<Func<object, object, object, object>>(
                Expression.Call(
                    Expression.Convert(Expression.Constant(obj), obj.GetType()),
                    methodInfo,
                    convertedParameterExpressions
                ),
                tailCall:false,
                parameters: new[]
                
                    param1,
                    param2,
                    param3
                
                ).Compile();

            var expressedInvoked = expressed.Invoke(obj, 0, 1, 2);
            Console.WriteLine(expressedInvoked);
        
    

如果您想为多个对象调用相同的函数,那么您也可以将主题设为 Func 参数 - 只需在开始时将其设为单独的 Expression.Parameter(typeof(object)),包含该参数而不是 Expression.Constant并且还包括在Expression.Lambda 的参数列表中。然后在调用的时候也把subject作为参数传入。

【讨论】:

以上是关于c# 使用运行时泛型类型调用委托的主要内容,如果未能解决你的问题,请参考以下文章

泛型类的基本使用

《C#零基础入门之百识百例》(八十一)泛型概念介绍 -- 泛型类/结构/接口/委托

C# 泛型的使用

详解C#泛型

C#规范整理·泛型委托事件

TypeToken获取运行时泛型类型