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

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

上一篇 下一篇

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

考虑以下示例代码 – 在方法中,使用存储在变量中的调用最简洁的方法是什么?Example()GenericMethod<T>()TypemyType

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>()
    {
        //...
    }
}

分割线

网友回答:

只是对原始答案的补充。虽然这将起作用:

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

这也有点危险,因为您丢失了 的编译时检查。如果稍后执行重构和重命名,则此代码不会注意到,并且将在运行时失败。此外,如果程序集有任何后处理(例如混淆或删除未使用的方法/类),此代码也可能中断。GenericMethodGenericMethod

因此,如果您知道在编译时链接到的方法,并且它没有调用数百万次,因此开销无关紧要,我会将此代码更改为:

Action<> GenMethod = GenericMethod<int>;  //change int by any base type 
                                          //accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

虽然不是很漂亮,但这里有一个编译时引用,如果你重构、删除或对 执行任何操作,这段代码将继续工作,或者至少在编译时中断(例如,如果您删除)。GenericMethodGenericMethodGenericMethod

执行相同操作的另一种方法是创建一个新的包装类,并通过 .我不知道有没有更好的方法。Activator

分割线

网友回答:

你需要使用反射来获取方法的开始,然后通过使用MakeGenericMethod提供类型参数来“构造”它:

MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.GenericMethod));
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

对于静态方法,将第一个参数传递给 。这与通用方法无关——这只是正常的反射。nullInvoke

如前所述,从 C# 4 开始,其中很多都更简单 – 当然,如果您可以使用类型推断。在类型推断不可用的情况下,例如问题中的确切示例,它无济于事。dynamic

分割线

网友回答:

使用类型而不是反射 API 可以大大简化使用仅在运行时已知的类型参数调用泛型方法。dynamic

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

如果要调用泛型方法,在“正常”用法中会推断出其类型,则只需将未知类型的对象强制转换为 。下面是一个示例: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

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

在此示例中,输出与您编写的输出相同:

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

动态类型的版本肯定更短,更容易编写。您也不必担心多次调用此函数的性能。由于 DLR 中的缓存机制,下一次使用相同类型参数的调用应该更快。当然,您可以编写缓存调用委托的代码,但通过使用类型,您可以免费获得此行为。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的替代品的真正好处在于,您只会丢失这种特定类型的编译时间检查,直到运行时才知道。其他参数和方法名称由编译器像往常一样静态分析。如果删除或添加更多参数、更改其类型或重命名方法名称,则会收到编译时错误。如果在 中以字符串形式提供方法名称,在 中将参数作为对象数组提供,则不会发生这种情况。dynamicType.GetMethodMethodInfo.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

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

返回类型陷阱

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

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

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

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

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

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

模板简介:该模板名称为【当类型参数在编译时未知,而是在运行时动态获取时,调用泛型方法的最佳方法是什么?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【C#】栏目查找您需要的精美模板。

相关搜索
  • 下载密码 lanrenmb
  • 下载次数 247次
  • 使用软件 Sublime/Dreamweaver/HBuilder
  • 文件格式 编程语言
  • 文件大小 暂无信息
  • 上传时间 02-17
  • 作者 网友投稿
  • 肖像权 人物画像及字体仅供参考
栏目分类 更多 >
热门推荐 更多 >
单页式简历模板 微信模板 微信文章 自适应 html5 微信图片 微信素材 响应式 微信公众平台 企业网站
您可能会喜欢的其他模板