博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# 依赖注入
阅读量:4495 次
发布时间:2019-06-08

本文共 10580 字,大约阅读时间需要 35 分钟。

依赖注入是一个过程,就是当一个类需要调用另一个类来完成某项任务的时候,在调用类里面不要去new被调用的类的对象,而是通过注入的方式来获取这样一个对象。具体的实现就是在调用类里面有一个被调用类的接口,然后通过调用接口的函数来完成任务。比如A调用B,而B实现了接口C,那么在A里面用C定义一个变量D,这个变量的实例不在A里面创建,而是通过A的上下文来获取。这样做的好处就是将类A和B分开了,他们之间靠接口C来联系,从而实现对接口编程。

 

依赖注入最常用的两种方式是setter注入和构造函数注入。

setter注入:

就是在类A里面定义一个C接口的属性D,在A的上下文通过B实例化一个对象,然后将这个对象赋值给属性D。主要就是set 与 get

构造函数注入:

就是在创建A的对象的时候,通过参数将B的对象传入到A中。

还有常用的注入方式就是工厂模式的应用了,这些都可以将B的实例化放到A外面,从而让A和B没有关系。还有一个接口注入,就是在客户类(A)的接口中有一个服务类(B)的属性。在实例化了这个接口的子类后,对这个属性赋值,这和setter注入一样。

 

 

MEF:

(一)、

下面重点介绍C#中实现依赖注入的一种组件MEF。先看一个简单的例子

创建一个控制台项目,添加一个接口IBookService:

然后创建一个类MusicBookService来实现这个接口。下面的这个Export的作用后面再说。

创建一个客户类,在客户类中要调用MusicBookService中的函数来完成任务

namespace DependencyInjection.MEF{    class MusicBookClient    {        [Import]        public IBookService Service { get; set; }        public static void Mef()        {            MusicBookClient pro = new MusicBookClient();            pro.Compose();            if (pro.Service != null)            {                Console.WriteLine(pro.Service.GetBookName());            }            Console.Read();        }                private void Compose()        {            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());//反射            CompositionContainer container = new CompositionContainer(catalog);            container.ComposeParts(this);        }    }}

然后在main函数中调用这个MusicBookClient.Mef();运行程序就会看到音乐书籍这几个字了。

在MusicBookClient中,按照以前的做法有3种:在Mef中实例化Service;定义一个参数为IBookService的构造函数,在创建MusicBookClient对象的时候将Service实例化;在main函数中实例化一个MusicBookService,然后赋值给MusicBookClient的service属性。

但是看上面的这几段代码,没有发现实例化MusicBookService的地方,但是确实在MusicBookClient中调用了MusicBookService的函数。这就是MEF组件来实现依赖注入的特殊之处。这个应该也是用的反射技术,但是通过MEF用起来要简单的多。

 

现在再来看看上面的[Export(typeof(IBookService))],这句的作用是将类MusicBookService按照类型IBookService导出,如果没有指定类型,那么将按照object导出。导出之后,看MusicBookClient类中,有个[Import],这句的作用是将刚刚导出的MusicBookService导入,下面的Compose方法,实例化CompositionContainer来实现组合。这整个过程都是MEF组件来完成,我们不用去关心它怎么做到的。但是有一点要注意,实现接口的类,必须有无参数的构造函数,否则会报错

通过上面的代码可以对MEF有个初步的认识。但是如果有多个类实现了IBookService,也和上面一样用[Export(typeof(IBookService))],那么再运行代码的时候就会报错,因为系统不知道你要导入的是哪个具体的类。下面就来介绍一下这种情况的处理。

 

(二)、

接口还是那个接口,不变,现在重新创建接口的实现类和客户类:

[Export("MathBookService", typeof(IBookService))]    class MathBookService : IBookService    {        public string GetBookName()        {            return "数学书籍";        }    }[Export("ChineseBookService", typeof(IBookService))]    class ChineseBookService : IBookService    {        public string GetBookName()        {            return "语文书籍";        }    }

现在创建了两个类来实现接口,但是在export属性的构造函数就必须要指定一个名称,这个名称可以随意指定,而且可以重复,但最好还是别乱起。

客户类BookClient1:这里可以看到,import也用了上面取的名字了,在main函数中调用Mef1,输出的是语文书籍。这里的Compose函数和上面的是一样的。

[Import("ChineseBookService")]        public IBookService Service { set; get; }          public static void Mef1()        {            BookClient1 pro = new BookClient1();            pro.Compose();            Console.WriteLine( pro.Service.GetBookName());            Console.Read();        }

刚才说了,export属性的构造函数里面取的名字可以重复,那么现在我们来看看这种情况,再创建一个类,实现接口IBookService:

看到这里的export的第一个参数和MathBookService类的一样,名字重复了。

[Export("MathBookService", typeof(IBookService))]    class MyMathBookService : IBookService    {        public string GetBookName()        {            return "数学书籍1";        }    }

在客户类BookClient1中添加如下代码:

[ImportMany("MathBookService")]        public IEnumerable
Services { get; set; } public static void Mef() { BookClient1 pro = new BookClient1(); pro.Compose(); if (pro.Services != null) { foreach (var s in pro.Services) { Console.WriteLine(s.GetBookName()); } } Console.Read(); }

注意,这里不是用 的import,而是ImportMany,并且service也不是原来的那样了,而是一个集合。这个机会包含了所有取名为MathBookService的类的对象。

在main函数中调用Mef函数,会输出两行文字。

注意:IEnumerable<T>中的类型必须和类的导出类型匹配,如类上面标注的是[Exprot(typeof(object))],那么就必须声明为IEnumerable<object>才能匹配到导出的类。如果不指定类型,默认是object

 

(三)、

前面导出的都是类,那么方法和属性能不能导出呢???答案是肯定的,下面就来说下MEF是如何导出方法和属性的。

接口还是不变,重新定义接口的实现类和客户类:

class HistoryBookService : IBookService    {        //导出私有属性        [Export(typeof(string))]        private string _privateBookName = "Private History BookName";        //导出公有属性        [Export(typeof(string))]        public string _publicBookName = "Public History BookName";        //导出公有方法        [Export(typeof(Func
))] public string GetBookName() { return "历史书籍"; } //导出私有方法 [Export(typeof(Func
))] private string GetBookPrice(int price) { return "$" + price; } }

