c# 如何实现插件化开发

来源:这里教程网 时间:2026-02-21 17:37:56 作者:

什么是 C# 插件化开发的核心机制

插件化开发在 C# 中本质是运行时动态加载程序集(

.dll
),并调用其中实现约定接口的类型。它不依赖编译期引用,而是靠反射 + 接口契约 +
AssemblyLoadContext
(.NET Core 3.0+)或
AppDomain
(.NET Framework)来隔离和管理生命周期。

关键判断:如果你只是想“换配置就换行为”,不用插件;但需要第三方独立发布功能模块、热更新业务逻辑、或隔离崩溃风险,那才真正需要插件化。

AssemblyLoadContext
实现可卸载插件(推荐 .NET 5+)

AppDomain
已在 .NET Core 中移除,
AssemblyLoadContext
是唯一支持卸载的机制。不使用它会导致插件 DLL 锁死、内存泄漏、类型冲突。

必须为每个插件创建独立的派生类(如
PluginLoadContext : AssemblyLoadContext
),重写
Load
方法以控制依赖解析
插件程序集及其所有依赖(非框架类库)必须显式加载进该上下文,否则会 fallback 到默认上下文,导致无法卸载 插件类型不能直接返回
string
List<t></t>
等托管对象给宿主——跨上下文传递需用接口(定义在共享程序集)、序列化或原始数据(
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<t></t>
—— 宿主和插件可能使用不同版本的
System.Runtime
,引发泛型类型不匹配
建议增加
Initialize(IConfiguration config)
方法,让插件自行读取配置,而不是由宿主传入
IConfiguration
(该类型跨上下文不可用)
插件实现类必须有无参构造函数,否则
CreateInstance
失败

常见失败场景和绕过方式

插件加载失败往往不是代码问题,而是环境或路径细节没对齐:

FileNotFoundException
:插件 DLL 缺少本地依赖(如
sqlite3.dll
libSkiaSharp.so
)。解决:把原生库复制到插件目录,并在
PluginLoadContext.Load
中监听
ResolvingUnmanagedDll
事件手动加载
InvalidCastException
:看似实现了接口,实则引用了不同版本的
PluginContract.dll
。解决:强制所有项目引用同一 NuGet 包或项目引用,检查输出目录是否混入旧版
PluginContract.dll
插件能加载但方法调用后整个进程卡死:插件内启动了同步阻塞 I/O(如
File.ReadAllText
在大文件上)或未处理的异常未被 catch。解决:宿主调用统一包装在
try/catch
+
CancellationToken
中,并限制执行超时
.NET Framework 下无法卸载:只能用
AppDomain.CreateDomain
隔离,但卸载后该域内所有线程必须退出,且静态字段不会重置。实际生产中已不推荐

最易被忽略的一点:插件程序集的

TargetFramework
必须与宿主完全一致(如都是
net6.0
),哪怕只差一个补丁号(
net6.0.1
vs
net6.0.2
)都可能导致
AssemblyLoadContext
拒绝加载。

相关推荐