我有两个程序集;AssemblyWithInterface
和AssemblyWithClass
。在AssemblyWithInterface
中,我有一个名为IDoSomething
的接口,由TheClass
在AssemblyWithClass
中实现。(AssemblyWithClass
引用AssemblyWithInterface
.)
在AssemblyWithInterface
中,我想使用反射从AssemblyWithClass
创建类的实例:
var theAssembly = Assembly.Load("Company.AssemblyWithClass, { FQN... }");
var theConcreteClass = theAssembly.CreateInstance("Company.AssemblyWithClass.TheClass");
程序集加载良好,实例被创建为TheConcreteClass
。
但是,我无法将theConcreteClass
强制转换为它的实现接口。我在这里得到一个InvalidCastException
:
var theConcreteClassInterfaced = (IDoSomething)theConcreteClass;
和
var isAssignable = typeof(IDoSomething).IsAssignableFrom(theConcreteClass.GetType();
是错误的。
我错过了什么?(目标是拥有命令模式样式的能力,将实现IDoSomething
的命令添加到AssemblyWithClass
中,并能够在AssemblyWithInterface
中执行它们,而无需更改AssemblyWithInterface
中的代码。)
平台是。net 3.5(不能使用动态)。
更新:这个问题的背景(解释为什么我不遵守DIP)是我有一个遗留的ASP。包含在一个大型程序集中的。net应用程序。我想创建一个插件程序集,它可以调用遗留程序集的各个部分来执行监视和一些自动化任务。我不希望在遗留程序集中添加任何额外的依赖项(对其他程序集的引用)。我们的想法是在遗留程序集中实现一个钩子(一个新的特殊页面和一个IPlugInOperation),在后面添加一个带有相应代码的监控页面。让后面的代码执行各种IPlugInOperations(绘制一个接口,允许管理员指定用于执行遗留程序集中代码的参数)。插件程序集必须引用旧程序集,旧程序集使用反射来列出并允许管理员执行插件程序集中包含的IPlugInOperation的各种实现。
接口不应该关心实现。
重构并将所有逻辑移动到第三个程序集。
更新:
规范装配
- 公共接口命令
- ICommandFactory
类程序集(引用规范)
- 内部类CreateUserCommand: iccommand
- 公共类CommandFactory: ICommandFactory
应用程序程序集(引用两者)
public class Program
{
private ICommandFactory _commandFactory;
public static void Main(string[] argv)
{
// this is the only line that is really dependent of a specific
// implementation.
_commandFactory = new TheSpecificImplementationAssembly.CommandFactory();
ICommand command = _commandFactory.Create("CreateUser");
command.Execute();
}
}
更新2
大多数现代解决方案使用控制反转容器来处理接口和实现之间的映射。
另一个解决方案是有一个小的工厂集合,用于创建特定接口的实现。在这种情况下,我还将使用工厂方法模式让聚合根能够创建子聚合。例如,类Order
将有一个名为CreateOrderLine
的方法,该方法将返回一个IOrderLine
对象。
这里有一种循环引用,这肯定不是理想的,但它应该可以实现您所追求的。看一下AppDomain.AssemblyResolve。我怀疑正在发生的事情是反射实例化类导致系统加载原始接口程序集的额外副本,因此它实现的接口类型不再是您静态引用的相同类型。通过实现AssemblyResolve,您可以使用AppDomain搜索已加载程序集的列表。GetAssemblies,返回名称匹配的那个。
这是一个可能的实现:
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
return AppDomain.CurrentDomain.GetAssemblies().
FirstOrDefault(assembly => assembly.FullName == args.Name);
}