将实例化的 System.Type 作为泛型类的类型参数传递

Posted

技术标签:

【中文标题】将实例化的 System.Type 作为泛型类的类型参数传递【英文标题】:Pass An Instantiated System.Type as a Type Parameter for a Generic Class 【发布时间】:2010-09-20 22:26:34 【问题描述】:

标题有点晦涩。我想知道的是这是否可能:

string typeName = <read type name from somwhere>;
Type myType = Type.GetType(typeName);

MyGenericClass<myType> myGenericClass = new MyGenericClass<myType>();

显然,MyGenericClass 被描述为:

public class MyGenericClass<T>

现在,编译器抱怨“找不到类型或命名空间 'myType'。”必须有办法做到这一点。

【问题讨论】:

泛型 != 模板。所有泛型类型变量都在编译时解析,而不是在运行时解析。这是 4.0 的“动态”类型可能有用的情况之一。 @Will - 以什么方式?当与泛型一起使用时,在当前的 CTP 下,您基本上最终会调用 版本(除非我错过了一个技巧......) @MarcGravell 您可以使用foo.Method((dynamic)myGenericClass) 进行运行时方法绑定,有效地为类型的方法重载提供服务定位器模式。 @ChrisMarisic 是的,对于一些通用的public void Method&lt;T&gt;(T obj) - 自那条评论以来的过去 6 年里,我已经多次使用过这个技巧;p @MarcGravell 有没有办法修改它,以便方法实例化它? 【参考方案1】:

没有反思就无法做到这一点。但是,您可以通过反射来做到这一点。这是一个完整的例子:

using System;
using System.Reflection;

public class Generic<T>

    public Generic()
    
        Console.WriteLine("T=0", typeof(T));
    


class Test

    static void Main()
    
        string typeName = "System.String";
        Type typeArgument = Type.GetType(typeName);

        Type genericClass = typeof(Generic<>);
        // MakeGenericType is badly named
        Type constructedClass = genericClass.MakeGenericType(typeArgument);

        object created = Activator.CreateInstance(constructedClass);
    

注意:如果您的泛型类接受多种类型,则在省略类型名称时必须包含逗号,例如:

Type genericClass = typeof(IReadOnlyDictionary<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);

【讨论】:

好的,这很好,但是如何在创建时调用方法呢?更多反思? 好吧,如果你可以让你的泛型类型实现一个非泛型接口,你可以转换到那个接口。或者,您可以编写自己的泛型方法来完成您想要对泛型执行的所有工作,然后使用反射调用 that 是的,如果您拥有的关于返回类型的唯一信息是在 typeArgument 类型变量中,我不明白如何使用 created?在我看来,您必须强制转换为该变量,但您不知道它是什么,所以我不确定您是否可以通过反射来做到这一点。另一个问题,如果对象是 int 类型,如果你将它作为对象变量传递给例如 List 这样的工作吗?创建的变量会被视为 int 吗? @RobertC.Barth 您还可以将示例中的“已创建”对象类型设为“动态”而不是“对象”。这样你就可以调用它的方法,并且评估将被推迟到运行时。 @balanza:你使用 MakeGenericMethod。【参考方案2】:

不幸的是,没有。泛型参数必须在编译时可解析为 1) 有效类型或 2) 另一个泛型参数。如果没有使用反射的大锤,就无法根据运行时值创建通用实例。

【讨论】:

【参考方案3】:

一些额外的如何使用剪刀代码运行。假设你有一个类似于

的类
public class Encoder() 
public void Markdown(IEnumerable<FooContent> contents)  do magic 
public void Markdown(IEnumerable<BarContent> contents)  do magic2 

假设在运行时你有一个 FooContent

如果你能够在编译时绑定你会想要的

var fooContents = new List<FooContent>(fooContent)
new Encoder().Markdown(fooContents)

但是您不能在运行时执行此操作。要在运行时执行此操作,您可以按照以下方式进行:

var listType = typeof(List<>).MakeGenericType(myType);
var dynamicList = Activator.CreateInstance(listType);
((IList)dynamicList).Add(fooContent);

动态调用Markdown(IEnumerable&lt;FooContent&gt; contents)

new Encoder().Markdown( (dynamic) dynamicList)

注意方法调用中dynamic 的用法。在运行时dynamicList 将是List&lt;FooContent&gt;(另外也是IEnumerable&lt;FooContent&gt;),因为即使使用动态仍然植根于强类型语言,运行时绑定器将选择适当的Markdown 方法。如果没有精确的类型匹配,它将寻找对象参数方法,如果两者都不匹配,则会引发运行时绑定器异常,提醒没有方法匹配。

这种方法的明显缺点是编译时类型安全性的巨大损失。尽管如此,这些代码将让您以一种非常动态的方式进行操作,在运行时仍然是完全类型化的,就像您期望的那样。

【讨论】:

【参考方案4】:

我的要求略有不同,但希望能对某人有所帮助。我需要从配置中读取类型并动态实例化泛型类型。

namespace GenericTest

    public class Item
    
    


namespace GenericTest

    public class GenericClass<T>
    
    

最后,这是你如何称呼它的。 Define the type with a backtick.

var t = Type.GetType("GenericTest.GenericClass`1[[GenericTest.Item, GenericTest]], GenericTest");
var a = Activator.CreateInstance(t);

【讨论】:

【参考方案5】:

如果您知道将传递哪些类型,则无需反射即可执行此操作。 switch 语句将起作用。显然,这只会在有限的情况下起作用,但它会比反射快得多。

public class Type1  

public class Type2  

public class Generic<T>  

public class Program

    public static void Main()
    
        var typeName = nameof(Type1);

        switch (typeName)
        
            case nameof(Type1):
                var type1 = new Generic<Type1>();
                // do something
                break;
            case nameof(Type2):
                var type2 = new Generic<Type2>();
                // do something
                break;
        
    

【讨论】:

一旦你开始处理 100 多个类,这会很快变得丑陋。【参考方案6】:

在这个 sn-p 中,我想展示如何创建和使用动态创建的列表。比如我这里添加到动态列表中。

void AddValue<T>(object targetList, T valueToAdd)

    var addMethod = targetList.GetType().GetMethod("Add");
    addMethod.Invoke(targetList, new[]  valueToAdd  as object[]);


var listType = typeof(List<>).MakeGenericType(new[]  dynamicType ); // dynamicType is the type you want
var list = Activator.CreateInstance(listType);

AddValue(list, 5);

同样,您可以调用列表中的任何其他方法。

【讨论】:

以上是关于将实例化的 System.Type 作为泛型类的类型参数传递的主要内容,如果未能解决你的问题,请参考以下文章

c# 自定义的一个泛型类可以序列化吗?

Java泛型

将实例化的 System.Type 作为通用方法的类型参数传递

泛型方法以及泛型方法泛型类的使用场景

Java泛型类

在 kotlin 中创建泛型类的新实例的正确方法是啥?