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

222
标题有些晦涩。我想知道的是这是否可能实现:
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 版本中的 'dynamic' 类型可能有用的情况。 - user1228
1
@Will - 以什么方式?在使用泛型时,在当前的CTP下,你基本上最终会调用<object>版本(除非我错过了什么技巧...) - Marc Gravell
@MarcGravell 你可以使用 foo.Method((dynamic)myGenericClass) 进行运行时方法绑定,有效地实现了类型方法重载的服务定位器模式。 - Chris Marisic
@ChrisMarisic 是的,对于一些通用的 public void Method<T>(T obj) - 这是我在过去6年中使用过多次的技巧 ;p - Marc Gravell
6个回答

254

如果不使用反射,你将无法做到这一点。但是,你可以通过反射来实现。下面是一个完整的示例:

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);

2
好的,这很不错,但是如何调用已创建的方法呢?需要更多的反射吗? - Robert C. Barth
8
如果你可以让你的泛型类型实现一个非泛型接口,那么你就可以将其强制转换为该接口。或者,你可以编写自己的泛型方法来处理你想要使用泛型完成的所有工作,并使用反射调用 - Jon Skeet
1
是的,如果你只有typeArgument类型变量中返回的关于返回类型的信息,我不知道如何使用created。对我来说好像你必须转换为该变量,但你不知道它是什么,所以我不确定你是否可以通过反射来实现。另一个问题是,如果对象例如是int类型,如果将其作为对象变量传递到List<int>中,这是否有效?创建的变量是否会被视为int? - theringostarrs
7
您可以将示例中的“created”对象类型更改为“dynamic”。这样,您就可以在其上调用方法,并将求值延迟到运行时。 - McGarnagle
4
你可以使用 MakeGenericMethod。 - Jon Skeet
显示剩余3条评论

16

很遗憾,没有这种方法。在编译时,通用参数必须能够被解析为1)有效类型或2)另一个通用参数。如果没有使用反射这个大工具,就无法基于运行时值创建通用实例。


2

以下是一些关于如何运用剪刀的代码。假设你有一个类似于

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<FooContent> contents)方法。
new Encoder().Markdown( (dynamic) dynamicList)

请注意方法调用中dynamic的使用。在运行时,dynamicList将是List<FooContent>(此外还是IEnumerable<FooContent>),因为即使使用了dynamic,仍然根据强类型语言进行运行时绑定程序会选择适当的Markdown方法。如果没有精确匹配的类型,则会查找对象参数方法,如果两者都不匹配,则会引发运行时绑定程序异常,提示没有匹配的方法。
这种方法的明显缺点是编译时严重损失类型安全性。尽管如此,按照这样的代码将让您以非常动态的方式操作,在运行时仍然完全符合您的期望而具有类型。

2

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

namespace GenericTest
{
    public class Item
    {
    }
}

namespace GenericTest
{
    public class GenericClass<T>
    {
    }
}

最后,这是如何调用它的方法。使用反引号定义类型

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

0
在这个片段中,我想展示如何创建和使用一个动态创建的列表。例如,在这里我正在向动态列表添加内容。
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);

同样地,您可以在列表上调用任何其他方法。


0

如果您知道将传递哪些类型,您可以在不使用反射的情况下完成此操作。一个 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;
        }
    }
}

1
一旦开始处理数百个类,这将很快变得混乱不堪。 - michael g

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接