如何使用反射动态创建通用的C#对象?

155

在C#中,我有以下的对象:

public class Item
{ }

public class Task<T>
{ }

public class TaskA<T> : Task<T>
{ }

public class TaskB<T> : Task<T>
{ }

我想使用C#反射(Activator.CreateInstance)动态创建TaskA或TaskB。然而在此之前我无法知道类型,因此需要基于字符串如"namespace.TaskA"或"namespace.TaskAB"来动态创建TaskA。

5个回答

282

看看这个文章和这个简单示例。 将其快速翻译为您的类...

var d1 = typeof(Task<>);
Type[] typeArgs = { typeof(Item) };
var makeme = d1.MakeGenericType(typeArgs);
object o = Activator.CreateInstance(makeme);

根据您的编辑: 对于这种情况,您可以这样做...

var d1 = Type.GetType("GenericTest.TaskA`1"); // GenericTest was my namespace, add yours
Type[] typeArgs = { typeof(Item) };
var makeme = d1.MakeGenericType(typeArgs);
object o = Activator.CreateInstance(makeme);

要查看我是如何得出泛型类名称“backtick1”的,请参阅此文章

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

Type type = typeof(IReadOnlyDictionary<,>);

反引号是否是必须的,即如果省略了它,编译器会默认为 1? - richard
8
感谢将我的博客文章“在C# .Net中使用反射实例化泛型类”(http://omegacoder.com/?p=38)链接为“简单示例”。 :-) 很高兴看到这篇文章受到了关注。 - ΩmegaMan
如何使用 is 关键字比较通用类型? - Zain Shaikh
你也可以使用 Activator.CreateInstance<T>,这可能会消除创建指定的 "typeargs" 并自己制作通用类型的需要。 - Francis Dean
2
Francis Dean,问题是你有一个Type对象,但你不能将其放在<>中。 - Bojidar Stanchev

8

实际上,您将无法编写最后一行。

但是,您可能不想仅仅为了创建对象而创建它。您可能希望在新创建的实例上调用某些方法。

然后,您需要类似于接口的东西:

public interface ITask 
{
    void Process(object o);
}

public class Task<T> : ITask
{ 
   void ITask.Process(object o) 
   {
      if(o is T) // Just to be sure, and maybe throw an exception
        Process(o as T);
   }

   public void Process(T o) { }
}

并使用以下命令进行调用:

Type d1 = Type.GetType("TaskA"); //or "TaskB"
Type[] typeArgs = { typeof(Item) };
Type makeme = d1.MakeGenericType(typeArgs);
ITask task = Activator.CreateInstance(makeme) as ITask;

// This can be Item, or any type derived from Item
task.Process(new Item());

无论如何,你不会被静态地转换为预先不知道的类型(在这种情况下是“makeme”)。ITask允许你到达目标类型。
如果这不是你想要的,你可能需要更加具体地说明你想要达成什么目的。

我在Task<T>中有类似Process()的东西。而且在我的情况下,就像你所说的那样,我实际上不再关心最后一行了,因为我只是调用task.Process(),因此能否编写最后一行变得无关紧要。 - Jeff

3

我认为你的示例代码的最后一行应该只是:

Task<Item> itsMe = o as Task<Item>;

还有什么我没注意到的吗?


7
你没有错过任何事情。是我当时没有头脑,不应该喝那么多酒昨晚! - Jeff

1

确保你有充分的理由这样做,像下面这样简单的函数可以实现静态类型,并且让你的IDE能够执行“查找引用”和“重构->重命名”等操作。

public Task <T> factory (String name)
{
  Task <T> result;

  if (name.CompareTo ("A") == 0)
  {
    result = new TaskA ();
  }
  else if (name.CompareTo ("B") == 0)
  {
    result = new TaskB ();
  }

  return result;
}

1
你为什么要使用.CompareTo呢?为什么不用==或.Equals(如果你想要更多的控制)?也许使用switch会更好。 - Yvo
我从未检查过C#是否对==进行字符串比较而不是引用比较。如果CompareTo和Equals被正确实现,它们应该具有相同的运行时效率。将整数放入switch块中并不会像在switch块中一样加速;它将编译为if-else块。 - clemahieu

1

我知道这个问题已经解决了,但是为了让其他人受益,如果你所有涉及的类型都是字符串,你可以将其作为一行代码实现:

IYourInterface o = (Activator.CreateInstance(Type.GetType("Namespace.TaskA`1[OtherNamespace.TypeParam]") as IYourInterface);

每当我做这种事情时,我都有一个我希望后续代码利用的接口,因此我将创建的实例转换为接口。

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