具有插件之间共享接口的 C# 插件架构

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/829597/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 03:20:39  来源:igfitidea点击:

C# Plugin Architecture with interfaces share between plugins

c#pluginsinterfaceobserver-pattern

提问by Erik Beijer

I divided my problem into a short and a long version for the people with little time at hand.

我将我的问题分为短版和长版,供手头时间不多的人使用。

Short version:

精简版:

I need some architecture for a system with provider and consumer plugins. Providers should implement intereface IProvider and consumers should implement IConsumer. The executing application should only be aware of IProvider and IConsumer. A consumer implementation can ask the executing assembly (by means of a ServiceProcessor) which providers implement InterfaceX and gets a List back. These IProvider objects should be casted to InterfaceX (in the consumer) to be able to hook the consumer onto some events InterfaceX defines. This will fail because the executing assembly somehow doesn't know this InterfaceX type (cast fails). Solution would be to include InterfaceX into some assembly that both the plugins and the executing assembly reference but this should mean a recompile for every new provider/consumer pair and is highly undesireable.

我需要一些具有提供者和消费者插件的系统的架构。提供者应该实现接口 IProvider,消费者应该实现 IConsumer。正在执行的应用程序应该只知道 IProvider 和 IConsumer。消费者实现可以询问正在执行的程序集(通过 ServiceProcessor)哪些提供者实现了 InterfaceX 并返回一个 List。这些 IProvider 对象应该被转换为 InterfaceX(在消费者中),以便能够将消费者挂钩到 InterfaceX 定义的某些事件上。这将失败,因为执行程序集不知何故不知道此 InterfaceX 类型(转换失败)。解决方案是将 InterfaceX 包含到插件和执行程序集都引用的某个程序集中,但这应该意味着为每个新的提供者/消费者对重新编译,这是非常不可取的。

Any suggestions?

有什么建议?

Long version:

长版:

I'm developing some sort of generic service that will use plugins for achieving a higher level of re-usability. The service consists of some sort of Observer pattern implementation using Providers and Consumers. Both providers and Consumers should be plugins for the main application. Let me first explain how the service works by listing the projects I have in my solution.

我正在开发某种通用服务,它将使用插件来实现更高级别的可重用性。该服务由使用提供者和消费者的某种观察者模式实现组成。提供者和消费者都应该是主应用程序的插件。让我首先通过列出我的解决方案中的项目来解释该服务的工作原理。

Project A: A Windows Service project for hosting all plugins and basic functionality. A TestGUI Windows Forms project is used for easier debugging. An instance of the ServiceProcessor class from Project B is doing the plugin related stuff. The subfolders "Consumers" and "Providers" of this project contains subfolders where every subfolder holds a consumer or provider plugin assebly respectively.

项目 A:用于托管所有插件和基本功能的 Windows 服务项目。TestGUI Windows 窗体项目用于更轻松的调试。来自项目 B 的 ServiceProcessor 类的一个实例正在做插件相关的事情。该项目的子文件夹“Consumers”和“Providers”包含子文件夹,其中每个子文件夹分别包含一个使用者或提供者插件。

Project B: A Class library holding the ServiceProcessor class (that does all plugin loading and dispatching between plugins, etc), IConsumer and IProvider.

项目 B:一个包含 ServiceProcessor 类的类库(它执行所有插件加载和插件之间的调度等)、IConsumer 和 IProvider。

Project C: A Class library, linked to project B, consisting of TestConsumer (implementing IConsumer) and TestProvider (implementing IProvider). An additional interface (ITest, itself derived from IProvider) is implemented by the TestProvider.

项目C:一个类库,链接到项目B,由TestConsumer(实现IConsumer)和TestProvider(实现IProvider)组成。TestProvider 实现了一个额外的接口(ITest,它本身是从 IProvider 派生的)。

The goal here is that a Consumer plugin can ask the ServiceProcessor which Providers (implementing at least IProvider) it has). The returned IProvider objects should be casted to the other interface it implements (ITest) in the IConsumer implementation so that the consumer can hook event handlers to the ITest events.

这里的目标是消费者插件可以询问 ServiceProcessor 它有哪些提供者(至少实现 IProvider)。返回的 IProvider 对象应该被转换到它在 IConsumer 实现中实现的另一个接口 (ITest),以便消费者可以将事件处理程序挂接到 ITest 事件。

When project A starts, the subfolders containing the consumer and provider plugins are loaded. Below are some problems I've encountered so far and tried to solve.

项目 A 启动时,将加载包含使用者和提供者插件的子文件夹。以下是我迄今为止遇到并试图解决的一些问题。

The interface ITest used to reside in Project C, since this only applies to methods and events TestProvider and TestConsumer are aware of. The general idea is to keep project A simple and unaware of what the plugins do with each other.

接口 ITest 曾经驻留在项目 C 中,因为这仅适用于 TestProvider 和 TestConsumer 知道的方法和事件。总体思路是保持项目 A 简单,不知道插件之间的作用。

