欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# 依赖注入 & MEF

程序员文章站 2022-04-28 12:19:39
之前面试有问道依赖注入,因为一直是做客户端的发开发,没有接触这个,后边工作接触到了MEF,顺便熟悉一下依赖注入 详细的概念解释就不讲了,网上一大把,个人觉着依赖注入本质是为了解耦,方便扩展 依赖注入的方式:属性注入和构造函数注入,还有接口注入的,看了下跟属性注入差不多·就不展示了 上代码: (DI ......

之前面试有问道依赖注入,因为一直是做客户端的发开发,没有接触这个,后边工作接触到了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 加载的时候过滤加载项,这些算热插拔么···