C# 依赖注入 & MEF
之前面试有问道依赖注入,因为一直是做客户端的发开发,没有接触这个,后边工作接触到了mef,顺便熟悉一下依赖注入
详细的概念解释就不讲了,网上一大把,个人觉着依赖注入本质是为了解耦,方便扩展
依赖注入的方式:属性注入和构造函数注入,还有接口注入的,看了下跟属性注入差不多·就不展示了
上代码:
public interface icalc { double calc(double a, double b); } public class addcalc:icalc { public double calc(double a, double b) { return a + b; } } public class subtractcalc:icalc { public double calc(double a, double b) { return a - b; } } public class myclac { icalc _calc; //属性注入 public icalc calc { get { return _calc; } set { _calc = value; } } //构造函数注入 public myclac(icalc calc) { _calc = calc; } public double calculate(double a, double b) { return _calc.calc(a, b); } }
(di )依赖注入是实现(ioc)控制反转的一种方式,但是使用的时候,比如再扩展的时候还是需要修改调用代码,所以就有了ioc 容器来方便这个调用
.net 下边 mef框架就是干这个的, 本质是通过特性和反射在运行的时候程序集动态加载。
//接口声明 //最终调用过程接口 public interface icalculator { string calculate(string input); } //过程中操作接口 [inheritedexport]//这里特性标识子类会被导出,后边子类可以不用表示export导出特性 public interface ioperation { int operate(int left, int right); } //这里定义导出操作名称,可以用来在导出的操作中进行筛选识别,这个接口不用实现 public interface ioperationdata { string symbol { get; } }
上边是接口声明,下边实现这些接口
[export(typeof(ioperation))] [exportmetadata("symbol", '+')] public class add : ioperation { public int operate(int left, int right) { return left + right; } } [export(typeof(ioperation))] [exportmetadata("symbol", '-')] public class subtract : ioperation { public int operate(int left, int right) { return left - right; } } [export(typeof(ioperation))] [exportmetadata("symbol",'/')] public class except : ioperation { public int operate(int left, int right) { return left / right; } } [export(typeof(icalculator))] class mycalculator : icalculator { [importmany(allowrecomposition = true)] ienumerable<lazy<ioperation, ioperationdata>> operations; public string calculate(string input) { int left; int right; char operation; int fn = findfirstnondigit(input); //finds the operator if (fn < 0) return "could not parse command."; try { //separate out the operands left = int.parse(input.substring(0, fn)); right = int.parse(input.substring(fn + 1)); } catch { return "could not parse command."; } operation = input[fn]; foreach (lazy<ioperation, ioperationdata> i in operations) { if (i.metadata.symbol.equals( operation)) return i.value.operate(left, right).tostring(); } return "operation not found!"; } private int findfirstnondigit(string s) { for (int i = 0; i < s.length; i++) { if (!(char.isdigit(s[i]))) return i; } return -1; } }
这里因为加了exportmetadata特性,所以继承类要加上export特性,不然mef 好像不识别,如果没有exportmetadata,只需要在接口上边加上inheritedexport特性就可以了· mef会自动导入导出的
这里是导出,下边看怎么导入使用
private compositioncontainer _container; //这个是容器 [import(typeof(icalculator))] public icalculator calculator; //这个导入的类 private program() { var catalog = new aggregatecatalog(); catalog.catalogs.add(new assemblycatalog(typeof(program).assembly));//这里直接导入本程序集内的类 catalog.catalogs.add(new directorycatalog("extensions", "mef_ex.dll"));//这里导入指定目录下的dll,可以设置筛选项或者不设置,把目录下所有的dll全部导入 _container = new compositioncontainer(catalog); try { this._container.composeparts(this); } catch (compositionexception ex) { console.writeline(ex.tostring()); } }
这里mef_ex.dll是另外一个项目,生成的程序集,放到主程序目录下extensions目录下即可
实现了一个类:
[export(typeof(ioperation))] [exportmetadata("symbol", '%')] public class mod : mef_interface.ioperation { public int operate(int left, int right) { return left % right; } }
在main函数中直接new program即可调用calc的方法
program pro = new program(); console.writeline(pro.calculator.calculate("1-2"));
还可以单独导出类的方法和属性,以及通过metadata筛选导入的类
完整代码如下:
[inheritedexport] interface ibookservice { string bookname { get; set; } string getbookname(); } // [export("musicbook",typeof(ibookservice))] class musicbook : ibookservice { public string bookname { get; set; } [export(typeof(string))] public string _publicbookname = "publicbookname"; [export(typeof(string))] private string _privatebookname = "privatebookname"; public string getbookname() { return "musicbook"; } } // [export("musicbook", typeof(ibookservice))] class mathbook : ibookservice { public string bookname { get; set; } [export(typeof(func<string>))] public string getbookname() { return "mathbook"; } [export(typeof(func<int,string>))] private string privategetname(int count) { return $"get {count} mathbook"; } } // [export("musicbook", typeof(ibookservice))] class historybook : ibookservice { public string bookname { get; set; } public string getbookname() { return "historybook"; } } [inheritedexport] public interface iplugin { string caption { get; } void do(); } public interface ipluginmark { string mark { get; } } [export(typeof(iplugin))] [exportmetadata("mark", "plugin1")] public class plugin1 : iplugin { public string caption { get { return "plugin1"; } } public void do() { console.writeline("plugin1 do"); } } [export(typeof(iplugin))] [exportmetadata("mark", "plugin2")] public class plugin2 : iplugin { public string caption { get { return "plugin2"; } } public void do() { console.writeline("plugin2 do"); } } [export(typeof(iplugin))] [exportmetadata("mark", "plugin2")] public class plugin3 : iplugin { public string caption { get { return "plugin3"; } } public void do() { console.writeline("plugin3 do"); } } #endregion class program { #region [importmany] public ienumerable<ibookservice> services { get; set; }//导入类 [importmany] public list<string> inputstring { get; set; }//导入属性 [import] public func<string> methodwithoutpara { get; set; }//导入方法 [import] public func<int, string> methodwithpara { get; set; }//导入方法 [importmany] public ienumerable< lazy<iplugin, ipluginmark>> plugins { get; set; } #endregion private compositioncontainer _container; [import(typeof(icalculator))] public icalculator calculator; private program() { var catalog = new aggregatecatalog(); catalog.catalogs.add(new assemblycatalog(typeof(program).assembly));//导出本程序集 catalog.catalogs.add(new directorycatalog("extensions", "mef_ex.dll"));//通过文件导入 _container = new compositioncontainer(catalog); try { this._container.composeparts(this); } catch (compositionexception ex) { console.writeline(ex.tostring()); } } static void main(string[] args) { program pro = new program(); console.writeline(pro.calculator.calculate("1-2")); var plugins = pro.plugins;//.where(v => v.metadata.mark == "plugin2").tolist();//这里可以做筛选 foreach (var p in plugins) { p.value.do(); } if (pro.services != null) { foreach (var service in pro.services) { console.writeline(service.getbookname()); } foreach (var str in pro.inputstring) { console.writeline(str); } //调用无参数的方法 if (pro.methodwithoutpara != null) { console.writeline(pro.methodwithoutpara()); } //调用有参数的方法 if (pro.methodwithpara != null) { console.writeline(pro.methodwithpara(5)); } } console.readline(); } }
总结:
1 mef会自动导入对应的类实现,然后自动初始化,但是具体什么时候初始化以及导入,这里要注意类的初始化方法 以及是不是有可能多线程的问题以及有依赖
2 导入程序集的方式可以直接导入程序集或者通过文件,看了反编译的代码以及.netcore的源码,底层是使用load 以及loadfrom的方法来时间加载程序集的,所以这玩意理论上应该实现不了热插拔把·
3 关于.net实现热插拔,网上有很多玩法,之前有看过通过appdomain 来实现,也就是应用程序域,实现略复杂这里没研究,也可以通过load的方式重新加载程序集·但是这些理论上应该做不到所谓的热插拔吧,起码程序要重启把···
4 之前有面试问mef 怎么实现热插拔,直接懵逼了,我是搞清楚。后来想了下,可以换一个方式实现,在mef基础上实现aop,通过aop实现权限控制,拦截某些操作,或者mef 加载的时候过滤加载项,这些算热插拔么···