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

C#进阶系列

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



前言

这里对MEF作了基本的介绍,包括使用了一个特定场景(搞自动化运控上位机开发的应该更容易代入场景了),一步一步地介绍了如果从常用的编程过渡到框架性的编程开发。


一、MEF介绍

  • MEF,全称Managed Extensibility Framework(托管可扩展框架)。MEF是专门致力于解决扩展性问题的框架。
  • MSDN:Managed Extensibility Framework 或 MEF 是一个用于创建可扩展的轻型应用程序的库。 应用程序开发人员可利用该库发现并使用扩展,而无需进行配置。 扩展开发人员还可以利用该库轻松地封装代码,避免生成脆弱的硬依赖项。 通过 MEF,不仅可以在应用程序内重用扩展,还可以在应用程序之间重用扩展。
  • MEF(Managed Extensibility Framework),是轻量级的插件框架。使用简单,功能强大。
  • MEF 位于 ComponentModel.Composition 程序集中,添加 System.ComponentModel.Composition 和 System.ComponentModel.Composition.Hosting 的项目引用

二、为什么要用MEF

我们先假设一个场景:在运动控制上位机编程开发中,有固高、雷赛两种运动控制卡,现在我们需要根据实际设备使用哪种卡来实现不同的编程。
常规的实现:

class Program
{
    
    
    static void Main(string[] args)
    {
    
    
        string ret = "";
        string cardName = "实际设备使用哪种运动控制卡";
        if (cardName == "固高板卡")
        {
    
    
            CLeadshine cLeadshine = new CLeadshine();
            ret = cLeadshine.InitMotionCard();
            ret = cLeadshine.ExitMotionCard();
        }
        else if (cardName == "雷赛板卡")
        {
    
    
            CGoogol cGoogol = new CGoogol();
            ret = cGoogol.InitMotionCard();
            ret = cGoogol.ExitMotionCard();
        }
    }
}

public class CLeadshine
{
    
    
    public string InitMotionCard()
    {
    
    
        return "初始化固高板卡成功";
    }
    public string ExitMotionCard()
    {
    
    
        return "退出固高板卡成功";
    }
}

public class CGoogol
{
    
    
    public string InitMotionCard()
    {
    
    
        return "初始化雷赛板卡成功";
    }
    public string ExitMotionCard()
    {
    
    
        return "退出雷赛板卡成功";
    }
}

看了上述代码,可以想象一下,里面存在一个主运行程序MEF_P1.exe、一个Leadshine.dll,一个Googol.dll,主程序需要引用两个DLL。
上述代码没有使用接口,违背了依赖倒置原则,接下来我们增加一个接口MotionCard.dll,让Leadshine.dll和Googol.dll都继承该接口,来保证使用的通用性、规范性;
代码实现如下:

class Program
{
    
    
    static void Main(string[] args)
    {
    
    
        string ret = "";
        string cardName = "实际设备使用哪种运动控制卡";
        IMotionCard motionCard = null;
        if (cardName == "固高板卡")
        {
    
    
            motionCard = new CLeadshine();
        }
        else if (cardName == "雷赛板卡")
        {
    
    
            motionCard = new CGoogol();
        }
        ret = motionCard.InitMotionCard();
        ret = motionCard.ExitMotionCard();
    }
}


public interface IMotionCard
{
    
    
    string InitMotionCard();
    string ExitMotionCard();
}

public class CLeadshine : IMotionCard
{
    
    
    public string InitMotionCard()
    {
    
    
        return "初始化固高板卡成功";
    }
    public string ExitMotionCard()
    {
    
    
        return "退出固高板卡成功";
    }
}

public class CGoogol : IMotionCard
{
    
    
    public string InitMotionCard()
    {
    
    
        return "初始化雷赛板卡成功";
    }
    public string ExitMotionCard()
    {
    
    
        return "退出雷赛板卡成功";
    }
}

可见,在使用接口后,只需要根据不同实现类实例化接口,更适用于团队的协助开发;
但仅仅是这样是不满足的,在复杂的系统设计中,不仅要考虑团队协助开发,也要想办法实现松耦合,以便日后扩展等维护;
如要团队中另外一个人新增一个正运动的板卡怎么操作?
按目前的做法是:
1.增加一个ZMotion.dll,继承IMotionCard接口,实现具体代码;
2.在主程序中,引用ZMotion.dll,增加“正运动板卡”的IF分支,并使用CZMotion实现类实例化接口;

接下来我们开始引入MEF的概念,看看MEF是如何操作的,相比较而言是否实现了松耦合,有哪些实际应用的优势。

三、MEF的概念

MEF(Managed Extensibility Framework)使开发人员能够在其.Net应用程序中提供挂钩,以供用户和第三方扩展。可以将MEF看成一个通用应用程序扩展实用工具。
MEF使开发人员能够动态创建扩展,无需扩展应用程序,也不需要任何特定于扩展内容的知识。这可以确保在编译时两者之间不存在耦合,使应用程序能够在运行时扩展,不需要重新编译。
MEF还可以在将扩展加载到应用程序之前,查看扩展程序集的元数据,这是一种速度更快的方法。
一些特定名词的说明:
Contract:契约,即一种约定
Part:部件,即契约的实现(通过Export特性加入到目录中)
Catalog:目录,存放部件的地方,当需要某个部件时,会在目录中寻找
Container:容器,存放目录并进行部件管理,如导出、导入等
Compose:组装,通过容器在目录中寻找到实现相应契约的部件,进行部件的组装
Export:导出,是部件向容器中提供的一个值,可装饰类、字段、属性或方法
Import:导入,从容器中获得可用的导出,可装饰字段、属性或构造函数参数
ImportMany:导入多个
DirectoryCatalog: 指定目录所在的路径。
Lazy<T,TMetadata>:延迟加载,由 MEF 提供来保存对导出的间接引用的类型。

