# MEFTest **Repository Path**: kaiouyang-sn/meftest ## Basic Information - **Project Name**: MEFTest - **Description**: 使用MEF来加载和使用扩展插件的代码案例 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-04-15 - **Last Updated**: 2024-12-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 使用MEF来加载和使用扩展插件 确保您的项目中已经添加了System.ComponentModel.Composition的引用。在.NET Framework项目中,这通常意味着需要添加对System.ComponentModel.Composition.dll的引用。 ### 定义契约接口 首先,您需要定义一个或多个接口或基类,这些将作为您的扩展点。扩展点定义了插件需要实现的功能。 ```csharp namespace MechTE.Test.MEF { /// /// 定义了消息发送者的契约。 /// 实现此接口的类将能够提供发送消息的功能。 /// 这将作为扩展点,供插件实现具体的消息发送逻辑。 /// public interface IMessageSender { /// /// 发送一条消息。 /// /// 要发送的消息内容。 void Send(string message); } } ``` ### 创建插件 用[Export(typeof(IMessageSender))]特性来标记,表明它们是可以被MEF框架发现并加载的插件。 ```csharp using System; using System.ComponentModel.Composition; namespace MechTE.Test.MEF { [Export(typeof(IMessageSender))] public class EmailSender : IMessageSender { public void Send(string message) { Console.WriteLine($@"EmailSender: {message}"); // 这里可以添加实际的电子邮件发送代码 } public void ExFile() { Console.WriteLine($@"EmailSender: 模拟执行文件操作"); } } } using System; using System.ComponentModel.Composition; namespace MechTE.Test.MEF { [Export(typeof(IMessageSender))] public class SmsSender : IMessageSender { public void Send(string message) { Console.WriteLine($@"SmsSender: {message}"); // 这里可以添加实际的短信发送代码 } public void ExFile() { Console.WriteLine($@"SmsSender: 模拟执行文件操作"); } } } ``` ### 创建宿主程序 在宿主程序中,您将使用MEF来加载插件,并调用它们的功能。 ```csharp using System; using System.Collections.Generic; using System.ComponentModel.Composition; // 引入MEF相关的命名空间 using System.ComponentModel.Composition.Hosting; using System.Linq; using System.Reflection; using MechTE.Test.MEF; // 引入自定义的命名空间,可能包含IMessageSender接口和相关的导出类 class Program { static void Main(string[] args) { try { var program = new Program(); program.ComposePlugins(); // 调用Compose方法来组合插件 program.Run(); // 运行程序,执行插件的功能 } catch (Exception ex) { // 捕获并打印任何可能发生的异常 Console.WriteLine(@"An error occurred: " + ex.Message); } } // Compose方法用于组合插件 private void ComposePlugins() { var pluginCatalog = CreatePluginCatalog(); // 创建插件目录 using (var compositionContainer = new CompositionContainer(pluginCatalog)) { compositionContainer.ComposeParts(this); } } // 创建插件目录的方法 private AggregateCatalog CreatePluginCatalog() { // 创建一个空的聚合目录 var aggregateCatalog = new AggregateCatalog(); // 添加当前程序集的目录到聚合目录中 var currentAssemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); aggregateCatalog.Catalogs.Add(currentAssemblyCatalog); // 如果需要,可以在此处添加其他程序集的目录 // 例如,假设我们有一个名为otherAssembly的变量,它引用了另一个程序集 // var otherAssemblyCatalog = new AssemblyCatalog(otherAssembly); // aggregateCatalog.Catalogs.Add(otherAssemblyCatalog); // 返回填充好的聚合目录 return aggregateCatalog; } // 使用ImportMany特性并指定导入类型为IMessageSender, // 允许默认的重构和允许重新组合来动态更新插件列表 [ImportMany(typeof(IMessageSender), AllowRecomposition = true)] public IEnumerable MessageSenders { get; set; } // 运行程序,执行插件的功能 private void Run() { // 定义要发送的消息内容 const string messageToSend = "Hello from the host!"; // 检查插件实例是否已经被成功导入 if (MessageSenders == null || !MessageSenders.Any()) { Console.WriteLine(@"No message senders are available."); return; } // 遍历所有导入的插件实例,并发送消息 foreach (var sender in MessageSenders) { try { // 调用插件的Send方法 sender.Send(messageToSend); sender.ExFile(); } catch (Exception ex) { // 可以在这里添加适当的日志记录或错误处理 Console.WriteLine($@"An error occurred while sending message: {ex.Message}"); } } } } ``` ### 使用元数据来区分功能 使用MEF的元数据功能来为每个导出添加额外的描述信息。这样,您可以根据这些元数据来决定如何调用不同的导出。 接口部分保持不变 #### 定义一个元数据视图 ```csharp namespace MechTE.Test.MEF { /// /// 定义一个接口来包含插件的元数据 /// public interface IMessageSenderMetadata { string Send { get; } } } ``` #### 修改导出包含元的数据 新增[ExportMetadata("Send", "Sms")] ```csharp using System; using System.ComponentModel.Composition; namespace MechTE.Test.MEF { [Export(typeof(IMessageSender))] [ExportMetadata("Send", "Sms")] public class SmsSender : IMessageSender { public void Send(string message) { Console.WriteLine($@"SmsSender: {message}"); // 这里可以添加实际的短信发送代码 } } } using System; using System.ComponentModel.Composition; namespace MechTE.Test.MEF { [Export(typeof(IMessageSender))] [ExportMetadata("Send", "Email")] public class EmailSender : IMessageSender { public void Send(string message) { Console.WriteLine($@"EmailSender: {message}"); // 这里可以添加实际的电子邮件发送代码 } } } ``` #### 主程序中加载和使用带有元数据的导出 ```csharp using System; using System.Collections.Generic; using System.ComponentModel.Composition; // 引入MEF相关的命名空间 using System.ComponentModel.Composition.Hosting; using System.Reflection; using MechTE.Test.MEF; // 引入自定义的命名空间,可能包含IMessageSender接口和相关的导出类 class Program { static void Main(string[] args) { try { var program = new Program(); program.ComposePlugins(); // 调用Compose方法来组合插件 program.Run(); // 运行程序,执行插件的功能 } catch (Exception ex) { // 捕获并打印任何可能发生的异常 Console.WriteLine(@"An error occurred: " + ex.Message); } } private CompositionContainer _compositionContainer; /// /// Compose方法用于组合插件 /// private void ComposePlugins() { var pluginCatalog = CreatePluginCatalog(); // 创建插件目录 _compositionContainer = new CompositionContainer(pluginCatalog); _compositionContainer.ComposeParts(this); // 注意:现在不再使用 using 语句,因此需要在程序结束时手动释放 CompositionContainer } /// /// 在程序的某个适当位置(比如 Main 方法的最后),释放 CompositionContainer /// private void Dispose() { if (_compositionContainer != null) { _compositionContainer.Dispose(); _compositionContainer = null; } } /// /// 创建插件目录的方法 /// /// private AggregateCatalog CreatePluginCatalog() { // 创建一个空的聚合目录 var aggregateCatalog = new AggregateCatalog(); // 添加当前程序集的目录到聚合目录中 var currentAssemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); aggregateCatalog.Catalogs.Add(currentAssemblyCatalog); // 如果需要,可以在此处添加其他程序集的目录 // 例如,假设我们有一个名为otherAssembly的变量,它引用了另一个程序集 // var otherAssemblyCatalog = new AssemblyCatalog(otherAssembly); // aggregateCatalog.Catalogs.Add(otherAssemblyCatalog); // 返回填充好的聚合目录 return aggregateCatalog; } // 使用Lazy来同时导入插件实例和元数据 [ImportMany(typeof(IMessageSender), AllowRecomposition = true)] public IEnumerable> MessageSenders { get; set; } /// /// 运行程序,执行插件的功能 /// private void Run() { const string messageToSend = "Hello from the host!"; // 遍历所有带有元数据的插件实例 foreach (var sender in MessageSenders) { // 根据元数据中的Send属性来决定如何调用插件 if (sender.Metadata.Send == "Email") { Console.WriteLine($"Processing email sender: {sender.Metadata.Send}"); sender.Value.Send(messageToSend); // 调用Send方法发送消息 } else if (sender.Metadata.Send == "Sms") { Console.WriteLine($"Processing SMS sender: {sender.Metadata.Send}"); sender.Value.Send(messageToSend); // 调用Send方法发送消息 } } Dispose(); // 释放资源,包括 CompositionContainer 和其管理的对象 } } ``` 请注意,为了使上述代码工作,您可能还需要添加对System.ComponentModel.Composition.Primitives的引用,因为Lazy等类型可能位于此命名空间中。 ### 使用复合方式导出 修改为复合导出(即同时导出多个接口或类型) #### 定义契约接口 ```csharp namespace MechTE.Test.MEF { /// /// 定义了消息发送者的契约。 /// 实现此接口的类将能够提供发送消息的功能。 /// 这将作为扩展点,供插件实现具体的消息发送逻辑。 /// public interface IMessageSender { /// /// 发送一条消息。 /// /// 要发送的消息内容。 void Send(string message); } } namespace MechTE.Test.MEF { /// /// 定义了消息发送者的契约。 /// 实现此接口的类将能够提供发送消息的功能。 /// 这将作为扩展点,供插件实现具体的消息发送逻辑。 /// public interface IMessageSender2 { /// /// 发送一条消息。 /// /// 要发送的消息内容。 void Send2(string message); } } ``` #### 插件类实现多个接口 EmailSender类通过[Export]属性被标记为同时导出了IMessageSender和IMessageSender2。这意味着当MEF容器进行组合时,任何导入了这两个接口的组件都可以接收到EmailSender的实例 ```csharp using System; using System.ComponentModel.Composition; namespace MechTE.Test.MEF { [Export(typeof(IMessageSender))] // 导出为 IMessageSender [Export(typeof(IMessageSender2))] // 导出为 IMessageSender2 public class EmailSender : IMessageSender, IMessageSender2 { public void Send(string message) { Console.WriteLine($@"EmailSender : {message}"); // 这里可以添加实际的电子邮件发送代码 } public void Send2(string message) { Console.WriteLine($@"EmailSender Send2: {message}"); // 这里可以添加实际的电子邮件发送代码 } } } ``` #### 宿主程序添加导入定义 ```csharp // 在 Program 类中添加新的导入定义 [ImportMany(typeof(IMessageSender2))] public IEnumerable> MessageSenders2 { get; set; } [ImportMany(typeof(IMessageSender2))] public IEnumerable> MessageSenders1 { get; set; } ``` 完整内容 ```csharp using System; using System.Collections.Generic; using System.ComponentModel.Composition; // 引入MEF相关的命名空间 using System.ComponentModel.Composition.Hosting; using System.Reflection; using MechTE.Test.MEF; // 引入自定义的命名空间,可能包含IMessageSender接口和相关的导出类 class Program { static void Main(string[] args) { try { var program = new Program(); program.ComposePlugins(); // 调用Compose方法来组合插件 program.Run(); // 运行程序,执行插件的功能 } catch (Exception ex) { // 捕获并打印任何可能发生的异常 Console.WriteLine(@"An error occurred: " + ex.Message); } } private CompositionContainer _compositionContainer; /// /// Compose方法用于组合插件 /// private void ComposePlugins() { var pluginCatalog = CreatePluginCatalog(); // 创建插件目录 _compositionContainer = new CompositionContainer(pluginCatalog); _compositionContainer.ComposeParts(this); // 注意:现在不再使用 using 语句,因此需要在程序结束时手动释放 CompositionContainer } /// /// 在程序的某个适当位置(比如 Main 方法的最后),释放 CompositionContainer /// private void Dispose() { if (_compositionContainer != null) { _compositionContainer.Dispose(); _compositionContainer = null; } } /// /// 创建插件目录的方法 /// /// private AggregateCatalog CreatePluginCatalog() { // 创建一个空的聚合目录 var aggregateCatalog = new AggregateCatalog(); // 添加当前程序集的目录到聚合目录中 var currentAssemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); aggregateCatalog.Catalogs.Add(currentAssemblyCatalog); // 如果需要,可以在此处添加其他程序集的目录 // 例如,假设我们有一个名为otherAssembly的变量,它引用了另一个程序集 // var otherAssemblyCatalog = new AssemblyCatalog(otherAssembly); // aggregateCatalog.Catalogs.Add(otherAssemblyCatalog); // 返回填充好的聚合目录 return aggregateCatalog; } // 在 Program 类中添加新的导入定义 [ImportMany(typeof(IMessageSender2))] public IEnumerable> MessageSenders2 { get; set; } [ImportMany(typeof(IMessageSender2))] public IEnumerable> MessageSenders1 { get; set; } /// /// 运行程序,执行插件的功能 /// private void Run() { const string messageToSend = "测试!"; foreach (var sender in MessageSenders1) { sender.Value.Send(messageToSend); } // 遍历所有带有元数据的插件实例 foreach (var sender in MessageSenders2) { sender.Value.Send2(messageToSend); } Dispose(); // 释放资源,包括 CompositionContainer 和其管理的对象 } } ``` ### 动态加载和卸载插件 #### 定义你的插件接口 ```csharp namespace MechTE.Test.MEF { /// /// 定义了消息发送者的契约。 /// 实现此接口的类将能够提供发送消息的功能。 /// 这将作为扩展点,供插件实现具体的消息发送逻辑。 /// public interface IMessageSender { /// /// 发送一条消息。 /// /// 要发送的消息内容。 void Send(string message); } } ``` 然后,你可以创建实现了这个接口的插件。这些插件将被编译为独立的程序集(DLLs)。 #### 实现PluginLoader 可以使用CompositionContainer和AggregateCatalog来动态地加载和卸载插件。 ```csharp using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; namespace MEFTest { /// /// 动态地加载和卸载插件 /// public class PluginLoader { private CompositionContainer _container; private AggregateCatalog _catalog; public PluginLoader() { _catalog = new AggregateCatalog(); _container = new CompositionContainer(_catalog); } public void LoadPlugins(string path) { var directoryCatalog = new DirectoryCatalog(path); _catalog.Catalogs.Add(directoryCatalog); } public void UnloadPlugins() { // MEF 不提供卸载插件的直接方法。 // 但是,您可以删除包含插件的目录, // 处理容器,并在需要时创建一个新容器。 // _catalog.Catalogs.Clear(); _container.Dispose(); _container = null; _container = new CompositionContainer(_catalog); } /// /// 调用Compose方法来组合插件 /// public void ComposePlugins() { _container.ComposeParts(this); } [ImportMany(typeof(IMessageSender2))] public IEnumerable> MessageSenders2 { get; set; } [ImportMany(typeof(IMessageSender2))] public IEnumerable> MessageSenders1 { get; set; } /// /// 运行程序,执行插件的功能 /// public void Run() { const string messageToSend = "测试!"; foreach (var sender in MessageSenders1) { sender.Value.Send(messageToSend); } // 遍历所有带有元数据的插件实例 foreach (var sender in MessageSenders2) { sender.Value.Send2(messageToSend); } // 释放资源,包括 CompositionContainer 和其管理的对象 UnloadPlugins(); } } } ``` #### 主程序使用 ```csharp using System; using MEFTest; class Program { static void Main(string[] args) { try { while (true) { var pluginLoader = new PluginLoader(); pluginLoader.LoadPlugins(@"D:\sw\Console\MEFTest\EmailSender\bin\Debug"); // 替换为你的插件路径 pluginLoader.ComposePlugins(); Console.WriteLine("回车执行dll"); Console.ReadKey(); pluginLoader.Run(); } } catch (Exception ex) { Console.WriteLine(@"An error occurred: " + ex.Message); } } } ``` 这种方法并不是真正的动态卸载,因为它不会从进程中卸载DLL。真正的动态加载和卸载DLL涉及更复杂的操作,如使用LoadLibrary和FreeLibrary的P/Invoke调用(在Windows平台上)。然而,对于许多应用场景来说,MEF提供的这种级别的动态性已经足够了。 ### 使用AppDomain实现从进程中动态加载卸载插件 使用MEF (Managed Extensibility Framework) 从进程中动态加载和卸载插件,实际上是通过添加和移除Catalog来实现的。然而,正如前面提到的,MEF并不直接支持从进程中完全卸载DLL。如果你需要完全卸载DLL,你可能需要将插件加载到独立的AppDomain中,并在完成后卸载整个AppDomain。 #### 定义加载和卸载类 使用AppDomain来尝试实现更彻底的插件卸载。但请注意,这种方法更复杂,并且可能引入额外的资源开销和编程复杂性。 ```csharp using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using MEFTest; namespace EmailSender { /// /// 负责插件的加载和卸载 /// public class PluginLoader { // 用于加载插件的独立AppDomain private AppDomain _pluginDomain; // 远程插件加载器,它在_pluginDomain中执行 private RemotePluginLoader _remoteLoader; /// /// 初始化_pluginDomain和_remoteLoader /// public PluginLoader() { // 创建一个新的AppDomain来加载插件 _pluginDomain = AppDomain.CreateDomain("PluginDomain"); // 在新的AppDomain中创建RemotePluginLoader的实例 _remoteLoader = (RemotePluginLoader)_pluginDomain.CreateInstanceAndUnwrap( typeof(RemotePluginLoader).Assembly.FullName, typeof(RemotePluginLoader).FullName); } /// /// 加载指定路径下的插件 /// /// public void LoadPlugins(string path) { _remoteLoader.LoadPlugins(path); } /// /// 卸载已加载的插件,通过卸载并重新创建AppDomain来实现 /// public void UnloadPlugins() { // 通过卸载AppDomain来卸载插件 AppDomain.Unload(_pluginDomain); _pluginDomain = null; // 重新创建一个新的AppDomain以备后续加载插件 _pluginDomain = AppDomain.CreateDomain("PluginDomain"); var fullName = typeof(RemotePluginLoader).FullName; if (fullName != null) // 重新创建一个新的AppDomain和远程加载器 _remoteLoader = (RemotePluginLoader)_pluginDomain.CreateInstanceAndUnwrap( typeof(RemotePluginLoader).Assembly.FullName, fullName); } /// /// 运行已加载的插件 /// public void Run() { _remoteLoader.Run(); // UnloadPlugins(); // 注意:这里注释掉了UnloadPlugins()方法,可能是为了在某些场景下保持插件的加载状态 // 如果需要在运行后立即卸载插件,可以取消此行的注释 } } /// /// RemotePluginLoader类在独立的AppDomain中执行MEF的加载和组合操作 /// MarshalByRefObject允许该类的实例跨AppDomain边界进行通信 /// public class RemotePluginLoader : MarshalByRefObject { // MEF的组合容器 private CompositionContainer _container; // MEF的聚合目录,用于收集多个目录或程序集 private AggregateCatalog _catalog; public RemotePluginLoader() { _catalog = new AggregateCatalog(); _container = new CompositionContainer(_catalog); } /// /// 加载指定路径下的插件 /// /// public void LoadPlugins(string path) { // 从指定路径创建目录目录 var directoryCatalog = new DirectoryCatalog(path); // 将目录目录添加到聚合目录中 _catalog.Catalogs.Add(directoryCatalog); // 组合当前实例,这将填充下面的MessageSenders2属性 _container.ComposeParts(this); } /// /// 使用MEF的ImportMany属性导入所有IMessageSender2接口的实例 /// [ImportMany(typeof(IMessageSender2))] public IEnumerable> MessageSenders2 { get; set; } /// /// 运行已加载的插件,即调用每个IMessageSender2实例的Send2方法 /// public void Run() { const string messageToSend = "测试!"; foreach (var sender in MessageSenders2) { sender.Value.Send2(messageToSend); } } } } ``` #### 使用场景 插件式架构:当你希望你的应用程序能够支持第三方插件或者模块时,可以使用这种架构。通过将插件加载到独立的AppDomain中,可以实现插件与主应用程序之间的隔离,从而提高应用程序的稳定性和安全性。 动态加载和卸载:在某些场景下,你可能希望在运行时动态地加载或卸载插件。例如,你可能想要在不重启应用程序的情况下更新或替换某个插件。通过使用`AppDomain ## MEF执行多个功能 1. 定义更多的接口和实现: 您可以为不同的功能定义不同的接口,并为每个接口提供多个实现。这样,您的主程序可以通过MEF加载并调用这些功能的实现。 2. 使用元数据来区分功能: 您可以使用MEF的元数据功能来为每个导出添加额外的描述信息。这样,您可以根据这些元数据来决定如何调用不同的导出。 3. 使用复合导出: 如果您有一组相关的功能需要一起使用,可以使用[ExportMany]​和[ImportMany]​来管理这些功能的集合。 ## 使用场景 MEF的使用场景非常广泛,特别是在需要构建可扩展的应用程序时。以下是一些具体的使用场景: 1. 插件系统:MEF非常适合用来构建插件系统。你可以定义一组接口,并允许第三方开发者创建实现了这些接口的插件。然后,你的主应用程序可以在运行时动态地加载这些插件,从而增加新的功能或行为。 2. 模块化开发:在大型项目中,你可能希望将不同的功能区域分解为独立的模块。通过使用MEF,你可以更容易地管理和更新这些模块,因为每个模块都可以作为独立的程序集进行编译和部署。 3. 可扩展的UI:如果你正在构建一个用户界面,并希望用户能够自定义某些部分(例如,添加自定义的控件或视图),那么MEF可以帮助你实现这一点。你可以定义一个接口来描述这些自定义控件的行为,并使用MEF来加载用户提供的实现。 4. 测试和模拟:在开发过程中,你可能希望替换某些组件以进行测试或模拟。通过使用MEF,你可以轻松地替换掉实际组件,而无需修改主应用程序的代码。这对于单元测试、集成测试和性能测试等场景非常有用。