MEF基础

article/2025/9/15 1:24:55

https://www.cnblogs.com/yk123/p/5350133.html

之前公司里用到了一个叫MEF的东西,说来惭愧一直只管写代码却不曾理解MEF框架为何物,今天就来学习一下,这是一篇迟到了不知多久的博客。

--------------------------------------------------------进入正题-------------------------------------------------------------------

1.MEF概念

MEF,全称Managed Extensibility Framework(托管可扩展框架)。单从名字我们不难发现:MEF是专门致力于解决扩展性问题的框架,MSDN中对MEF有这样一段说明:

  Managed Extensibility Framework 或 MEF 是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项。 通过 MEF,不仅可以在应用程序内重用扩展,还可以在应用程序之间重用扩展。

2.MEF使用

  MEF的使用范围广泛,在Winform、WPF、Win32、Silverlight中都可以使用,我们就从控制台说起,看看控制台下如何实现MEF,下面先新建一个win32控制台项目MEF_1的Demo,添加一个IBookService接口一个继承这个接口的类MusicBook ,如图:

首先需要手动添加对System.ComponentModel.Composition命名空间的引用,由于在控制台程序中没有引用这个DLL,所以要手动添加:

 

我们再回到program类中写如下代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

class Program

    {

        [Import]

        public IBookService Service { getset; }

 

        static void Main(string[] args)

        {

            Program pro = new Program();

            pro.Compose();

            if (pro.Service != null)

            {

                Console.WriteLine(pro.Service.GetBookName());

            }

            Console.Read();

        }

       //宿主MEF并组合部件

1

2

private void Compose() { <br>       //获取包含当前执行的代码的程序集 <br>             var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());<br>

   CompositionContainer container = new CompositionContainer(catalog);

      //将部件(part)和宿主程序添加到组合容器

1

container.ComposeParts(this); } <br>}

  这个方法表示添加当前Program这个类到组合容器,为什么要添加到组合容器?是因为只要添加到组合容器中之后,如果该类里面有Import,MEF才会自动去寻找对应的Export。这也就是为什么使用MEF前必须要组合部件的原因。

代码运行情况:

可以看到调用了MusicBook类的GetBookName方法,但是我们并没有实例化MusicBook类,是不是很神奇呢???

这一就是实现了主程序和类之间的解耦,大大提高了代码的扩展性和易维护性!

看到这里可能有人就会说这个Import是多此一举了,既然我们可以new,为什么非要用这种奇怪的语法呢,怪别扭的。其实如果我们站在架构的层面,它的好处就是可以减少dll之间的引用。

------------------------------------------------MEF中的Import(导入)和Export(导出)-------------------------------------

上面的测试小Demo我们对MEF有了一个初步的了解,下面我们来细看一下MEF中的Import(导入)和Export(导出)。

复制代码

 class Program{[Import("MusicBook")]public IBookService Service { get; set; }static void Main(string[] args){Program pro = new Program();pro.Compose();if (pro.Service != null){Console.WriteLine(pro.Service.GetBookName());}Console.Read();}//宿主MEF并组合部件private void Compose(){//获取包含当前执行的代码的程序集var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());CompositionContainer container = new CompositionContainer(catalog);//将部件(part)和宿主程序添加到组合容器container.ComposeParts(this);}}
}

复制代码

复制代码

 Export(typeof(IBookService)) 这句话将类声明导出为IBookService接口类型//[Export(typeof(IBookService))][Export("MusicBook",typeof(IBookService))]class MusicBook:IBookService{public string BookName { get; set; }public string GetBookName(){return "音乐书本";}}

复制代码

注意,标红的是改动过的地方,其他地方的代码没有变,上一次我们使用的是Export的方法是[Export(typeof(IBookService))],这次前面多了一个参数,没错,这个就是一个契约名,名字可以随便起,而且可以重复,但是如果名字乱起,和其他DLL中的重复,到时候会导致程序出现很多Bug,最好按照一定的规范去起名字。

