插件機制讓系統在不重新編譯主程式的前提下,透過放置 DLL 即可擴充功能。本文示範基於介面契約的標準插件架構,涵蓋介面定義、插件實作、主程式動態發現與載入全流程。
建立獨立專案 PluginInterface,僅定義介面,編譯為 DLL 供主程式與插件共同參考:
// PluginInterface.dll
public interface IPlugin
{
int MovementID { get; set; }
string MovementName { get; set; }
int RetryMinute { get; set; }
string ConnectionString { get; set; }
event Action<string> RaiseLog;
void Initialize();
}
關鍵原則:主程式與插件必須參考同一個介面 DLL 檔案,否則即使介面內容相同,型別身分不同會導致 InvalidCastException。
新建 Class Library 專案,參考 PluginInterface.dll,實作介面:
// MyPlugin.dll
using PluginInterface;
public class DataSyncPlugin : IPlugin
{
public int MovementID { get; set; } = 1001;
public string MovementName { get; set; } = "資料同步作業";
public int RetryMinute { get; set; } = 30;
public string ConnectionString { get; set; }
public event Action<string> RaiseLog;
public void Initialize()
{
// 實際業務邏輯:資料抓取、轉換、寫入
RaiseLog?.Invoke($"[{MovementName}] 開始執行...");
// ...
RaiseLog?.Invoke($"[{MovementName}] 執行完成!");
}
// 內部私有方法,不暴露於介面
private void SaveLog(DateTime dt, string msg) { /* ... */ }
}
編譯輸出 MyPlugin.dll 置於主程式 Plugins 目錄。
using System;
using System.IO;
using System.Reflection;
using PluginInterface;
public class PluginManager
{
private readonly string _pluginDirectory;
public event Action<string> OnPluginLog;
public PluginManager(string pluginDir = "Plugins")
{
_pluginDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, pluginDir);
if (!Directory.Exists(_pluginDirectory))
Directory.CreateDirectory(_pluginDirectory);
}
public IPlugin[] LoadAllPlugins()
{
var plugins = new List<IPlugin>();
var dllFiles = Directory.GetFiles(_pluginDirectory, "*.dll");
foreach (var file in dllFiles)
{
try
{
Assembly asm = Assembly.LoadFrom(file);
foreach (Type type in asm.GetExportedTypes())
{
if (typeof(IPlugin).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract)
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
plugin.RaiseLog += msg => OnPluginLog?.Invoke($"[{plugin.MovementName}] {msg}");
plugins.Add(plugin);
}
}
}
catch (Exception ex)
{
OnPluginLog?.Invoke($"[載入失敗] {file}: {ex.Message}");
}
}
return plugins.ToArray();
}
}
var manager = new PluginManager();
manager.OnPluginLog += msg => Console.WriteLine($"[LOG] {msg}");
var plugins = manager.LoadAllPlugins();
foreach (var p in plugins)
{
Console.WriteLine($"發現插件:{p.MovementName} (ID:{p.MovementID})");
p.ConnectionString = ConfigurationManager.ConnectionStrings["MainDB"].ConnectionString;
p.Initialize(); // 觸發插件邏輯
}
FileLoadException。AppDomain 隔離(.NET Framework)或 AssemblyLoadContext(.NET Core 3.0+),各插件擁有獨立載入上下文。FileSystemWatcher 監控目錄變更,觸發重新載入。IAsyncDisposable 或 IDisposable)。public interface IPlugin : IDisposable
{
// ... 既有成員 ...
Task StartAsync(CancellationToken ct);
Task StopAsync(CancellationToken ct);
}
主程式負責協調 StartAsync/StopAsync,確保資源正確獲取與釋放。
MyApp/
├── MyApp.exe
├── PluginInterface.dll
├── Plugins/
│ ├── DataSyncPlugin.dll
│ ├── ReportGenerator.dll
│ └── NotificationSender.dll
└── Config/
└── plugins.json (可選:啟用/停用、參數配置)
基於介面的插件架構三要素:
1. 契約優先:共用介面 DLL 定義邊界。
2. 約定優於配置:固定目錄、掃描 *.dll、實作介面即可載入。
3. 隔離與治理:版本隔離、安全簽名、生命週期管理。
掌握此模式,即可打造高擴充性、低耦合的企業級應用系統。