With ITest in project C there and code in the Initialize method of the TestConsumer that casts the IProvider to ITest (this whould not fail in a single class library itself when an object implementing ITest is known as an IConsumer object) an invalid casting error would occur. This error can be solved by placing the ITest interface into project B that is referenced by project A as well. It is highly unwanted though since we need to recompile project A when a new interface is build.

在项目 C 中使用 ITest 并且 TestConsumer 的 Initialize 方法中的代码将 IProvider 转换为 ITest(当实现 ITest 的对象被称为 IConsumer 对象时,这不会在单个类库本身中失败)会发生无效的转换错误. 可以通过将 ITest 接口放入项目 A 引用的项目 B 来解决此错误。这是非常不需要的,因为我们需要在构建新接口时重新编译项目 A。

I tried to put ITest in a single class library referenced by project C only, since only the provider and consumer need to be aware of this interface, but with no success: when loading the plugin the CLR states the referenced project could not be found. This could be solved by hooking on the AssemblyResolve event of the current AppDomain but somehow this seems unwanted as well. ITest went back to Project B again.

我试图将 ITest 放在仅由项目 C 引用的单个类库中,因为只有提供者和使用者需要知道这个接口,但没有成功:加载插件时,CLR 声明找不到引用的项目。这可以通过挂钩当前 AppDomain 的 AssemblyResolve 事件来解决,但不知何故,这似乎也是不需要的。ITest又回到了Project B。

I tried to split project C into a separate project for the consumer and provider and both load the assemblies which itself work well: both assemblies are resident in the Assemblies collection or the current AppDomain: Assembly found: Datamex.Projects.Polaris.Testing.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2813de212e2efcd3 Assembly found: Datamex.Projects.Polaris.Testing.Consumers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ea5901de8cdcb258

我试图将项目 C 拆分为消费者和提供者的单独项目,并且都加载本身运行良好的程序集:两个程序集都驻留在 Assemblies 集合或当前的 AppDomain:Assembly found:Datamex.Projects.Polaris.Testing.Providers , Version=1.0.0.0, Culture=neutral, PublicKeyToken=2813de212e2efcd3 找到的程序集:Datamex.Projects.Polaris.Testing.Consumers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ea5901de8cdcb258

Since the Consumer uses the Provider a reference was made from the Consumer to the Provider. Now the AssemblyResolve event fired again stating it needs the following file: AssemblyName=Datamex.Projects.Polaris.Testing.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2813de212e2efcd3

由于消费者使用提供者,因此从消费者到提供者进行了引用。现在再次触发 AssemblyResolve 事件,说明它需要以下文件:AssemblyName=Datamex.Projects.Polaris.Testing.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2813de212e2efcd3

My questions: Why is this? This file is already loaded right? Why is the cast from IProvider to some interface I know it implements impossible? This is probably because the executing program itself doesn't know this interface, but can't this be loaded dynamically?

我的问题:这是为什么?这个文件已经加载了吧?为什么从 IProvider 转换到我知道它实现的某个接口是不可能的?这可能是因为执行程序本身不知道这个接口,但是这个不能动态加载吗?

My ultimate goal: Consumer plugins ask the ServiceProcessor which Providers it has that do implement Interface x. The providers can be casted to this interface x, without executing assembly being aware of interface x.

我的最终目标:消费者插件询问 ServiceProcessor 它有哪些提供者实现了接口 x。提供者可以被强制转换到这个接口 x,而无需执行程序集知道接口 x。

Somebody that can help?

有人可以帮忙吗?

Thanks in advance, Erik

提前致谢,埃里克

采纳答案by Martin Harris

I just tried to recreate your solution as best as I can, and I have no such issues. (Warning, lots of code samples follow....)

我只是尽可能地重新创建您的解决方案,我没有这样的问题。(警告,后面有很多代码示例......)

First project is the application, this contains one class:

第一个项目是应用程序,它包含一个类:

public class PluginLoader : ILoader
{
    private List<Type> _providers = new List<Type>();

    public PluginLoader()
    {
        LoadProviders();
        LoadConsumers();
    }

    public IProvider RequestProvider(Type providerType)
    {
        foreach(Type t in _providers)
        {
            if (t.GetInterfaces().Contains(providerType))
            {
                return (IProvider)Activator.CreateInstance(t);
            }
        }
        return null;
    }

    private void LoadProviders()
    {
        DirectoryInfo di = new DirectoryInfo(PluginSearchPath);
        FileInfo[] assemblies = di.GetFiles("*.dll");
        foreach (FileInfo assembly in assemblies)
        {
            Assembly a = Assembly.LoadFrom(assembly.FullName);
            foreach (Type type in a.GetTypes())
            {
                if (type.GetInterfaces().Contains(typeof(IProvider)))
                {
                    _providers.Add(type);
                }
            }
        }

    }

