

新闻资讯
技术教程C#插件化开发核心是运行时动态加载程序集并调用约定接口类型,依赖AssemblyLoadContext(.NET Core 3.0+)实现隔离与卸载,需共享接口契约、独立加载上下文及严格版本匹配。
插件化开发在 C# 中本质是运行时动态加载程序集(.dll),并调用其中实现约定接口的类型。它不依赖编译期引用,而是靠反射 + 接口契约 + AssemblyLoadContext(.NET Core 3.0+)或 AppDomain(.NET Framework)来隔离和管理生命周期。
关键判断:如果你只是想“换配置就换行为”,不用插件;但需要第三方独立发布功能模块、热更新业务逻辑、或隔离崩溃风险,那才真正需要插件化。
AssemblyLoadContext 实现可卸载插件(推荐 .NET 5+)AppDomain 已在 .NET Core 中移除,AssemblyLoadContext 是唯一支持卸载的机制。不使用它会导致插件 DLL 锁死、内存泄漏、类型冲突。
PluginLoadContext : AssemblyLoadContext),重写 Load 方法以控制依赖解析string、List 等托管对象给宿主——跨上下文传递需用接口(定义在共享程序集)、序列化或原始数据(byte[])context.LoadFromAssemblyPath(path) 加载,再通过 assembly.CreateInstance(typeName) 实例化public class PluginLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath) : base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
}
}接口必须定义在**宿主与插件共同引用的独立程序集**(如 PluginContract.dll)中,且该程序集不能包含任何插件私有依赖。否则会出现 System.TypeLoadException: Could not load type ... from assembly。
int、string、DateTime)、共享接口、或 Stream/MemoryStream
Task 或 IEnumerable —— 宿主和插件可能使用不同版本的 System.Runtime,引发泛型类型不匹配Initialize(IConfiguration config) 方法,让插件自行读取配置,而不是由宿主传入 IConfiguration(该类型跨上下文不可用)CreateInstance 失败插件加载失败往往不是代码问题,而是环境或路径细节没对齐:
FileNotFoundException:插件 DLL 缺少本地依赖(如 sqlite3.dll、libSkiaSharp.so)。解决:把原生库复制到插件目录,并在 PluginLoadContext.Load 中监听 ResolvingUnmanagedDll 事
件手动加载InvalidCastException:看似实现了接口,实则引用了不同版本的 PluginContract.dll。解决:强制所有项目引用同一 NuGet 包或项目引用,检查输出目录是否混入旧版 PluginContract.dll
File.ReadAllText 在大文件上)或未处理的异常未被 catch。解决:宿主调用统一包装在 try/catch + CancellationToken 中,并限制执行超时AppDomain.CreateDomain 隔离,但卸载后该域内所有线程必须退出,且静态字段不会重置。实际生产中已不推荐最易被忽略的一点:插件程序集的 TargetFramework 必须与宿主完全一致(如都是 net6.0),哪怕只差一个补丁号(net6.0.1 vs net6.0.2)都可能导致 AssemblyLoadContext 拒绝加载。