共计 1985 个字符,预计需要花费 5 分钟才能阅读完成。
1. 动态加载的实际业务场景
在开发中,我们经常会遇到需要动态加载外部库的场景。比如:

- 插件系统:主程序不需要重新编译就能加载新功能
- 热更新:修复 bug 或添加功能时无需重启应用
- 模块化设计:按需加载功能模块减少内存占用
- 多版本兼容:同时支持不同版本的库文件
这些场景都要求我们能动态加载外部程序集并调用其中的方法,这正是反射的强大之处。
2. 三种加载方式的区别与选择
C# 提供了三种主要的程序集加载方式:
Assembly.Load:- 通过程序集的强名称加载
- 会检查 GAC 和应用程序基目录
-
适合加载已知的、已部署的程序集
-
Assembly.LoadFrom: - 通过完整路径加载程序集
- 会加载该程序集的所有依赖项
-
适合加载特定位置的插件
-
Assembly.LoadFile: - 仅加载指定文件,不解析依赖
- 不会影响默认加载上下文
- 适合临时加载独立程序集
3. 完整代码示例
try
{
// 加载程序集
var assembly = Assembly.LoadFrom(@"C:\Plugins\MyPlugin.dll");
// 获取类型
Type pluginType = assembly.GetType("MyPlugin.PluginClass");
// 创建实例
object pluginInstance = Activator.CreateInstance(pluginType);
// 获取方法信息
MethodInfo method = pluginType.GetMethod("Execute",
BindingFlags.Public | BindingFlags.Instance);
// 调用方法
object result = method.Invoke(pluginInstance, new object[] {"参数 1"});
Console.WriteLine("调用结果:" + result);
}
catch (FileNotFoundException ex)
{Console.WriteLine("找不到文件:" + ex.Message);
}
catch (TypeLoadException ex)
{Console.WriteLine("类型加载失败:" + ex.Message);
}
catch (TargetInvocationException ex)
{Console.WriteLine("方法调用异常:" + ex.InnerException?.Message);
}
finally
{
// 释放资源
// 注意:反射加载的程序集无法卸载,除非卸载整个 AppDomain
}
4. 性能优化建议
反射调用比直接调用慢约 100-200 倍,可以通过以下方式优化:
- 缓存 MethodInfo 对象,避免重复获取
- 使用 Delegate.CreateDelegate 创建委托并缓存
- 对高频调用的方法,考虑使用 dynamic 或表达式树
- 批量处理反射操作,减少单个调用次数
示例(委托缓存优化):
// 第一次调用时创建并缓存委托
private static Func<object, string, object> _cachedDelegate;
public static object FastInvoke(object instance, MethodInfo method, string arg)
{if (_cachedDelegate == null)
{_cachedDelegate = (Func<object, string, object>)
Delegate.CreateDelegate(typeof(Func<object, string, object>),
null, method);
}
return _cachedDelegate(instance, arg);
}
5. 生产环境避坑指南
- 依赖项问题 :
- 确保所有依赖的 DLL 都在可访问路径
-
使用 AppDomain.CurrentDomain.AssemblyResolve 事件处理缺失依赖
-
版本冲突 :
- 不同插件使用相同依赖的不同版本时容易冲突
-
考虑将插件加载到独立的 AppDomain 中
-
权限问题 :
- 网络共享路径的程序集可能需要完全信任
-
检查 CAS 策略或使用
-
内存泄漏 :
- 反射加载的程序集无法单独卸载
-
长期运行的插件系统应使用单独的 AppDomain
-
异常处理 :
- 反射调用会包装所有异常为 TargetInvocationException
- 记得检查 InnerException 获取真实异常
6. 思考题
- 如何实现一个安全的插件系统,防止恶意插件破坏主程序?
- 在跨平台的.NET Core/.NET 5+ 环境中,反射加载有什么不同?
- 如何设计一个可热插拔的模块系统,支持运行时添加和移除功能模块?
总结
反射是 C# 中强大的动态编程工具,但也需要谨慎使用。本文介绍了从基础加载到生产优化的完整流程。记住:反射虽好,但不要滥用。在实际项目中,平衡动态性与类型安全、性能是需要持续思考的问题。希望这篇指南能帮助你安全高效地使用反射技术。
正文完