    private void LoadConsumers()
    {
        DirectoryInfo di = new DirectoryInfo(PluginSearchPath);
        FileInfo[] assemblies = di.GetFiles("*.dll");
        foreach (FileInfo assembly in assemblies)
        {
            Assembly a = Assembly.LoadFrom(assembly.FullName);
            foreach (Type type in a.GetTypes())
            {
                if (type.GetInterfaces().Contains(typeof(IConsumer)))
                {
                    IConsumer consumer = (IConsumer)Activator.CreateInstance(type);
                    consumer.Initialize(this);
                }
            }
        }
    }

Obviously this can be tidied up enormously.

显然,这可以极大地整理。

Next project is the shared library which contains the following three interfaces:

下一个项目是共享库,包含以下三个接口:

public interface ILoader
{
    IProvider RequestProvider(Type providerType);
}

public interface IConsumer
{
    void Initialize(ILoader loader);
}

public interface IProvider
{
}

Finally there is the plugin project with these classes:

最后是带有这些类的插件项目:

public interface ITest : IProvider
{        
}

public class TestConsumer : IConsumer
{
    public void Initialize(ILoader loader)
    {
        ITest tester = (ITest)loader.RequestProvider(typeof (ITest));
    }
}

public class TestProvider : ITest
{        
}

Both the application and the plugin projects reference the shared project and the plugin dll is copied to the search directory for the application - but they don't reference one another.

应用程序和插件项目都引用了共享项目,并且插件 dll 被复制到应用程序的搜索目录 - 但它们并不相互引用。

When the PluginLoader is constructed it finds all the IProviders then creates all the IConsumers and calls Initialize on them. Inside the initialize the consumer can request providers from the loader and in the case of this code a TestProvider is constructed and returned. All of this works for me with no fancy control of the loading of assemblies.

当构建 PluginLoader 时,它会找到所有 IProvider,然后创建所有 IConsumers 并调用它们的 Initialize。在初始化内部,消费者可以从加载器请求提供者,在此代码的情况下,一个 TestProvider 被构造并返回。所有这些都对我有用,对程序集的加载没有花哨的控制。

回答by edosoft

If you're question is, how can two unrelated assemblies share the same interface, the answer is 'you cannot' A solution is to include the interface in all assemblies, perhaps in a dll that the plugin-builders can reference, and in your loading assembly.

如果您的问题是,两个不相关的程序集如何共享同一个接口,答案是“您不能” 解决方案是将接口包含在所有程序集中,可能包含在插件构建器可以引用的 dll 中,并在您的加载组件。

回答by JohnOpincar

I've done something similar to what you are trying to do and as long as I had the assemblies in a place where the loader looked automatically, I didn't encounter any problems.

我已经做了一些类似于你想要做的事情,只要我把程序集放在加载器自动查看的地方,我就没有遇到任何问题。

Have you tried putting all your assemblies in a subdirectory underneath where the exe resides? I can't remember the details now but there's a list of steps documented as to exactly where and in what order the loader looks for assemblies/types.

您是否尝试将所有程序集放在 exe 所在的子目录中?我现在不记得细节了,但是有一个步骤列表记录了加载程序查找程序集/类型的确切位置和顺序。

回答by Redth

You might find my articles useful to see a working example of a plugin framework, and how these issues are addressed by creating a common assembly holding the interfaces:

您可能会发现我的文章有助于查看插件框架的工作示例,以及如何通过创建包含接口的通用程序集来解决这些问题:

Plugins in C# Basic Tutorial:

C# 基础教程中的插件:

http://www.codeproject.com/KB/cs/pluginsincsharp.aspx

http://www.codeproject.com/KB/cs/pluginsincsharp.aspx

Followup article, with a Generics enabled Plugin Manager Library:

后续文章,带有启用泛型的插件管理器库:

http://www.codeproject.com/KB/cs/ExtensionManagerLibrary.aspx

http://www.codeproject.com/KB/cs/ExtensionManagerLibrary.aspx

回答by Alex

It is still in development, but is sounds like a perfect usecase for MEF (to be included in .Net 4) and used internally in VS2010.

它仍在开发中,但听起来像是 MEF 的完美用例(将包含在 .Net 4 中)并在 VS2010 内部使用。

MEF presents a simple solution for the runtime extensibility problem. Until now, any application that wanted to support a plugin model needed to create its own infrastructure from scratch. Those plugins would often be application-specific and could not be reused across multiple implementations.

MEF 为运行时可扩展性问题提供了一个简单的解决方案。到目前为止,任何想要支持插件模型的应用程序都需要从头开始创建自己的基础设施。这些插件通常是特定于应用程序的,不能在多个实现中重复使用。

Previews are already available on http://www.codeplex.com/MEF

预览版已在http://www.codeplex.com/MEF 上提供

The blogof Glen Block can also be useful.

Glen Block的博客也很有用。