这里有了契约名以后,导入(Import)时就要指定的契约名,否则将无法找到MusicBook,Export还有一个方法是[Export("Name")],这个方法只指定了契约名,没有指定导出类型,那么默认的导出类型是object类型,在导入时导出到的对象就要为object类型,否则将匹配不到那个组件。

  到现在,我们只写了一个接口和一个实现类,导出的也是一个类,下面我们多添加几个类来看看会怎么样,为了方便大家测试,我把实现接口的类写在一个文件里面,新加几个类后,的MusicBook类文件代码如下:

复制代码

[Export("MusicBook",typeof(IBookService))]public class MusicBook:IBookService{public string BookName { get; set; }public string GetBookName(){return "音乐书本";}}[Export("MusicBook", typeof(IBookService))]public class MathBook : IBookService{public string BookName { get; set; }public string GetBookName(){return "数学课本";}}[Export("MusicBook", typeof(IBookService))]public class HistoryBook:IBookService{public string BookName { get; set; }public string GetBookName(){return "历史课本";}}

复制代码

这里添加两个类,HistoryBook和MathBook,都继承自IBookService接口,注意他们的契约名都相同,都为MusicBook,后面再详细的说这个问题,修改后的program的代码如下:

复制代码

class Program{[ImportMany("MusicBook")]public IEnumerable<IBookService> Services { get; set; }static void Main(string[] args){Program pro = new Program();pro.Compose();if (pro.Services != null){foreach (var service in pro.Services){Console.WriteLine(service.GetBookName());}}Console.Read();}//宿主MEF并组合部件private void Compose(){//获取包含当前执行的代码的程序集var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());CompositionContainer container = new CompositionContainer(catalog);//将部件(part)和宿主程序添加到组合容器container.ComposeParts(this);}}

复制代码

这里需要注意的是标红的两行代码,[ImportMany("MusicBook")]还有下面的声明变成了IEnumerable<>,因为要导出多个实例,所以要用到集合,下面采用foreach遍历输出,运行的结果如下图:

现在三个类的契约名都不相同了,其他的代码不动,再次运行程序看看,是不是现在只输出MusicBook了,同理,修改[Import("Name")]中的契约名称,就会导入指定含有名称的类,契约名可以重复,这一以来,我们就可以用契约名给类进行分类,导入时可以根据契约名来导入。

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

例如:我们在类上面标注[Export("Book")],我们仅仅指定了契约名,而没有指定类型,那么默认为object,此时还用IEnumerable<IBookService>就匹配不到。

那么,这种情况就要在输出是进行强制类型转换,代码如下:

复制代码

 [Export("MusicBook")]public class MusicBook:IBookService{public string BookName { get; set; }public string GetBookName(){return "音乐书本";}}

复制代码

如果我们这样写了那样结果就只能匹配到后两个:

此时我们需要强制转换一下输出的类型:

复制代码

[ImportMany("MusicBook")]public IEnumerable<object> Services { get; set; }static void Main(string[] args){Program pro = new Program();pro.Compose();if (pro.Services != null){foreach (var service in pro.Services){var ss = (IBookService)service;Console.WriteLine(ss.GetBookName());}}Console.Read();}

复制代码

结果就匹配到了所有类的输出:

------------------------------------------------MEF中方法和属性能不能导出呢?---------------------------------------------

前面说完了导入和导出的几种方法,如果大家细心的话会注意到前面我们导出的都是类,那么方法和属性能不能导出呢???答案是肯定的,下面就来说下MEF是如何导出方法和属性的:

首先是导出属性:

复制代码

