在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
依赖注入是一个过程,就是当一个类需要调用另一个类来完成某项任务的时候,在调用类里面不要去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<IBookService> 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<string>))] public string GetBookName() { return "历史书籍"; } //导出私有方法 [Export(typeof(Func<int, string>))] private string GetBookPrice(int price) { return "$" + price; } } 客户类: class BookClient2 { //导入属性,这里不区分public还是private [ImportMany] public List<string> InputString { get; set; } //导入无参数方法 [Import] public Func<string> methodWithoutPara { get; set; } //导入有参数方法 [Import] public Func<int, string> 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<ICard> 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 { /// <summary> /// AllowMultiple = false,代表一个类不允许多次使用此属性 /// </summary> [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<ICard> cards { get; set; } //其中AllowRecomposition=true参数就表示运行在有新的部件被装配成功后进行部件集的重组. [ImportMany(AllowRecomposition = true)] public IEnumerable<Lazy<ICard, IMetaData>> 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文件夹。
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论