运行时动态加载程序集:如何避免类定义重复的技术咨询
我构建了一套较为复杂的类库系统,希望其他开发者能够加载其中一个程序集,读取其属性、调用其方法并监听其事件。目前,终端开发者必须引用Library1,同时还要搭配Library1所依赖的其他所有类库。
为解决该问题,我计划制作一个安装程序,将所有类库复制到指定位置,终端开发者只需引用MainLibrary,由它动态加载其余类库并暴露所需使用的内容。
Assembly library1Assembly = Assembly.LoadFrom("/path/to/where/the/libraries/where/installed/library1.dll"); Type? library1Type = library1Assembly.GetType("Library1.MainClass"); dynamic library1Instance = Activator.CreateInstance(library1Type);
不过Library1包含一些希望开发者能够访问的公共类,例如User类。MainLibrary需要暴露Library1已有的List<User> Users和User SelectedUser这两个属性,因此我需要创建一个与Library1.User完全一致的MainLibrary.User类,其他多个类、自定义EventArgs等也需如此处理。
我并不希望这种重复定义的做法,请问是否有其他实现方式?
1. 提取共享契约程序集
把User、自定义EventArgs等需要跨库共享的类型,单独提取到一个独立的契约程序集(比如Library.Contracts.dll)中:
- 让
Library1和MainLibrary都引用这个契约程序集 - 终端开发者只需引用
MainLibrary和契约程序集,就能直接使用这些共享类型,无需重复定义 - 动态加载
Library1后,直接将实例转换为契约程序集中的接口或类型,暴露给开发者:
// 契约程序集中定义共享接口与类型 public interface IUser { string Name { get; set; } int Age { get; set; } } public interface IMainClass { List<IUser> Users { get; } IUser SelectedUser { get; set; } event EventHandler<CustomUserEventArgs> UserSelected; } public class CustomUserEventArgs : EventArgs { public IUser TargetUser { get; set; } } // MainLibrary中动态加载并转换 Assembly library1Assembly = Assembly.LoadFrom("path/to/library1.dll"); Type library1Type = library1Assembly.GetType("Library1.MainClass"); IMainClass instance = (IMainClass)Activator.CreateInstance(library1Type); // 将IMainClass实例暴露给开发者,直接使用契约类型即可
2. 使用动态类型配合扩展方法
如果不想额外增加契约程序集,可以用dynamic类型配合扩展方法,让开发者无需直接引用Library1就能操作共享类型:
// MainLibrary中提供扩展方法封装类型操作 public static class UserExtensions { public static string GetUserName(this dynamic user) { return user.Name; } public static void SetUserAge(this dynamic user, int age) { user.Age = age; } } // 开发者使用示例 dynamic selectedUser = mainLibraryInstance.SelectedUser; string userName = selectedUser.GetUserName(); selectedUser.SetUserAge(30);
这种方式无需重复定义类型,但缺点是缺少编译时类型检查,依赖运行时保证属性/方法存在。
3. 全封装反射访问逻辑
把对Library1中类型的所有访问逻辑封装在MainLibrary内部,开发者仅通过MainLibrary提供的方法、属性操作,完全不接触底层类型:
// MainLibrary中封装所有对外接口 public class MainLibraryWrapper { private dynamic _library1Instance; public MainLibraryWrapper() { // 动态加载Library1的逻辑 Assembly library1Assembly = Assembly.LoadFrom("path/to/library1.dll"); Type library1Type = library1Assembly.GetType("Library1.MainClass"); _library1Instance = Activator.CreateInstance(library1Type); } // 封装Users属性,返回只读枚举 public IEnumerable<object> Users => _library1Instance.Users; // 封装SelectedUser的属性访问 public string SelectedUserName => _library1Instance.SelectedUser.Name; public void SetSelectedUserAge(int age) => _library1Instance.SelectedUser.Age = age; // 封装事件监听 public event EventHandler<EventArgs> UserSelected { add => _library1Instance.UserSelected += value; remove => _library1Instance.UserSelected -= value; } }
这种方式完全隔离了底层类库,开发者无需知道Library1的存在,但封装工作量较大,适合需要严格控制访问权限的场景。
内容的提问来源于stack exchange,提问作者Claudio