客户类:

class BookClient2    {        //导入属性,这里不区分public还是private        [ImportMany]        public List
InputString { get; set; } //导入无参数方法 [Import] public Func
methodWithoutPara { get; set; } //导入有参数方法 [Import] public Func
methodWithPara { get; set; } public static void Mef() { BookClient2 c2 = new BookClient2(); c2.Compose(); foreach (var str in c2.InputString) { Console.WriteLine(str); } //调用无参数方法 if (c2.methodWithoutPara != null) { Console.WriteLine(c2.methodWithoutPara()); } //调用有参数方法 if (c2.methodWithPara != null) { Console.WriteLine(c2.methodWithPara(3000)); } Console.Read(); } private void Compose() { var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());//反射 CompositionContainer container = new CompositionContainer(catalog); container.ComposeParts(this); } }

在main函数中调用BookClient2.Mef();,运行后:

至此,MEF组件的用法基本介绍完了,下面看看MEF在项目中如何使用。

 

 

重新建一个控制台项目,项目结构如下:

BankInterface是接口项目,BankOfChina是一个类库项目,MEFDemo是主项目,后两者需要引用接口项目。

接口项目中定义一个接口:

public interface ICard    {        //账户金额        double Money { get; set; }        //获取账户信息        string GetCountInfo();        //存钱        void SaveMoney(double money);        //取钱        void CheckOutMoney(double money);    }

BankOfChina项目中定义一个类ZHCard,实现ICard接口:

namespace BankOfChina{    [Export(typeof(ICard))]    public class ZHCard : ICard    {        public string GetCountInfo()        {            return "中国银行";        }        public void SaveMoney(double money)        {            this.Money += money;        }        public void CheckOutMoney(double money)        {            this.Money -= money;        }        public double Money { get; set; }    }}

主项目:

class Program    {        [ImportMany(typeof(ICard))]        public IEnumerable
cards { get; set; } static void Main(string[] args) { Program pro = new Program(); pro.Compose(); foreach (var c in pro.cards) { Console.WriteLine(c.GetCountInfo()); } Console.Read(); } private void Compose() { var catalog = new DirectoryCatalog("Cards"); var container = new CompositionContainer(catalog); container.ComposeParts(this); } }

注意到Compose函数,这里的和上面的有点不一样,在上面的代码里面获取的是当前项目所在的程序集,而这里呢是获取指定目录中的所有dll文件,其目的都是为了用反射创建对象。

然后先编译一遍项目,在主项目的Debug文件夹下面创建一个cards文件夹,为什么是cards呢,因为代码里面指定的是这个名字。然后将BankOfChina项目编译的dll放到里面。然后运行才可以正确输出信息(毕竟我们没有引用那个项目)

运行后看到输出的内容是中国银行。

整个项目到此应该是完整了,现在的问题是,我们需要对项目进行扩展,需要添加一个工商银行。怎么扩展呢,如果不用MEF组件,按照原来的方式,肯定是要重新编译主项目的,因为要修改主项目嘛。但是现在用了MEF组件的依赖注入功能,就不用了。

新建一个项目BankOfICBC,这个项目和BankOfChina基本是一样的。

namespace BankOfICBC{    [Export(typeof(ICard))]    public class ICBCCard : ICard    {        public string GetCountInfo()        {            return "工商银行";        }        public void SaveMoney(double money)        {            this.Money += money;        }        public void CheckOutMoney(double money)        {            this.Money -= money;        }        public double Money { get; set; }    }}

项目写完之后,这里可以只编译这一个项目,然后将编译好的BankOfICBC.dll放到cards文件夹。然后运行程序,会输出:中国银行,工商银行。这两行文字。如果要扩展其他的银行的,都可以按照这样的方式。这就完美的实现了只扩展,不修改的原则。

 

 但是这里还有一个问题,就是在主项目MEFDemo的main函数中,我们无法知道pro.cards中的每个对象具体是哪个,也就无法分别作出处理。这就需要重新定义export特性了:

在接口项目中添加特性类ExportCardAttribute:  注意,这里的构造函数用的是无参的,然后调用了父类的构造函数,但是却传递了一个参数,这里写死了,本来打算写一个有参的构造函数,像注释的那样,但是好像不行。

namespace BankInterface{    ///     /// AllowMultiple = false,代表一个类不允许多次使用此属性    ///     [MetadataAttribute]    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]    public class ExportCardAttribute : ExportAttribute    {        public ExportCardAttribute() : base(typeof(ICard))        {        }        //public ExportCardAttribute(Type t) : base(t)        //{        //}        public string CardType { get; set; }    }}

在这个自定义的特性中,添加了一个属性CardType,用来当作一个区分标记。

添加一个接口:

public interface IMetaData    {        string CardType { get; }    }

 

然后修改BankOfChina项目:

namespace BankOfChina{    //[Export(typeof(ICard))]    [ExportCard( CardType = "BankOfChina")]    public class ZHCard : ICard    {        public string GetCountInfo()        {            return "中国银行";        }        public void SaveMoney(double money)        {            this.Money += money;        }        public void CheckOutMoney(double money)        {            this.Money -= money;        }        public double Money { get; set; }    }}

主项目:

class Program    {        //[ImportMany(typeof(ICard))]        //public IEnumerable
cards { get; set; } //其中AllowRecomposition=true参数就表示运行在有新的部件被装配成功后进行部件集的重组. [ImportMany(AllowRecomposition = true)] public IEnumerable
> cards { get; set; } static void Main(string[] args) { Program pro = new Program(); pro.Compose(); foreach (var c in pro.cards) { if (c.Metadata.CardType == "BankOfChina") { Console.WriteLine("这是中国银行卡"); Console.WriteLine(c.Value.GetCountInfo()); } else if (c.Metadata.CardType == "NongHang") { Console.WriteLine("这是农行卡"); Console.WriteLine(c.Value.GetCountInfo()); } } Console.Read(); } private void Compose() { var catalog = new DirectoryCatalog("Cards"); var container = new CompositionContainer(catalog); container.ComposeParts(this); } }

 

记得要重新编译BankOfChina项目,然后将dll放到cards文件夹。

 

转载于:https://www.cnblogs.com/jin-/p/9683694.html

你可能感兴趣的文章
如何开发JAVA的GUI程序
查看>>
使用EnityFramework时,如何将指定字符串用作将连接到数据库的名称或者连接字符串名称...
查看>>
DISPOSE_ON_CLOSE 和 EXIT_ON_CLOSE 的区别
查看>>
es6学习--promise对象
查看>>
hdu1501 动态规划
查看>>
关于Microsoft app下同义词的整理
查看>>
EclipseADT编写单元测试代码的步骤
查看>>
Dart集合
查看>>
POJ 1988 Cube Stacking(并查集+路径压缩)
查看>>
黑马程序猿——JAVA高新技术——反射
查看>>
window.location.hash在firefox下中文自动转码为UTF-8问题
查看>>
Raspberry Pi 上使用GPU的OpenMax视频编码
查看>>
[LeetCode] Combinations 组合项
查看>>
95. Unique Binary Search Trees II
查看>>
js input框输入1位数字后自动跳到下一个input框聚焦
查看>>
我才知道爬虫也可以酱紫--火车采集器
查看>>
未来的物联网结点:可穿戴设备
查看>>
h5播放音乐
查看>>
Python 的內建模块
查看>>
每天一个linux命令(54):ping命令
查看>>