使用反射在dll中获取所有特定基类型的类

20

我有一个dll文件,其中包含许多从CommandBase类继承的类(比如:CommandA、CommandB、CommandC等)。我正在尝试使用C#反射获取所有这些类的实例,以便我可以在每个实例上调用特定的方法。以下是我目前的代码:

//get assemblies in directory.
string folder = Path.Combine(HttpContext.Current.Server.MapPath("~/"), "bin");
var files = Directory.GetFiles(folder, "*.dll");
//load each assembly.
foreach (string file in files)
{
  var assembly = Assembly.LoadFile(file);
  if (assembly.FullName == "MyCommandProject")
  {
    foreach (var type in assembly.GetTypes())
    {
      if (!type.IsClass || type.IsNotPublic) continue;
      if(type is CommandBase)
      {
        var command = Activator.CreateInstance(type) as CommandBase;
      }
    }
  }
}

我有两个问题。第一,代码中的“if(type is CommandBase)”这一行会得到以下警告:

给定的表达式永远不是类型CommandBase。

第二个问题是我无法找出如何获取实际对象的实例(CommandA,CommandB等等...),仅将其转换为CommandBase是不够的。


你的最后一行没有正确地创建实例吗? 使用Activator.CreateInstance(),你应该有一个CommandA、CommandB...类的实例(取决于type),不是吗? - Julien N
Activation.CreateInstance() 返回类型为 object。 - Justin
是的,但它会返回您提供的类型的对象。您可以将其存储在“object”变量或“CommandBase”变量中,它仍将是CommandA,CommandB等实例。您以后可以将其强制转换为其“真实”类型。 我没有Visual Studio进行检查,但我看不出有什么问题。 - Julien N
也许我明白你想要什么,但这是不可能的。我认为你期望“var”是CommandA、CommandB等类型。 但是var只是一个简化的符号,它不是一个“可变”的类型,当它被编译时,它将被替换为一个“静态”的类型,在你的情况下,它将始终是CommandBase。但这只是一个容器,用于存储实例。正如我之前所说,对象“内部”是类型为“CommandA”、“CommandB”等的对象。只有从父类继承的成员才能访问到更通用的变量。 - Julien N
4个回答

32

这是我使用的基于接口加载的方法。

private static List<T> GetInstances<T>()
{
        return (from t in Assembly.GetExecutingAssembly().GetTypes()
                where t.GetInterfaces().Contains(typeof (T)) && t.GetConstructor(Type.EmptyTypes) != null
                select (T) Activator.CreateInstance(t)).ToList();
}

以下是按基类返回的相同函数。

private static IList<T> GetInstances<T>()
{
        return (from t in Assembly.GetExecutingAssembly().GetTypes()
                       where t.BaseType == (typeof(T)) && t.GetConstructor(Type.EmptyTypes) != null
                       select (T)Activator.CreateInstance(t)).ToList();
}

当然,它需要稍微修改以指向您正在加载的参考文献。

谢谢,但出于某种原因它只返回CommandBase类,而不是继承自它的类。另外,如果我像这样调用它"GetInstances<CommandBase>()",那么它会给我一个CommandBase对象列表,而不是我需要的对象-CommandA,CommandB等等。听起来我需要使用接口来使其工作。 - Justin
我为您添加了第二个函数,供您在CommandBase类中使用。您应该检索一组继承自CommandBase类的实例列表。将其放入简单的代码片段测试中时,它可以正常工作。 - Jeff Sheldon
我最终转而使用接口,并使用了您的第一个代码示例,谢谢! - Justin

9
你必须进行更改。
if(type is CommandBase) 

为了

if(type.IsSubclassOf(typeof(CommandBase)))

如果IsSubclassOf是IsAssignableFrom的逆运算。也就是说,如果t1.IsSubclassOf(t2)为真,则t2.IsAssignableFrom(t1)也为真。

除了我的答案一开始就是正确的以外,:),我还要给你一个+1,因为你是对的,而且我从来没有注意到IsSubclassOf方法,因为我只使用IsAssignableFrom。 - Dr. Wily's Apprentice

8

type is CommandBase 改为 typeof(CommandBase).IsAssignableFrom(type)


+1 当我在打字时,我没有看到你的结果,但是这个 MSDN 说:“如果 IsSubclassOf 是 IsAssignableFrom 的相反。也就是说,如果 t1.IsSubclassOf(t2) 为 true,则 t2.IsAssignableFrom(t1) 也为 true。”所以我们两个都是正确的,我指向了 is 和 == 的答案。 - Akash Kava

2
这是因为你的 type 变量是一个 Type,而不是 CommandBase
你想要的是:
if(type == typeof(CommandBase))

(感谢Greg的纠正)



1
最终是使用“==”而不是“is”,但是没错,它起作用了,谢谢。对于我的第二个问题有什么建议吗? - Justin
我可以告诉你,它被转换为CommandBase,因为你将其转换为一个,但不幸的是,我不确定如何将其转换为更高级别的类,因为在那一点上你会失去类型安全。通常情况下,对于这样的东西,你希望有一个接口,在所有派生类中都可以调用相同的函数。 - Jess
我怀疑它甚至无法编译,因为“Is”需要类名而不是类型。 - Akash Kava
修复了答案中的is -> ==。 - Jess

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