四、使用示例

还是在上面的场景中修改。我们增加MEF的应用,让主程序MEF_P1.exe不依赖于Leadshine.dll和Googol.dll,而是根据ContractName(契约名称)在指定的路径中寻找DLL文件,实现相同的功能,从而解决了上层依赖下层的强耦合问题。
代码结构如下:
主程序MEF_P1.exe

class Program
{
    
    
    static string DllFilePath = "";
    static string ContractName = "";
    static void Main(string[] args)
    {
    
    
        string ret = "";
        string cardName = "实际设备使用哪种运动控制卡";
        cardName = "固高板卡";
        if (cardName == "固高板卡")
        {
    
    
            DllFilePath = "Googol.dll";
            ContractName = "GoogolCard";
        }
        else if (cardName == "雷赛板卡")
        {
    
    
            DllFilePath = "Leadshine.dll";
            ContractName = "LeadshineCard";
        }

        ret = InitMotionCard();
        ret = ExitMotionCard();
    }
    public static string InitMotionCard()
    {
    
    
        return ExportPart(ContractName, DllFilePath).InitMotionCard();
    }
    public static string ExitMotionCard()
    {
    
    
        return ExportPart(ContractName, DllFilePath).ExitMotionCard();
    }

    public static IMotionCard ExportPart(string ContractName, string DllName)
    {
    
    
        // 创建一个程序集目录,用于从一个程序集获取所有的组件定义
        //Assembly.GetExecutingAssembly():当前方法所在程序集
        //Assembly.GetCallingAssembly():调用当前方法的方法 所在的程序集
        //Assembly.LoadFrom(@"DLLName.dll"):DLLName.dll的程序集
        AssemblyCatalog Catalog = new AssemblyCatalog(Assembly.LoadFrom(DllName));
        //创建容器
        CompositionContainer Container = new CompositionContainer(Catalog);
        //获得容器中的Export
        IMotionCard Export = Container.GetExportedValue<IMotionCard>(ContractName);
        return Export;
    }
}

接口MotionCard.dll

public interface IMotionCard
{
    
    
    string InitMotionCard();
    string ExitMotionCard();
}

插件Googol.dll

[Export("GoogolCard", typeof(IMotionCard))]
public class CGoogol : IMotionCard
{
    
    
    public string InitMotionCard()
    {
    
    
        return "初始化固高板卡成功";
    }
    public string ExitMotionCard()
    {
    
    
        return "退出固高板卡成功";
    }
}

插件Leadshine.dll

[Export("LeadshineCard", typeof(IMotionCard))]
public class CLeadshine : IMotionCard
{
    
    
    public string InitMotionCard()
    {
    
    
        return "初始化雷赛板卡成功";
    }
    public string ExitMotionCard()
    {
    
    
        return "退出雷赛板卡成功";
    }
}

这样,当需要新增一个正运动的板卡怎么操作?
1.增加一个ZMotion.dll,继承IMotionCard接口,实现具体代码;
2.在主程序中,增加“正运动板卡”的锲约名和DLL路径;
完了?还没,这时候还是需要修改主程序,还不是我们想要的,但是到了这一步,效果已经很明显了,接下来
我们把契约名和DLL名称从程序中剥离到配置文件中,并且设计可视化界面提供新增和修改就可以了
这样,当需要新增一个正运动的板卡怎么操作?
1.增加一个ZMotion.dll,继承IMotionCard接口,实现具体代码;
2.更改配置文件或者在可视化界面更改配置
这样就无需更改MEF_P1.exe的代码也无需重新编译,ZMotion.dll相当于一个插件,只通过更改配置文件即可加载到应用程序中。

最终效果如下图,当需要增加ZMotion.dll时,写好ZMotion.dll放到指定路径中,修改config.xml配置文件,增加Item3信息,然后就可以直接打开软件,软件下拉框就出现了正运动板卡了,可选中操作。
在这里插入图片描述

五、MEF框架的好处

解耦

试想下,如果主程序引用了Leadshine.dll和Googol.dll,那么就意味着你可以无限制的开放DLL中类,属性,方法的访问权限,也就意味着主程序中会出现很多耦合的代码,哪天你想移除这个DLL,程序肯定编译失败,而且你要手动删除这些耦合代码,而MEF因为是通过中间接口来完成调用的,所以只向外暴露了接口里面的成员,程序员是无法任意调用DLL中的任何方法,只能通过接口来调用。就算删掉这个DLL,程序也能正常运行!

可扩展性

举个例子,假设你的程序已经移交给客户了,哪天客户说我不想用固高、雷赛的板卡了,我要用正运动的板卡,这时,你只需重新一个ZMotion.dll,使其继承并实现IMotionCard接口,然后,只要将ZMotion.dll和改好的配置文件交给客户,并让其修改选择选项,就能运行使用正运动板卡了。是不是很方便!如果是以前,必须要修改主程序的代码,然后重新编译,发布,再将整个程序移交客户,这样说大家应该都明白了!

更多

MEF不仅可以导出类,还可以导出方法,属性,不管是私有还是公有,从而满足更多的需求!

六、源码链接

MEF插件框架学习展示(源代码)(一)

总结

依赖倒置原则:高层模板不应该依赖于底层模板,两者应该依赖于抽象,而抽象不应该依赖于细节。

猜你喜欢

转载自blog.csdn.net/Aflashstar/article/details/129063647