# 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,你可以轻松地替换掉实际组件,而无需修改主应用程序的代码。这对于单元测试、集成测试和性能测试等场景非常有用。