8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

如何使用类型变量调用泛型方法?

Shobith k Chandran 1月前

46 0

当类型参数在编译时未知,而是在运行时动态获取时,调用泛型方法的最佳方法是什么?考虑以下示例代码 - 在 Ex...

当类型参数在编译时未知但在运行时动态获取时,调用泛型方法的最佳方法是什么?

考虑以下示例代码 - 在方法内部 Example() 使用 GenericMethod<T>() 存储在 Type 内容进行调用的最简洁的方法是什么 myType

public class Sample
{
    public void Example(string typeName)
    {
        Type myType = FindType(typeName);

        // What goes here to call GenericMethod<T>()?
        GenericMethod<myType>(); // This doesn't work

        // What changes to call StaticMethod<T>()?
        Sample.StaticMethod<myType>(); // This also doesn't work
    }

    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}
帖子版权声明 1、本帖标题:如何使用类型变量调用泛型方法?
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由Shobith k Chandran在本站《linq》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 我最近在我的博客文章中使用了这个(makeGeneric 等),不确定这是否足够,但看一看,它附带一个您可以克隆和调试的 repo。https https://iancoetzer.com/blog/csharp-dynamic-objects-and-generics-part-2

  • 受到 Enigmativity 的回答 - 假设你有两个(或更多)类,例如

    public class Bar { }
    public class Square { }
    

    并且您想要使用 Foo<T> Bar 调用 Square ,该方法声明为

    public class myClass
    {
        public void Foo<T>(T item)
        {
            Console.WriteLine(typeof(T).Name);
        }
    }
    

    然后您可以实现 扩展方法

    public static class Extension
    {
        public static void InvokeFoo<T>(this T t)
        {
            var fooMethod = typeof(myClass).GetMethod("Foo");
            var tType = typeof(T);
            var fooTMethod = fooMethod.MakeGenericMethod(new[] { tType });
            fooTMethod.Invoke(new myClass(), new object[] { t });
        }
    }
    

    有了这个,您可以简单地调用 Foo

    var objSquare = new Square();
    objSquare.InvokeFoo();
    
    var objBar = new Bar();
    objBar.InvokeFoo();
    

    它适用于每个类。在本例中,它将输出:

    正方形
    酒吧

  • @MichaelFreidgeim 这是否意味着普通成员不应该学习如何改进代码?:)

  • 如果你在团队中工作,代码应该对团队的普通成员(包括新手)具有可读性,而不仅仅是你自己的习惯

  • @LucaCremonesi 这是习惯问题。当我习惯使用表达式树时,我就不再使用反射了。当你理解了表达式树,你会发现它相当简单,但说实话,学习起来很难

  • 尽管这是一个相当古老的问题,但我发现它很有趣,因为有几种动态调用方法的选项。从字面上看,它是反射、表达式树和发射器。从历史上看,反射是最慢的选项,而发射器是最快的选项。因此,我决定在这个有趣的案例中比较它们,并找出现在是否有任何变化。原始问题询问**当编译时类型参数未知时调用泛型方法的最佳方式**。然而,几乎所有上述答案都建议使用反射。

    我为所有提到的方法创建了三个测试用例。首先,这是一个略微修改过的示例类,将使用 3 种方法进行测试:TestReflection、TestExpression 和 TestEmit。

    public class Sample
    {
        public void TestDirectCall(Type type)
        {
            GenericMethod<string>();
            GenericMethodWithArg<string>(42);
            StaticMethod<string>();
            StaticMethodWithArg<string>(6);
        }
    
        public void TestReflection(Type type)
        {
            CallViaReflection.CallGenericMethod(this, type);
            CallViaReflection.CallGenericMethod(this, type, 42);
            CallViaReflection.CallStaticMethod(type);
            CallViaReflection.CallStaticMethod(type, 6);
        }
    
        public void TestExpression(Type type)
        {
            CallViaExpression.CallGenericMethod(this, type);
            CallViaExpression.CallGenericMethod(this, type, 42);
            CallViaExpression.CallStaticMethod(type);
            CallViaExpression.CallStaticMethod(type, 6);
        }
    
        public void TestEmit(Type type)
        {
            CallViaEmit.CallGenericMethod(this, type);
            CallViaEmit.CallGenericMethod(this, type, 42);
            CallViaEmit.CallStaticMethod(type);
            CallViaEmit.CallStaticMethod(type, 6);
        }
    
        public void T()
        {
            StaticMethod<string>();
        }
    
        public void GenericMethod<T>()
        {
        }
    
        public void GenericMethodWithArg<T>(int someValue)
        {
        }
    
        public static void StaticMethod<T>()
        {
        }
    
        public static void StaticMethodWithArg<T>(int someValue)
        {
        }
    }
    

    CallViaReflection 类表示通过反射调用通用方法的辅助类。我决定引入缓存以获得更好的结果。

    public static class CallViaReflection
    {
        private readonly static Cache<MethodInfo> cache = new();
    
        public static void CallGenericMethod(Sample sample, Type genericType)
        {
            var callDelegate = GetDelegate(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
            callDelegate.Invoke(sample, null);
        }
    
        public static void CallGenericMethod(Sample sample, Type genericType, int someValue)
        {
            var callDelegate = GetDelegate(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType, typeof(int));
            callDelegate.Invoke(sample, new object[] { someValue });
        }
    
        public static void CallStaticMethod(Type genericType)
        {
            var callDelegate = GetDelegate(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
            callDelegate.Invoke(null, null);
        }
    
        public static void CallStaticMethod(Type genericType, int someValue)
        {
            var callDelegate = GetDelegate(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType, typeof(int));
            callDelegate.Invoke(null, new object[] { someValue });
        }
    
        private static MethodInfo GetDelegate(string methodName, BindingFlags bindingFlags, Type genericType, params Type[] arguments)
        {
            if (cache.TryGet(methodName, genericType, out var concreteMethodInfo))
                return concreteMethodInfo;
    
            var sampleType = typeof(Sample);
            MethodInfo genericMethodInfo = sampleType.GetMethod(methodName, bindingFlags)!;
            concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
            cache.Add(methodName, genericType, concreteMethodInfo);
            return concreteMethodInfo;
        }
    }
    

    下一个类 CallViaExpression 使用缓存的表达式树。

    public static class CallViaExpression
    {
        private static readonly Cache<Delegate> cache = new();
    
        public static void CallGenericMethod(Sample sample, Type genericType)
        {
            var callDelegate = GetDelegate(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
            ((Action<Sample>)callDelegate).Invoke(sample);
        }
    
        public static void CallGenericMethod(Sample sample, Type genericType, int someValue)
        {
            var callDelegate = GetDelegate(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType, typeof(int));
            ((Action<Sample, int>)callDelegate).Invoke(sample, someValue);
        }
    
        public static void CallStaticMethod(Type genericType)
        {
            var callDelegate = GetDelegate(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
            ((Action)callDelegate).Invoke();
        }
    
        public static void CallStaticMethod(Type genericType, int someValue)
        {
            var callDelegate = GetDelegate(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType, typeof(int));
            ((Action<int>)callDelegate).Invoke(someValue);
        }
    
        private static Delegate GetDelegate(string methodName, BindingFlags bindingFlags, Type genericType, params Type[] arguments)
        {
            if (cache.TryGet(methodName, genericType, out var callDelegate))
                return callDelegate;
    
            var sampleType = typeof(Sample);
            MethodInfo genericMethodInfo = sampleType.GetMethod(methodName, bindingFlags)!;
            var concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
    
            var argumentExpr = arguments.Select((type, i) => Expression.Parameter(type, "arg" + i)).ToArray();
            if (concreteMethodInfo.IsStatic)
            {
                var callExpr = Expression.Call(concreteMethodInfo, argumentExpr);
                callDelegate = Expression.Lambda(callExpr, argumentExpr).Compile();
            }
            else
            {
                var parameterExpr = Expression.Parameter(sampleType, "sample");
                var callExpr = Expression.Call(parameterExpr, concreteMethodInfo, argumentExpr);
                callDelegate = Expression.Lambda(callExpr, new[] { parameterExpr }.Union(argumentExpr).ToArray()).Compile();
            }
    
            cache.Add(methodName, genericType, callDelegate);
            return callDelegate;
        }
    }
    

    最后但并非最不重要的一点是,CallViaEmit 发出了必要的操作。

    public static class CallViaEmit
    {
        private static readonly Cache<Delegate> cache = new();
    
        public static void CallGenericMethod(this Sample sample, Type genericType)
        {
            var callDelegate = GetDynamicMethod(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
            ((Action<Sample>)callDelegate).Invoke(sample);
        }
    
        public static void CallGenericMethod(this Sample sample, Type genericType, int someValue)
        {
            var callDelegate = GetDynamicMethod(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType);
            ((Action<Sample, int>)callDelegate).Invoke(sample, someValue);
        }
    
        public static void CallStaticMethod(Type genericType)
        {
            var callDelegate = GetDynamicMethod(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
            ((Action)callDelegate).Invoke();
        }
    
        public static void CallStaticMethod(Type genericType, int someValue)
        {
            var callDelegate = GetDynamicMethod(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType);
            ((Action<int>)callDelegate).Invoke(someValue);
        }
    
        private static Delegate GetDynamicMethod(string methodName, BindingFlags bindingFlags, Type genericType)
        {
            if (cache.TryGet(methodName, genericType, out var callDelegate))
                return callDelegate;
    
            var genericMethodInfo = typeof(Sample).GetMethod(methodName, bindingFlags)!;
            var concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
            var argumentTypes = concreteMethodInfo.GetParameters().Select(x => x.ParameterType).ToArray(); ;
            var dynamicMethodArgs = concreteMethodInfo.IsStatic
                ? argumentTypes
                : new[] { typeof(Sample) }.Union(argumentTypes).ToArray();
    
            var dynamicMethod = new DynamicMethod("DynamicCall", null, dynamicMethodArgs);
            var il = dynamicMethod.GetILGenerator();
            il.Emit(OpCodes.Nop);
    
            switch (dynamicMethodArgs.Length)
            {
                case 0:
                    break;
                case 1:
                    il.Emit(OpCodes.Ldarg_0);
                    break;
                case 2:
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldarg_1);
                    break;
                case 3:
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldarg_1);
                    il.Emit(OpCodes.Ldarg_2);
                    break;
                default:
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldarg_1);
                    il.Emit(OpCodes.Ldarg_2);
                    il.Emit(OpCodes.Ldarg_3);
                    for (int i = 4; i < argumentTypes.Length; i++)
                    {
                        il.Emit(OpCodes.Ldarg, argumentTypes[i]);
                    }
                    break;
            }
    
            il.EmitCall(concreteMethodInfo.IsStatic ? OpCodes.Call : OpCodes.Callvirt, concreteMethodInfo, null);
            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ret);
    
            callDelegate = dynamicMethod.CreateDelegate(GetActionType(dynamicMethodArgs));
            cache.Add(methodName, genericType, callDelegate);
            return callDelegate;
        }
    
        private static Type GetActionType(Type[] argumentTypes)
        {
            switch (argumentTypes.Length)
            {
                case 0:
                    return typeof(Action);
                case 1:
                    return typeof(Action<>).MakeGenericType(argumentTypes);
                case 2:
                    return typeof(Action<,>).MakeGenericType(argumentTypes);
                case 3:
                    return typeof(Action<,,>).MakeGenericType(argumentTypes);
                case 4:
                    return typeof(Action<,,,>).MakeGenericType(argumentTypes);
                case 5:
                    return typeof(Action<,,,,>).MakeGenericType(argumentTypes);
                case 6:
                    return typeof(Action<,,,,,>).MakeGenericType(argumentTypes);
                case 7:
                    return typeof(Action<,,,,,,>).MakeGenericType(argumentTypes);
                case 8:
                    return typeof(Action<,,,,,,,>).MakeGenericType(argumentTypes);
                default:
                    throw new NotSupportedException("Action with more than 8 arguments is not supported");
            }
        }
    }
    

    最后,这里是一个测试类和测试结果。

    [TestFixture]
    public class SampleTests
    {
        private const int Iterations = 10000000;
    
        [Test]
        public void TestDirectCall()
        {
            var sample = new Sample();
            var stopwatch = new Stopwatch();
            stopwatch.Start();
    
            for (var i = 0; i < Iterations; i++)
                sample.TestDirectCall(typeof(string));
    
            stopwatch.Stop();
            Assert.Pass($"Calling methods directly took {stopwatch.ElapsedMilliseconds} milliseconds.");
        }
    
        [Test]
        public void TestReflection()
        {
            var sample = new Sample();
            var stopwatch = new Stopwatch();
            stopwatch.Start();
    
            for (var i = 0; i < Iterations; i++)
                sample.TestReflection(typeof(string));
    
            stopwatch.Stop();
            Assert.Pass($"Calling methods dynamically via reflection took {stopwatch.ElapsedMilliseconds} milliseconds.");
        }
    
        [Test]
        public void TestExpressionTree()
        {
            var sample = new Sample();
            var stopwatch = new Stopwatch();
            stopwatch.Start();
    
            for (var i = 0; i < Iterations; i++)
                sample.TestExpression(typeof(string));
    
            stopwatch.Stop();
            Assert.Pass($"Calling methods dynamically via expression tree took {stopwatch.ElapsedMilliseconds} milliseconds.");
        }
    
        [Test]
        public void TestEmit()
        {
            var sample = new Sample();
            var stopwatch = new Stopwatch();
            stopwatch.Start();
    
            for (var i = 0; i < Iterations; i++)
                sample.TestEmit(typeof(string));
    
            stopwatch.Stop();
            Assert.Pass($"Calling methods dynamically via emit took {stopwatch.ElapsedMilliseconds} milliseconds.");
        }
    }
    

    通过 emit 动态调用方法花费了 2939 毫秒。通过表达式树动态调用方法花费了 3910 毫秒。通过反射动态调用方法花费了 6381 毫秒。

    显然,获胜者是发射器。它的性能仍然快两倍多。第二名是表达式树。

    因此,我的结论在第二个十年内还没有改变。如果您需要动态调用方法,请开始使用表达式树。如果您的代码对性能至关重要,请使用 ILGenerator 并发出必要的调用。尽管乍一看可能很复杂,但所有必要的 MSIL 指令都可以使用 ildasm .

    源代码可在 GitHub .

  • MakeGenericMethod() 的参数具有 params 关键字,因此您不需要创建数组;也不需要创建实例来获取类型 - methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject)) 就足够了。

  • 这是我根据 Grax 的回答 ,但泛型方法需要两个参数。

    假设您的方法在 Helpers 类中定义如下:

    public class Helpers
    {
        public static U ConvertCsvDataToCollection<U, T>(string csvData)
        where U : ObservableCollection<T>
        {
          //transform code here
        }
    }
    

    就我而言,U 类型始终是一个可观察的集合,存储 T 类型的对象。

    由于我已经预定义了类型,因此我首先创建代表可观察集合 (U) 和存储在其中的对象 (T) 的“虚拟”对象,这些对象将在下面调用 Make 时用于获取它们的类型

    object myCollection = Activator.CreateInstance(collectionType);
    object myoObject = Activator.CreateInstance(objectType);
    

    然后调用 GetMethod 来查找您的通用函数:

    MethodInfo method = typeof(Helpers).
    GetMethod("ConvertCsvDataToCollection");
    

    到目前为止,上述调用与上面解释的几乎相同,但是当您需要向其传递多个参数时会略有不同。

    您需要将 Type[] 数组传递给 MakeGenericMethod 函数,该函数包含上面创建的“dummy”对象的类型:

    MethodInfo generic = method.MakeGenericMethod(
    new Type[] {
       myCollection.GetType(),
       myObject.GetType()
    });
    

    完成后,您需要调用上面提到的 Invoke 方法。

    generic.Invoke(null, new object[] { csvData });
    

    大功告成。效果棒极了!

    更新:

    正如 @Bevan 所强调的那样,调用 MakeGenericMethod 函数时,我不需要创建数组,因为它需要传入参数,而且我不需要创建对象来获取类型,因为我可以直接将类型传递给此函数。就我而言,由于我已在另一个类中预定义了类型,因此我只需将代码更改为:

    object myCollection = null;
    
    MethodInfo method = typeof(Helpers).
    GetMethod("ConvertCsvDataToCollection");
    
    MethodInfo generic = method.MakeGenericMethod(
       myClassInfo.CollectionType,
       myClassInfo.ObjectType
    );
    
    myCollection = generic.Invoke(null, new object[] { csvData });
    

    myClassInfo 包含 2 个类型的属性 Type ,这些属性是我根据传递给构造函数的枚举值在运行时设置的,并将为我提供相关类型,然后我在 MakeGenericMethod 中使用这些类型。

    再次感谢@Bevan 强调这一点。

  • 没有人提供'经典的反射'解决方案,因此这里有一个完整的代码示例:

    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    namespace DictionaryRuntime
    {
        public class DynamicDictionaryFactory
        {
            /// <summary>
            /// Factory to create dynamically a generic Dictionary.
            /// </summary>
            public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
            {
                //Creating the Dictionary.
                Type typeDict = typeof(Dictionary<,>);
    
                //Creating KeyValue Type for Dictionary.
                Type[] typeArgs = { keyType, valueType };
    
                //Passing the Type and create Dictionary Type.
                Type genericType = typeDict.MakeGenericType(typeArgs);
    
                //Creating Instance for Dictionary<K,T>.
                IDictionary d = Activator.CreateInstance(genericType) as IDictionary;
    
                return d;
    
            }
        }
    }
    

    上面的 DynamicDictionaryFactory 类有一个方法

    CreateDynamicGenericInstance(Type keyType, Type valueType)

    它创建并返回一个 IDictionary 实例,其键和值的类型与调用时指定的完全一致 keyType ,并且 valueType .

    如何调用此方法来实例化和使用的 完整示例 Dictionary<String, int>

    using System;
    using System.Collections.Generic;
    
    namespace DynamicDictionary
    {
        class Test
        {
            static void Main(string[] args)
            {
                var factory = new DictionaryRuntime.DynamicDictionaryFactory();
                var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));
    
                var typedDict = dict as Dictionary<String, int>;
    
                if (typedDict != null)
                {
                    Console.WriteLine("Dictionary<String, int>");
    
                    typedDict.Add("One", 1);
                    typedDict.Add("Two", 2);
                    typedDict.Add("Three", 3);
    
                    foreach(var kvp in typedDict)
                    {
                        Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
                    }
                }
                else
                    Console.WriteLine("null");
            }
        }
    }
    

    当执行上述控制台应用程序时,我们得到了正确的、预期的结果:

    Dictionary<String, int>
    "One": 1
    "Two": 2
    "Three": 3
    
  • 使用 C# 4.0,反射不再必要,因为 DLR 可以使用运行时类型来调用它。由于动态使用 DLR 库有点麻烦(而不是 C# 编译器为您生成代码),开源框架 Dynamitey (.net 标准 1.5)可让您轻松缓存运行时访问编译器为您生成的相同调用。

    var name = InvokeMemberName.Create;
    Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));
    
    
    var staticContext = InvokeContext.CreateStatic;
    Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));
    
  • 补充一下 Adrian Gallero的回答 :

    从类型信息调用泛型方法涉及三个步骤。

    ##TLDR:使用类型对象调用已知的泛型方法可以通过以下方式完成:##

    ((Action)GenericMethod<object>)
        .Method
        .GetGenericMethodDefinition()
        .MakeGenericMethod(typeof(string))
        .Invoke(this, null);
    

    其中 GenericMethod<object> ,要调用的方法名称和满足泛型约束的任何类型。

    (动作)与要调用的方法的签名相匹配,即( Func<string,string,int> Action<bool> )

    ##步骤 1 获取通用方法定义的 MethodInfo##

    ###方法 1:使用具有适当类型或绑定标志的 GetMethod() 或 GetMethods()。###

    MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
    

    ###方法 2:创建一个委托,获取 MethodInfo 对象,然后调用 GetGenericMethodDefinition

    从包含方法的类内部:

    MethodInfo method = ((Action)GenericMethod<object>)
        .Method
        .GetGenericMethodDefinition();
    
    MethodInfo method = ((Action)StaticMethod<object>)
        .Method
        .GetGenericMethodDefinition();
    

    从包含方法的类的外部:

    MethodInfo method = ((Action)(new Sample())
        .GenericMethod<object>)
        .Method
        .GetGenericMethodDefinition();
    
    MethodInfo method = ((Action)Sample.StaticMethod<object>)
        .Method
        .GetGenericMethodDefinition();
    

    在 C# 中,方法的名称(即 \'ToString\' 或 \'GenericMethod\')实际上是指一组方法,其中可能包含一个或多个方法。除非您提供方法参数的类型,否则无法知道您指的是哪种方法。

    ((Action)GenericMethod<object>) 指的是特定方法的委托。 ((Func<string, int>)GenericMethod<object>) 指的是 GenericMethod 的不同重载

    ###方法3:创建一个包含方法调用表达式的lambda表达式,获取MethodInfo对象,然后GetGenericMethodDefinition

    MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
        (Sample v) => v.GenericMethod<object>()
        )).Body).Method.GetGenericMethodDefinition();
    

    这可以分解为

    创建一个 lambda 表达式,其中主体是对您所需方法的调用。

    Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();
    

    提取主体并转换为 MethodCallExpression

    MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;
    

    从方法中获取泛型方法定义

    MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
    

    ##第 2 步调用 MakeGenericMethod 来创建具有适当类型的泛型方法。##

    MethodInfo generic = method.MakeGenericMethod(myType);
    

    ##步骤 3 是使用适当的参数调用该方法。##

    generic.Invoke(this, null);
    
  • 很棒的答案和解释,对我来说非常完美。比公认的答案好得多,写起来更短,性能更高,更安全。

  • @AlexEdelstein 我修改了我的答案以澄清一点。这是因为通用 ProcessItem 方法具有通用约束并且仅接受实现 IItem 接口的对象。当您调用 ProcessItem(new Aplha(), \'test\' , 1); 或 ProcessItem((object)(new Aplha()), \'test\' , 1); 时,您会收到编译器错误,但当转换为动态时,您会将该检查推迟到运行时。

  • Mariusz,对“但是,当您传递 Alpha 类型的参数时,会出现运行时错误,因为没有可以处理此对象的方法”感到困惑。 如果我调用 var a = new Alpha() ProcessItem(a,\'test\' + i, i),为什么通用 ProcessItem 方法不能有效地处理这个问题,输出\'General Process Item\'?

  • 动态 类型而不是反射 API, 可以大大简化调用具有仅在运行时知道的类型参数的泛型方法

    要使用此技术,必须从实际对象(而不仅仅是类的实例 Type )中知道类型。否则,您必须创建该类型的对象或使用标准反射 API 解决方案 。您可以使用 Activator.CreateInstance 方法创建一个对象。

    如果您想调用一个通用方法,在“正常”用法中会推断出它的类型,那么只需将未知类型的对象转换为 dynamic 。以下是一个例子:

    class Alpha { }
    class Beta { }
    class Service
    {
        public void Process<T>(T item)
        {
            Console.WriteLine("item.GetType(): " + item.GetType()
                              + "\ttypeof(T): " + typeof(T));
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            var a = new Alpha();
            var b = new Beta();
    
            var service = new Service();
            service.Process(a); // Same as "service.Process<Alpha>(a)"
            service.Process(b); // Same as "service.Process<Beta>(b)"
    
            var objects = new object[] { a, b };
            foreach (var o in objects)
            {
                service.Process(o); // Same as "service.Process<object>(o)"
            }
            foreach (var o in objects)
            {
                dynamic dynObj = o;
                service.Process(dynObj); // Or write "service.Process((dynamic)o)"
            }
        }
    }
    

    该程序的输出如下:

    item.GetType(): Alpha    typeof(T): Alpha
    item.GetType(): Beta     typeof(T): Beta
    item.GetType(): Alpha    typeof(T): System.Object
    item.GetType(): Beta     typeof(T): System.Object
    item.GetType(): Alpha    typeof(T): Alpha
    item.GetType(): Beta     typeof(T): Beta
    

    Process 是一个泛型实例方法,它写出传递参数的真实类型(通过使用方法 GetType() )和泛型参数的类型(通过使用 typeof 运算符)。

    通过将对象参数转换为 dynamic 类型,我们将提供类型参数推迟到运行时。当 Process 调用方法 dynamic 时,编译器不关心此参数的类型。编译器生成的代码在运行时检查传递的参数的实际类型(通过使用反射)并选择最佳调用方法。这里只有这一个泛型方法,因此使用适当的类型参数调用它。

    在此示例中,输出与下列输出相同:

    foreach (var o in objects)
    {
        MethodInfo method = typeof(Service).GetMethod("Process");
        MethodInfo generic = method.MakeGenericMethod(o.GetType());
        generic.Invoke(service, new object[] { o });
    }
    

    动态类型的版本肯定更短,也更容易编写。您也不必担心多次调用此函数的性能。由于 缓存 机制,下一次使用相同类型参数的调用应该会更快。当然,您可以编写缓存调用的委托的代码,但通过使用类型, dynamic 您可以免费获得此行为。

    如果您想要调用的泛型方法没有参数化类型的参数(因此无法推断其类型参数),那么您可以将泛型方法的调用包装在辅助方法中,如以下示例所示:

    class Program
    {
        static void Main(string[] args)
        {
            object obj = new Alpha();
    
            Helper((dynamic)obj);
        }
    
        public static void Helper<T>(T obj)
        {
            GenericMethod<T>();
        }
    
        public static void GenericMethod<T>()
        {
            Console.WriteLine("GenericMethod<" + typeof(T) + ">");
        }
    }
    

    增强类型安全性

    使用对象代替使用反射 API 的真正好处在于 dynamic ,您只会失去对这种特定类型的编译时检查,因为您直到运行时才知道。其他参数和方法名称照常由编译器静态分析。如果您删除或添加更多参数、更改其类型或重命名方法名称,那么您将收到编译时错误。如果您在中以字符串形式提供方法名称 Type.GetMethod 并在中以对象数组形式提供参数, MethodInfo.Invoke .

    下面是一个简单的示例,说明了如何在编译时(注释代码)捕获某些错误,在运行时捕获其他错误。它还显示了 DLR 如何尝试解析要调用的方法。

    interface IItem { }
    class FooItem : IItem { }
    class BarItem : IItem { }
    class Alpha { }
    
    class Program
    {
        static void Main(string[] args)
        {
            var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
            for (int i = 0; i < objects.Length; i++)
            {
                ProcessItem((dynamic)objects[i], "test" + i, i);
    
                //ProcesItm((dynamic)objects[i], "test" + i, i);
                //compiler error: The name 'ProcesItm' does not
                //exist in the current context
    
                //ProcessItem((dynamic)objects[i], "test" + i);
                //error: No overload for method 'ProcessItem' takes 2 arguments
            }
        }
    
        static string ProcessItem<T>(T item, string text, int number)
            where T : IItem
        {
            Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
                              typeof(T), text, number);
            return "OK";
        }
        static void ProcessItem(BarItem item, string text, int number)
        {
            Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
        }
    }
    

    这里我们再次通过将参数转换为 dynamic 类型来执行一些方法。只有第一个参数类型的验证被推迟到运行时。如果您调用的方法的名称不存在或其他参数无效(参数数量错误或类型错误),您将收到编译器错误。

    当您将 dynamic 参数传递给方法时,此调用将被 绑定 。方法重载解析发生在运行时,并尝试选择最佳重载。因此,如果您 ProcessItem 使用类型的对象调用该方法 BarItem ,那么您实际上会调用非泛型方法,因为它更适合此类型。但是,当您传递该 Alpha 类型的参数时,您将收到运行时错误,因为没有可以处理此对象的方法(泛型方法具有约束 where T : IItem ,而 Alpha 类未实现此接口)。但这就是重点。编译器没有此调用是否有效的信息。作为程序员,您知道这一点,并且您应该确保此代码运行时没有错误。

    返回类型陷阱

    当你使用动态类型参数调用非 void 方法时,其返回类型 be dynamic too 。因此,如果你将前面的示例更改为此代码:

    var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
    

    那么结果对象的类型将是 dynamic 。这是因为编译器并不总是知道将调用哪个方法。如果您知道函数调用的返回类型,那么您应该将 隐式转换 为所需类型,以便其余代码是静态类型的:

    string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
    

    如果类型不匹配,您将收到运行时错误。

    实际上,如果您尝试获取上例中的结果值,那么您将在第二次循环迭代中收到运行时错误。这是因为您尝试保存 void 函数的返回值。

  • 引用 17

    @EricScherrer:应该是 Action 而不是 Action<>

  • 你可以使用 GenMethod.Method.GetGenericMethodDefinition() 来代替 this.GetType().GetMethod(GenMethod.Method.Name)。这样更简洁,可能也更安全。

  • \'?我认为答案应该包括一种避免丢失与 GenericMethod 的编译时链接的方法。现在我不知道这个问题是否常见,但我知道我昨天遇到了同样的问题,这就是我提出这个问题的原因。好吧,我同意反射的常见用途。但最初的问题是如何调用“GenericMethod()\' 如果允许使用这种语法,我们就根本不需要 GetMethod()。但对于“我该如何编写 \'GenericMethod

  • 在使用反射调用方法的情况下,方法名称本身通常会被另一个方法发现。提前知道方法名称的情况并不常见。

返回
作者最近主题: