【问题标题】:Interface and Implementation in Separate Assemblies Using Reflection使用反射的单独程序集中的接口和实现
【发布时间】:2011-08-16 11:46:45
【问题描述】:

我有两个程序集; AssemblyWithInterfaceAssemblyWithClass。在AssemblyWithInterface 中,我有一个名为IDoSomething 的接口,它由TheClassAssemblyWithClass 中实现。 (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 的各种实现。

【问题讨论】:

    标签: c# reflection


    【解决方案1】:

    接口不应该关心实现。

    重构并将所有逻辑移至第三个程序集。

    更新:

    规范汇编

    • 公共接口 ICommand
    • 公共接口 ICommandFactory

    类组装(参考规范)

    • 内部类 CreateUserCommand:ICommand
    • 公共类 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 对象。

    【讨论】:

    • 但这就是我正在做的。我想从 AssemblyWithClasses (低级实现)动态加载所有“命令”,并将它们视为 AssemblyWithInterface (高级实现)中的接口。
    • @antirysm:AssebmlyWithInterface 不应该知道接口实现。代码Assembly.Load(AssemblyWithClass) 是错误的,因为您将明确的程序集和实现类硬编码到接口程序集,而您应该使用 jgauffin 建议的第三个程序集。
    • @Boris B. - 我们将让 AssemblyWithClass 加载配置设置,从而将整个程序集“注入”到 AssemblyWithInterface 中。如果我没记错的话,第三个程序集意味着 AssemblyWithInterface 需要引用 AssemblyWithClass。这是不可能的,我不想将任何引用的依赖项添加到 AssemblyWithInterface。如果我在第三个程序集中同时拥有接口和实现,我将看不到如何在 AssemblyWithInterface 中执行“命令”。
    • 没有。第三个程序集将加载规范和实现程序集。第三个程序集可以随时从您的类程序集切换到获得接口程序集实现的任何其他程序集。这就是将接口与实现分开的重点。
    • 好吧,我看看能不能看懂。如果我们切换到命令模式:我在 AssemblyCommands 中有一个“ICommand”和“CommandImpl”。如果不添加从 AssemblyExecuting 到 AssemblyCommands 的引用,我将如何从 AssemblyExecuting 执行“CommandImpl”(将其视为“ICommand”)? AssemblyExecuting 如何知道“ICommand”是什么?
    【解决方案2】:

    你这里有一种循环引用,这绝对不理想,但应该可以做你想做的事。看看AppDomain.AssemblyResolve。我怀疑正在发生的事情是反射实例化类导致系统加载原始接口程序集的额外副本,因此它实现的接口类型不再与您静态引用的类型相同。通过实现 AssemblyResolve,您可以使用 AppDomain.GetAssemblies 搜索已加载程序集的列表,并返回具有匹配名称的程序集。

    这是一种可能的实现方式:

        private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            return AppDomain.CurrentDomain.GetAssemblies().
                         FirstOrDefault(assembly => assembly.FullName == args.Name);
        }
    

    【讨论】:

    • 是的,我怀疑环境 (ASP.NET) 有问题。只需从针对硬盘驱动器上的 DLL 的命令行执行 Assembly.LoadFrom 即可。
    • @jguaffin,执行静态编译时不能有循环依赖,但这是在运行时通过反射加载程序集,允许引用已加载的程序集。因为没有静态类型的依赖,所以一切都已经编译好了,没有问题。禁止循环依赖纯粹是为了编译器;这只是因为编译器需要类型信息,在编译引用的程序集之前不存在。
    • @antirysm,我之前在动态加载程序集时遇到过这个问题。您最终可能会遇到奇怪的事情,例如检索 TypeConverters 加载程序集的副本,从而导致奇怪的错误。没有禁止将同一个程序集多次加载到 AppDomain 中,因此需要注意这一点。
    • @antirysm,如果您使用的是 ASP.NET,则可能会遇到阴影问题,即正在加载的程序集是从阴影目录中加载的。这可以允许替换底层程序集文件,而不会干扰已经运行的代码。在这种情况下,AssemblyResolve 仍应解决您的问题,但您必须小心如何找到要加载的“插件”。我对此工作不多,但我记得 AppDomain 中需要一些设置以确保加载程序集使用正确的阴影。
    • @Dan Bryant - 非常感谢您对“可疑”部分的解释。我只使用控制台应用程序进行了一些实验,而没有遇到任何问题。我明天要用你的代码试试看。 (将用背景更新我的问题......)
    猜你喜欢
    • 1970-01-01
    • 2015-10-21
    • 2021-09-26
    • 2010-12-03
    • 2012-07-04
    • 2010-09-09
    • 2011-10-31
    • 2015-09-16
    • 2013-01-18
    相关资源
    最近更新 更多