C# 通过反射动态加载库及函数调用的实战指南

1次阅读
没有评论

共计 2332 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

背景与痛点

在开发插件系统或模块化应用时,我们经常需要动态加载外部库(DLL)。传统的静态引用方式虽然简单,但缺乏灵活性,无法实现运行时按需加载。手动管理依赖又容易引发版本冲突或路径错误,这时候反射机制就派上用场了。

C# 通过反射动态加载库及函数调用的实战指南

动态加载的主要痛点包括:

  • 程序集加载失败时的异常处理
  • 跨平台路径兼容性问题
  • 反射调用的性能损耗
  • 类型安全检查缺失风险

技术选型:Assembly 加载方式对比

C# 提供了多种加载程序集的方法,最常用的是以下两种:

  1. Assembly.Load:从程序集名称加载(需在运行时搜索路径或 GAC 中)
// 适用于已存在于应用程序域的依赖项
var assembly = Assembly.Load("MyLibrary");
  1. 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); // 直接调用,无反射开销

安全防护措施

  1. 验证程序集签名:
var pubKey = assembly.GetName().GetPublicKey();
if (pubKey.Length == 0) 
{throw new SecurityException("未签名的程序集");
}
  1. 沙箱环境加载:
var domain = AppDomain.CreateDomain("PluginDomain");
try 
{var loader = domain.CreateInstanceAndUnwrap(...);
}
finally 
{AppDomain.Unload(domain);
}

避坑指南

常见问题及解决

  1. 路径问题:
  2. 使用 Path.Combine 代替字符串拼接
  3. 检查 Environment.CurrentDirectory 与预期是否一致

  4. 版本冲突:

  5. 通过 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;
};
  1. 类型转换异常:
  2. 使用 is 或as运算符进行安全转换
  3. 通过 Type.IsAssignableFrom 检查类型兼容性

总结与思考

动态加载技术为系统扩展提供了极大灵活性,但需要权衡以下方面:

  • 架构设计:是否真的需要动态加载?静态依赖注入能否满足需求?
  • 维护成本:版本管理、异常处理等额外工作
  • 性能影响:高频调用场景建议转为强类型调用

推荐进一步学习:

在实际项目中,可以先从简单的插件系统开始实践,逐步掌握类型发现、依赖管理等高级特性。

正文完
 0
评论(没有评论)