 [Export("MusicBook", typeof(IBookService))]public class MathBook : IBookService{//导出私有的属性[Export(typeof(string))]private string _privateBookName = "私有的属性BookName";//导出公用的属性[Export(typeof(string))]public string _publicBookName = "公有的属性BookName";public string BookName { get; set; }public string GetBookName(){return "数学课本";}}

复制代码

复制代码

 class Program{[ImportMany("MusicBook")]public IEnumerable<object> Services { get; set; }//导入属性(这里不区分public还是private)[ImportMany]public List<string> InputString { get; set; } static void Main(string[] args){Program pro = new Program();pro.Compose();if (pro.Services != null){foreach (var service in pro.Services){var ss = (IBookService)service;Console.WriteLine(ss.GetBookName());}foreach (var str in pro.InputString){Console.WriteLine(str);}}Console.Read();}//宿主MEF并组合部件private void Compose(){//获取包含当前执行的代码的程序集var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());CompositionContainer container = new CompositionContainer(catalog);//将部件(part)和宿主程序添加到组合容器container.ComposeParts(this);}}

复制代码

最后的输出结果为:

下面说导出方法吧,同理无论是公有方法还是私有方法都是可以导出的,MusicBook代码如下:

复制代码

        //导出公用方法(无参数)[Export(typeof (Func<string>))]public string PublicGetBookName(){return "PublicMathBook";}//导出私有方法(有参数)[Export(typeof (Func<int,string>))]public string PrivateGetBookName(int count){return string.Format("我看到一个好书,我买了{0}本", count);}

复制代码

复制代码

class Program{[ImportMany("MusicBook")]public IEnumerable<object> Services { get; set; }//导入属性(这里不区分public还是private)[ImportMany]public List<string> InputString { get; set; }//导入无参数方法[Import]public Func<string> methodWithoutPara { get; set; }//导入有参数的方法[Import]public Func<int, string> methodWithPara { get; set; }static void Main(string[] args){Program pro = new Program();pro.Compose();if (pro.Services != null){foreach (var service in pro.Services){var ss = (IBookService)service;Console.WriteLine(ss.GetBookName());}foreach (var str in pro.InputString){Console.WriteLine(str);}}//调用无参数的方法if (pro.methodWithPara != null){Console.WriteLine(pro.methodWithoutPara());}//调用有参数的方法if (pro.methodWithoutPara!=null){Console.WriteLine(pro.methodWithPara(5));}Console.Read();}//宿主MEF并组合部件private void Compose(){//获取包含当前执行的代码的程序集var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());CompositionContainer container = new CompositionContainer(catalog);//将部件(part)和宿主程序添加到组合容器container.ComposeParts(this);}}

复制代码

导入导出方法用到了Func<T>委托,当然没有返回值的话可以用Action<T>委托。

------------------------------------------------MEF在分层架构中的使用---------------------------------------------

前面讲了MEF的基础和基本到导入导出方法,下面就是见证MEF真正魅力所在的时刻。如果没有看过前面的文章,请到我的博客首页查看。

  前面我们都是在一个项目中写了一个类来测试的,但实际开发中,我们往往要采用分层架构,就拿最简单的三层架构来说吧,我们通常把业务逻辑写在DLL中,现在就来写一个例子,看看如何在不编译整个项目的情况下,轻松的实现扩展。先透露一下,我们只要添加一个DLL就可以了。

  这里就以银行为例子吧,首先新建一个控制台项目,叫MEF_2吧,然后建一个类库写接口,然后再建一个类库实现接口。项目结构如下:

其中MEF_2和BankOfChina都只引用接口项目,MEF_2不需要引用BankOfChina。

BankInterface的代码如下,做个简单实例,写几个方法测试一下:

复制代码

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

复制代码

这里添加一个中国银行卡,实现接口,引用命名空间什么的不再重复说了,不懂看前面的文章,代码如下:

复制代码

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

复制代码

主程序中的代码:

复制代码

namespace MEF_2
{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("MyCards");var container = new CompositionContainer(catalog);container.ComposeParts(this);}}
}

复制代码

现在,我们知道只有一种银行卡,及中国银行的,注意我标红的代码,这里是一个目录,及主程序所在目录的Cards(bin-debug-Cards)文件夹(我们需要提前建立好),我们把生成的BankOfChian.dll拷贝到这个文件夹下,然后运行才可以正确输出信息(毕竟我们没有引用那个项目),如图:

 

此时我们看到输出结果为"中国银行"。

到了这里相信大家已经明白了,如果现在需求改变了,需要支持建行、农行等银行卡,怎么办呢?通常我们要改项目,把整个项目都编译再重新发布。但是现在不需要这么做了,我们只需要添加一个类库项目,把生成的dll拷贝到Cards目录下即可。

我们在这个解决方案下继续添加一个类库项目,实现ICard接口,代码如下:

复制代码

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

复制代码

点击右键编译,把生成的dll拷贝到Cards目录下面,运行看到如下结果:

再看看Cards目录中,现在你添加几个dll,就会显示多少条信息了。

------------------------------------------------MEF的高级用法 MEF中如何访问某个具体的对象-----------------------------------

前面我们讲过在导出的时候,可以在[Export()]注解中加入名称标识,从而识别某个具体的对象,然而这种方法只是用于页面初始化的时候就行过滤,页面打开后没有导入的就再也导入不了了,就是说我们不能在导入的集合中分辨各自的不同,所有导入的类都是没有标识的。

待续...


http://chatgpt.dhexx.cn/article/rlVyWMZe.shtml

相关文章

C# 依赖注入 MEF

1、什么是MEF 先来看msdn上面的解释&#xff1a;MEF(Managed Extensibility Framework)是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展&#xff0c;而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码&#xff0c;避免生成脆弱…

MEF 基础简介 一

前言 小编菜鸟级别的程序员最近感慨颇多&#xff0c;经历了三五春秋深知程序路途遥远而我沧海一粟看不到的尽头到不了的终点何处是我停留的驿站。说了段废话下面进入正题吧&#xff01; 什么是MEF&#xff1f; MEF&#xff1a;全称Managed Extensibility Framework&#xff08;…

MEF插件式开发

一、MEF 简介 1、 Managed Extensibility Framework简称MEF&#xff0c;是WPF中的一种插件式开发的框架。其开发程序的主要优点如下&#xff1a; 易于将复杂程序进行拆分成不同的功能模块&#xff0c;然后进行多团队的协作。将各个功能编译成dll的形式提供给主程序&#xff0…

C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo

在文章开始之前&#xff0c;首先简单介绍一下什么是MEF&#xff0c;MEF,全称Managed Extensibility Framework&#xff08;托管可扩展框架&#xff09;。单从名字我们不难发现&#xff1a;MEF是专门致力于解决扩展性问题的框架&#xff0c;MSDN中对MEF有这样一段说明&#xff1…

【C#进阶系列】【MEF框架(一)】

C#进阶系列 第一章 【C#进阶系列】【MEF框架&#xff08;一&#xff09;】 文章目录 C#进阶系列前言一、MEF介绍二、为什么要用MEF三、MEF的概念四、使用示例五、MEF框架的好处六、源码链接总结 前言 这里对MEF作了基本的介绍&#xff0c;包括使用了一个特定场景&#xff08;…

【虚拟化生态平台】平台架构图思路和实现细节

设计思路 我需要一个内网生态&#xff0c;通过一个nginx访问到我所有的资源&#xff0c;内部实现细节我不关心&#xff0c;那么就需要一个nginx服务器来负载所有的服务服务器太多了&#xff0c;我不想一个个的记录ip去登录&#xff0c;那我就需要一个跳板机来记录我的服务器&a…

ARM平台的虚拟化介绍

本篇博文主要介绍虚拟化的基本思想以及在arm平台如何做虚拟化&#xff0c;arm提供的硬件feature等等。 虚拟化技术简介 虚拟化技术 虚拟化是一个概念&#xff0c;单从这个概念的角度来看&#xff0c;只要是用某一种物品去模拟另一种物品都可以称为虚拟化&#xff0c;甚至于有…

ARMv8虚拟化

1 概述 2 虚拟化简介 3 AArch64虚拟化 4 Stage-2地址转换 5 指令的陷入和模拟 6 虚拟化异常 7 虚拟化通用定时器 8 虚拟化主机扩展 9 嵌套虚拟化 10 安全空间的虚拟化 11 虚拟化的成本 12 小测验 13 其它参考文章 14 接下来的计划 1 概述 本文描述了ARMv8-64的虚…

虚拟化服务器环境搭建方案,虚拟化环境建设方案

虚拟化环境建设方案 市场背景: 随着IT技术的飞速发展,基础传统IT架构模式下的应用规模扩大,需要更多的服务器来支持业务的发展,同时出现了如下几个问题: 1.资源利用率低、闲置率高,运行效率低 2.应急处理响应时间慢、服务保障差 3.运行维护成本高 虚拟化的出现很好的解决…

搭建vmware虚拟化平台的基础配置,以及Hadoop平台的搭建

需要准备的东西&#xff1a; vmware workstations centos.iso hadoop3.3.0 mobaxterm/xshell/pietty/winscp jdk的tar包 第一步 &#xff1a; 安装centos操作系统 第二步 &#xff1a; 克隆虚拟机&#xff08;也可以在hadoop安装后再克隆&#xff09; 我们这里需要选择完整克隆…

WSL2 请启用虚拟机平台 Windows 功能并确保在 BIOS 中启用虚拟化

bcdedit /set hypervisorlaunchtype autoC:\WINDOWS\system32>bcdedit /set hypervisorlaunchtype auto 操作成功完成。

嵌入式虚拟化平台jailhouse踩坑笔记

目录 简介 相关论文 架构 jailhouse的应用示例 jailhouse 官方demo jailhouse的运行 jailhouse none-root cell中挂载nfs作为根目录启动linux 注意事项 jailhouse的配置 1.cell配置文件简介 2. cell文件的生成过程 2. .c配置文件内容解析 QEMU中实现jailhouse的…

搭建华为FusionCompute虚拟化平台

最小实验要求&#xff1a;两台服务器 1、通过BMC口安装CNA操作系统 远程光驱安装CNA&#xff0c;需要设置服务器从远程光驱启动&#xff0c;然后进入BIOS可以查看BMC控制台的用户名和密码&#xff0c;以及IP&#xff0c;一般服务器都默认有。然后登陆BMC的控制台&#xff0c;进…

服务器虚拟化 涉密,面向高安全服务器虚拟化领域应用研制的安全保密虚拟化平台...

中标麒麟服务器虚拟化系统是基于中标麒麟虚拟化平台软件V7&#xff0c;面向高安全服务器虚拟化领域应用研制的安全保密虚拟化平台&#xff1b;产品通过国家保密科技测评中心基于《涉密信息系统服务器虚拟化和桌面虚拟化产品安全保密技术要求》(BMB30-2017)的安全性检测。支持海…

VA虚拟平台十大亮点

VA虚拟平台十大亮点 一、自适应多种安装环境、并可以手动调整二、兼容互联网上主流动态域名自动连接&#xff0c;并有自主的动态域名解析。三、系统稳定性有效保障和看门狗机制四、高级参数最大限度保障程序兼容性五、智能打印-动态调整、实时预览、多页排版、自动打印六、向下…

Linux虚拟化平台检测

如果要找出 Linux 系统运行在虚拟化平台中还是硬件服务器上&#xff0c;有多种方式可供大家选择&#xff0c;这主要取决于你的 hypervisor 或 container 环境。不同的虚拟化或容器技术会在其实例中引入不同的识别指纹&#xff0c;如&#xff1a;处理器厂商、特殊的 /proc 文件或…

虚拟化平台PVE(ProxmoxVirtual Environment)安装部署

PVE是一个完整的企业虚拟化开源平台。通过内置的web界面&#xff0c;可以轻松地管理虚拟机和容器、软件定义的存储和网络、高可用性集群和多个开箱即用的工具。 官方&#xff1a;Proxmox - Powerful open-source server solutions 部署前准备 1.下载镜像&#xff0c;并刻录到U…

云平台管理与部署---虚拟化平台-----KVM

第一&#xff0c;搭建KVM服务器 1&#xff0c;虚拟化平台的安装 # yum -y install qemu-kvm libvirt-daemon libvirt-client libvirt-daemon-driver-qemu # systemctl start libvirtd # ls /etc/libvirt/qemu # ls /var/lib/libvirt/images/ 第二&#xff0c;管理KVM平台 1…

云计算-华为虚拟化平台FusionCompute

目录 FusionCompute产品介绍 FusionCompute虚拟化套件 FusionCompute产品定位 FusionCompute在虚拟化套件中位置 FusionCompute产品架构 FusionCompute模块功能模块 ​编辑FusionCompute产品功能 - 虚拟化计算 FusionCompute产品功能 - 虚拟化存储 FusionCompute产品功…

服务器虚拟化如何分配内存,VMware虚拟化平台内存如何管理?

当运行一个虚拟机时,vSphere的VMKernel为虚拟机生成一段可编址的连续内存,与普通操作系统提供给上层应用使用的内存具有相同的属性特征。引入内存虚拟化之后,同样的内存地址空间,允许VMkernel同时运行多个虚拟机并保证它们之间使用内存的独立性。 虚拟化平台三种内存模式 主…