动态加载与插件系统的初步实现(一):反射与MEF解决方案
涉及内容:
- 反射与MEF解决方案
- AppDomain卸载与代理
- WinForm、WcfRestService示
PRRT1: 反射实现
插件系统的基本目的是实现宿主与组件的隔离,核心是作为接驳约定的接口,宿主使用类型发现及挂载插件,以下是反射实现。
创建类库项目Plugin,添加接口IPlugin:
public interface IPlugin { String DoStuff(); }
创建控制台程序HostApp,添加对Plugin项目的引用,Main方法代码:
class Program { static void Main(string[] args) { IEnumerable<Type> pluginTypes = GetPluginTypes(); foreach (Type pluginType in pluginTypes) { IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType); Console.WriteLine(plugin.DoStuff()); } } private static IEnumerable<Type> GetPluginTypes() { String root = AppDomain.CurrentDomain.BaseDirectory; String[] files = Directory.GetFiles(root, "*.dll", SearchOption.TopDirectoryOnly); foreach (String file in files) { Type[] types = Assembly.LoadFrom(file).GetTypes(); foreach (Type type in types) { if (type.IsClass && typeof(IPlugin).IsAssignableFrom(type)) { yield return type; } } } } }
创建类库项目MyPlugin1,添加对Plugin项目的引用,添加Plugin1类并实现IPlugin:
public class Plugin1: IPlugin { public String DoStuff() { return "MyPlugin1 Plugin1.DoStuff"; } }
修改该项目的属性,在“生成”选项卡中找到输出,将“输出路径”指向HostApp下的bin\Debug文件夹,运行。
宿主使用无参的IPlugin子类完成组件调用。代码逻辑并不复杂但我们还有更优雅的解决方式即MEF框架,这里拿MEF的完成所需功能,组件生命周期等内容并不深入讨论,如有需求请自行MSDN。
PRRT2: MEF实现
MEF框架以Import、Export特性为功能入口,修改MyPlugin项目,引用System.ComponentModel.Composition,为MyPlugin添加Export特性:
[Export(typeof(IPlugin))] public class Plugin1: IPlugin { public String DoStuff() { return "MyPlugin1 Plugin1.DoStuff"; } }
注意Export明确指定导出类型为IPlugin,在Plugin项目中添加类PluginProvider,引用System.ComponentModel.Composition和System.ComponentModel.Composition.Hosting,添加IEnumerable<Lazy<IPlugin>>类型只读属性并标注ImportMany特性:
public class PluginProvider { [ImportMany] public IEnumerable<Lazy<IPlugin>> Plugins { get; private set; } public PluginProvider() { AggregateCatalog catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(".")); CompositionContainer container = new CompositionContainer(catalog); container.ComposeParts(this); } }
抽象基类ComposablePartCatalog表示组件目录,子类DirectoryCatalog使用指定目录进行搜索。PluginProvider使用了当前程序运行目录作为dll路径。同时导入点所在字段或属性可以是IEnumerable<T>、IEnumerable<Lazy<T>>、IEnumerable<Lazy<T, TMetadata>>等,延迟绑定能相对降低内存开销,这里使用了第2种,接着修改Main方法:
class Program { static void Main(string[] args) { PluginProvider pluginProvider = new PluginProvider(); foreach (Lazy<IPlugin> plugin in pluginProvider.Plugins) { Console.WriteLine(plugin.Value.DoStuff()); } } }
运行得到同样的结果,代码更加优雅;根据需求,修改PluginProvider的导入逻辑及使用泛型版本,将得到更多的灵活性。代码文件
附求职信息:目前在北京,寻求.Net相关职位,偏向后端,请邮件jusfr.v#gmail.com,替换#为@,沟通后奉上简历。