核心思想:**平台(主程序)+ 插件(功能模塊)**。主程序預先定義擴展點(接口),插件實現接口並部署為獨立 DLL。主程序運行時動態發現、加載、實例化插件,**無需重新編譯、重啟主程序即可擴展功能**。
FileSystemWatcher 可熱重載)。├── PluginContracts.dll // 共享接口定義(公共契約)
├── Plugins/ // 插件目錄
│ ├── Plugin.A.dll // 實現 IPlugin
│ └── Plugin.B.dll
└── HostApp.exe // 主程序,動態加載 Plugins/*.dll
// PluginContracts.dll
namespace PluginContracts {
public interface IPlugin {
string Name { get; }
string Version { get; }
void Initialize(IHostContext context); // 主程序提供上下文(日志、配置、服務)
void Execute(PluginParameters args);
event EventHandler OnProgress; // 可選:進度回報
}
// 輔助類型
public interface IHostContext {
ILogger Logger { get; }
IConfiguration Config { get; }
T GetService(); // 服務定位器
}
public class PluginParameters { /* 輸入參數 */ }
public class PluginEventArgs : EventArgs { /* 進度數據 */ }
}
// Plugin.A.dll
using PluginContracts;
[Export(typeof(IPlugin))] // 可選:MEF/Autofac 屬性
public class DataExportPlugin : IPlugin {
public string Name => "數據導出插件";
public string Version => "1.2.0";
private ILogger _logger;
public void Initialize(IHostContext context) {
_logger = context.Logger;
}
public void Execute(PluginParameters args) {
_logger.Info($"開始導出:{args.OutputPath}");
// 具體導出邏輯...
OnProgress?.Invoke(this, new PluginEventArgs { Progress = 100 });
}
public event EventHandler OnProgress;
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using PluginContracts;
public class PluginManager {
private readonly string _pluginDir;
private readonly IHostContext _hostContext;
private readonly List _loadedPlugins = new();
public PluginManager(string pluginDir, IHostContext hostContext) {
_pluginDir = pluginDir;
_hostContext = hostContext;
}
public IReadOnlyList LoadAll() {
_loadedPlugins.Clear();
if (!Directory.Exists(_pluginDir)) return _loadedPlugins;
foreach (var file in Directory.GetFiles(_pluginDir, "*.dll")) {
try {
var asm = Assembly.LoadFrom(file);
var pluginTypes = asm.GetTypes()
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
foreach (var type in pluginTypes) {
var plugin = (IPlugin)Activator.CreateInstance(type);
plugin.Initialize(_hostContext);
_loadedPlugins.Add(plugin);
Console.WriteLine($"已加載插件:{plugin.Name} v{plugin.Version}");
}
}
catch (Exception ex) {
Console.Error.WriteLine($"加載插件失敗 {file}:{ex.Message}");
}
}
return _loadedPlugins;
}
public void ExecuteAll(PluginParameters args) {
foreach (var p in _loadedPlugins) {
try { p.Execute(args); }
catch (Exception ex) { _hostContext.Logger.Error($"插件 {p.Name} 執行失敗", ex); }
}
}
// 熱重載支援
public void Reload() => LoadAll();
}
.NET Core / .NET 5+ 內建 System.ComponentModel.Composition (MEF) 或 Microsoft.Extensions.DependencyModel,提供更豐富的元數據、版本協商、依賴注入整合。
// MEF 範例
[ImportMany(typeof(IPlugin))]
public IEnumerable> Plugins { get; set; }
var catalog = new DirectoryCatalog(@".\Plugins");
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
foreach (var lazyPlugin in Plugins) {
Console.WriteLine($"發現插件:{lazyPlugin.Metadata.Name} v{lazyPlugin.Metadata.Version}");
lazyPlugin.Value.Initialize(_hostContext);
}
| 決策點 | 推薦做法 | 反模式 |
|---|---|---|
| 主程序是否引用插件實現 | **絕不引用**,僅引用契約程序集 | 主程序 `new PluginA()` 破壞解耦 |
| 插件之間通信 | 透過主程序提供的事件總線/服務總線 | 插件直接引用其他插件 DLL |
| 版本衝突 | 插件獨立依賴,必要時用 `AssemblyLoadContext` 隔離 | 所有插件共享同一 `Newtonsoft.Json` 版本 |
| 安全性 | 簽名驗證、沙箱隔離、權限聲明 | 任意 DLL 放入目錄即執行 |
| 協議變更 | 接口版本化(`IPluginV2`)或擴展方法 | 修改 `IPlugin` 導致所有插件失效 |
插件式架構的核心在於 **「契約優先、動態發現、生命週期隔離」**。C# 配合 `AssemblyLoadContext`(.NET Core 3.0+)可實現插件級依賴隔離,徹底解決「DLL 地獄」。從簡單的 `Directory.GetFiles` + `Activator.CreateInstance` 起步,隨業務演進引入 MEF/Autofac,是演進式架構的最佳實踐。