共计 2332 个字符,预计需要花费 6 分钟才能阅读完成。
背景与痛点
在开发插件系统或模块化应用时,我们经常需要动态加载外部库(DLL)。传统的静态引用方式虽然简单,但缺乏灵活性,无法实现运行时按需加载。手动管理依赖又容易引发版本冲突或路径错误,这时候反射机制就派上用场了。

动态加载的主要痛点包括:
- 程序集加载失败时的异常处理
- 跨平台路径兼容性问题
- 反射调用的性能损耗
- 类型安全检查缺失风险
技术选型:Assembly 加载方式对比
C# 提供了多种加载程序集的方法,最常用的是以下两种:
Assembly.Load:从程序集名称加载(需在运行时搜索路径或 GAC 中)
// 适用于已存在于应用程序域的依赖项
var assembly = Assembly.Load("MyLibrary");
Assembly.LoadFrom:通过完整文件路径加载
// 适合加载任意位置的 DLL
var assembly = Assembly.LoadFrom(@"C:\plugins\MyLibrary.dll");
关键区别:
Load会检查程序集身份(名称、版本等),避免重复加载LoadFrom会创建新的加载上下文,可能导致同一程序集被多次加载
核心实现:四步完成动态调用
1. 加载程序集
// 建议使用绝对路径并处理异常
try
{string dllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MyLibrary.dll");
Assembly assembly = Assembly.LoadFrom(dllPath);
}
catch (FileNotFoundException ex)
{Console.WriteLine($"DLL 未找到: {ex.Message}");
}
2. 获取目标类型
// 获取公开类型(忽略大小写)Type targetType = assembly.GetType("MyLibrary.Calculator", throwOnError: false);
if (targetType == null)
{throw new TypeLoadException("未找到指定类型");
}
3. 创建类型实例
// 调用无参构造函数
object instance = Activator.CreateInstance(targetType);
// 或者调用带参构造
object paramInstance = Activator.CreateInstance(targetType, new object[] {"config"});
4. 反射调用方法
// 获取方法信息(注意参数类型匹配)MethodInfo method = targetType.GetMethod("Add",
new Type[] { typeof(int), typeof(int) });
// 调用实例方法
int result = (int)method.Invoke(instance, new object[] {1, 2});
Console.WriteLine($"调用结果: {result}");
性能与安全优化
性能优化技巧
- 缓存反射对象:将 MethodInfo/Type 等存储为静态变量
private static MethodInfo _cachedMethod;
if (_cachedMethod == null)
{_cachedMethod = targetType.GetMethod(...);
}
- 使用 Delegate.CreateDelegate:将方法转为强类型委托
var func = (Func<int, int, int>)Delegate.CreateDelegate(typeof(Func<int, int, int>), instance, method);
int result = func(1, 2); // 直接调用,无反射开销
安全防护措施
- 验证程序集签名:
var pubKey = assembly.GetName().GetPublicKey();
if (pubKey.Length == 0)
{throw new SecurityException("未签名的程序集");
}
- 沙箱环境加载:
var domain = AppDomain.CreateDomain("PluginDomain");
try
{var loader = domain.CreateInstanceAndUnwrap(...);
}
finally
{AppDomain.Unload(domain);
}
避坑指南
常见问题及解决
- 路径问题:
- 使用
Path.Combine代替字符串拼接 -
检查
Environment.CurrentDirectory与预期是否一致 -
版本冲突:
- 通过
AppDomain.AssemblyResolve事件处理缺失依赖
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{string path = Path.Combine(pluginsDir, $"{args.Name.Split(',')[0]}.dll");
return File.Exists(path) ? Assembly.LoadFrom(path) : null;
};
- 类型转换异常:
- 使用
is或as运算符进行安全转换 - 通过
Type.IsAssignableFrom检查类型兼容性
总结与思考
动态加载技术为系统扩展提供了极大灵活性,但需要权衡以下方面:
- 架构设计:是否真的需要动态加载?静态依赖注入能否满足需求?
- 维护成本:版本管理、异常处理等额外工作
- 性能影响:高频调用场景建议转为强类型调用
推荐进一步学习:
在实际项目中,可以先从简单的插件系统开始实践,逐步掌握类型发现、依赖管理等高级特性。
正文完
