MEF 程序设计指南


在应用程序中宿主 MEF

在应用程序中宿主 MEF 其实非常简单,只需要创建一个组合容器对象的实例,然后将需要组合的部件和当前宿主程序添加到容器中即可。首先需要添加 MEF 框架的引用,即 System.ComponentModel.Composition.dll。

private void Compose()
{
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
}

通过上面的代码实现就可以完成 MEF 的宿主,实际上在使用 MEF 开发过程中并不会如此简单的应用。可能会定义一个或多个导入和导出部件,然后通过 MEF 容器进行组合,其实也可以理解为“依赖注入”的一种实现。

public interface IBookService
{
    void GetBookName();
}

/// <summary>
/// 导入
/// </summary>
[Export(typeof(IBookService))]
public class ComputerBookService : IBookService
{
    public void GetBookName()
    {
        Console.WriteLine("《Hello Silverlight》");
    }
}

完整代码示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.Reflection;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication1
{

    public interface IBookService
    {
        void GetBookName();
    }

    [Export(typeof(IBookService))]
    public class ComputerBookService : IBookService
    {
        public void GetBookName()
        {
            Console.WriteLine("《Hello WPF》");
        }
    }

    class Program
    {
        /// <summary>
        /// 导入接口的实现部件(Part)
        /// </summary>
        [Import]
        public IBookService Service{get;set;}

        /// <summary>
        /// 宿主MEF并组合部件
        /// </summary>
        private void Compose()
        {
            var catalog=new AssemblyCatalog(Assembly.GetExecutingAssembly());

            var container=new CompositionContainer(catalog);

            //将部件(Part)和宿主程序添加到组合容器
            container.ComposeParts(this, new ComputerBookService());
        }

        static void Main(string[] args)
        {
            Program p =new Program();
            p.Compose();

            p.Service.GetBookName();

            Console.ReadKey(true);

        }
    }
}

Silverlight中使用CompositionInitializer宿主MEF

暂缺

MEF中组合部件(Composable Parts)与契约(Contracts)的基本应用

按照 MEF 的约定,任何一个类或者是接口的实现都可以通过 [System.ComponentModel.Composition.Export] 属性将其他定义组合部件 (Composable Parts),在任何需要导入组合部件的地方都可以通过在特定的组合部件对象属性上使用 [System.ComponentModel.Composition.Import ]实现部件的组合,两者之间通过契约 (Contracts) 进行通信,实际上这一步可以简单的理解为“依赖注入”,本质上就是对象的实例初始化过程。

我个人理解,凡是通过 MEF 的 [ExportAttribute] 标注的对象都可以理解为一个可进行组合的部件,包括对象和对象的属性、字段、方法、事件等,且该对象可以通过 [ImportAttribute] 进行导入。

所谓的契约也就是一种约定,或者叫做一种规则。

在相同的契约下获取所有导出部件的实例代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;

namespace ConsoleApplication2
{
    class Program
    {
        [ImportMany]
        public IEnumerable<ILogger> Loggers { get; set; }

        /// <summary>
        /// 宿主MEF并组合部件
        /// </summary>
        private void Compose()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

            //var catalog = new AggregateCatalog();

            //catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

            var container = new CompositionContainer(catalog);

            try
            {
                container.ComposeParts(this);
            }
            catch (CompositionException ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        static void Main(string[] args)
        {
            //var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

            //Program p = new Program();
            //p.Compose();

            var catalog = new AggregateCatalog();

            catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            try
            {
                container.ComposeParts(p);
            }
            catch (CompositionException ex)
            {
                Console.WriteLine(ex.ToString());
            }





            if (p.Loggers != null)
            {
                Console.WriteLine("Loggers.Length = "+p.Loggers.Count());
                foreach (var logger in p.Loggers)
                {
                    logger.WriteLog("QQ");
                }
            }

            Console.ReadKey(true);
        }
    }

    public interface ILogger
    {
        void WriteLog(string message);
    }

    [Export(typeof(ILogger))]
    public class TXTLogger : ILogger
    {

        public void WriteLog(string message)
        {
            Console.WriteLine("TXTLogger>>>>>>" + message);
        }
    }

    [Export(typeof(ILogger))]
    public class DBLogger : ILogger
    {

        public void WriteLog(string message)
        {
            Console.WriteLine("DBLogger>>>>>>" + message);
        }
    }
}

在某种情况下或许我们就只直接知道需要使用哪一种实现方式,以下是通过契约名从多个实现中获取到指定的组合部件代码示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication3
{
    class Program
    {
        [Import]
        public LogService Service { get; set; }

        static void Main(string[] args)
        {
            var catalog = new AggregateCatalog();

            catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            try
            {
                container.ComposeParts(p);
            }
            catch (CompositionException ex)
            {
                Console.WriteLine(ex.ToString());
            }

            p.Service.DBLogger.WriteLog("Hello MEF in WPF");
            p.Service.TXTLogger.WriteLog("Hello MEF in WPF");

            Console.ReadKey(true);

        }
    }

    /// <summary>
    /// 聚合不同的日志记录部件,通过 MEF 进行组合
    /// </summary>
    [Export]
    public class LogService
    {
        /// <summary>
        /// 根据契约名进行部件的装配
        /// </summary>
        [Import("TXT")]
        public ILogger TXTLogger { get; set; }

        [Import("DB")]
        public ILogger DBLogger { get; set; }
    }

    public interface ILogger
    {
        void WriteLog(string message);
    }

    [Export("TXT", typeof(ILogger))]
    public class TXTLogger : ILogger
    {

        public void WriteLog(string message)
        {
            Console.WriteLine("TXTLogger>>>>>>" + message);
        }
    }

    [Export("DB", typeof(ILogger))]
    public class DBLogger : ILogger
    {

        public void WriteLog(string message)
        {
            Console.WriteLine("DBLogger>>>>>>" + message);
        }
    }
}

使用MEF声明导出(Exports)与导入(Imports)

在 MEF 中,使用 [System.ComponentModel.Composition.ExportAttribute] 支持多种级别的导出部件配置,包括类、字段、属性以及方法级别的导出部件,通过查看 ExportAttribute 的源代码就知道 ExportAttribute 被定义为Attribute,并为其设置了使用范围。

当任何一个类对象或者是其内部的字段、属性、方法需要作为可组合部件的时候,就可以使用 [ExportAttribute] 将其标注为可导出部件。比如需要将一个对象做为可组合部件进行导出(就是类级别的导出),只需要在类上添加 [ExportAttribute] 就行了。

代码示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication4
{
    class Program
    {
        [Import("Name")]
        public string BookName { get; set; }

        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            container.ComposeParts(p);

            Console.WriteLine(p.BookName);

            Console.ReadKey(true);

        }
    }

    public class BookService
    {
        [Export("Name")]
        public string Name
        {
            get
            {
                return "MEF 程序设计";
            }
        }
    }

}

方法的导入导出主要是利用委托实现,即 Action 或 Action,其使用也是非常简单的,无聊是方法所需的参数还是返回值,都可以通过匿名委托去实现。在需要使用到此方法的地方,只需要通过匿名委托的方法对该方法进行导入就可以了。

代码示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication4
{
    class Program
    {
        [Import("Name")]
        public string BookName { get; set; }

        [Import(typeof(Action<string>))]
        public Action<string> PrintBookName { get; set; }

        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            container.ComposeParts(p);

            p.PrintBookName(p.BookName);

            Console.ReadKey(true);

        }
    }

    public class BookService
    {
        [Export("Name")]
        public string Name
        {
            get
            {
                return "MEF 程序设计";
            }
        }

        [Export(typeof(Action<string>))]
        public void PrintBookName(string name)
        {
            Console.WriteLine(name);
        }

    }

}

MEF 也支持继承的导入和导出应用,使用 [System.ComponentModel.Composition.InheritedExportAttribute] 实现基于继承的导出,其他的和字段、属性、方法级的应用完全一致。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication4
{
    class Program
    {
        [Import("Name")]
        public string BookName { get; set; }

        [Import(typeof(Action<string>))]
        public Action<string> PrintBookName { get; set; }

        [Import(typeof(IUserService))]
        public UserService userService { get; set; }

        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            container.ComposeParts(p);

            p.PrintBookName(p.BookName);

            Console.WriteLine(p.userService.GetUserName());

            Console.ReadKey(true);

        }
    }

    public class BookService
    {
        [Export("Name")]
        public string Name
        {
            get
            {
                return "MEF 程序设计";
            }
        }

        [Export(typeof(Action<string>))]
        public void PrintBookName(string name)
        {
            Console.WriteLine(name);
        }

    }

    [InheritedExport(typeof(IUserService))]
    public interface IUserService
    {
        string GetUserName();
    }

    public class UserService : IUserService
    {

        public string GetUserName()
        {
            return "CMONO.NET";
        }
    }

}

构造方法参数的导入

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication4
{
    class Program
    {
        [Import("Name")]
        public string BookName { get; set; }

        [Import(typeof(Action<string>))]
        public Action<string> PrintBookName { get; set; }

        [Import(typeof(IUserService))]
        public UserService userService { get; set; }

        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            container.ComposeParts(p);

            p.PrintBookName(p.BookName);

            Console.WriteLine(p.userService.GetUserName());

            Console.ReadKey(true);

        }
    }

    public class BookService
    {
        [Export("Name")]
        public string Name
        {
            get
            {
                return "MEF 程序设计";
            }
        }

        [Export(typeof(Action<string>))]
        public void PrintBookName(string name)
        {
            Console.WriteLine(name);
        }

    }

    [InheritedExport(typeof(IUserService))]
    public interface IUserService
    {
        string GetUserName();
    }


    public class UserService : IUserService
    {
        public string Name { get; set; }

        [ImportingConstructor]
        public UserService([Import("Name")]string name)
        {
            this.Name = name;
        }

        public string GetUserName()
        {
            return Name;
        }
    }

}

迟延(Lazy)加载导出部件(Export Part)与元数据(Metadata)

MEF中使用导出与导入,实质上就是对一个对象的实例化的过程,通过MEF的特性降低了对象的直接依赖,从而让系统的设计达到一种高灵活、高扩展性的效果。在具体的设计开发中,存在着某些对象是不需要在系统运行或者的附属对象初始化的时候进行实例化的,仅仅只需要在需要使用到他的时候才会进行实例化,从系统的上来说这也是提高系统性能的一种可行的实现方式,这种方式就可以理解为对象的迟延初始化,或者叫迟延加载。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication5
{
    class Program
    {
        /// <summary>
        /// 传统加载
        /// </summary>
        [Import(typeof(ILogger))]
        public ILogger Logger { get; set; }

        [Import]
        public Lazy<ILogger> LazyLogger;

        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            container.ComposeParts(p);

            p.Logger.WriteLog("传统加载日志内容");

            p.LazyLogger.Value.WriteLog("延迟加载日志内容");

            Console.ReadKey(true);
        }
    }

    public interface ILogger
    {
        void WriteLog(string message);
    }

    [Export(typeof(ILogger))]
    public class DBLogger : ILogger
    {

        public void WriteLog(string message)
        {
            Console.WriteLine(message);
        }
    }

}

延迟加载还支持元数据的导入和导出,主要使用[MetadataAttribute]特性实现,实际开发中可以进行自定义元数据结构。

MEF中也提供了专门用于元数据导入、导出的特性[ExportMetadata],使用ExportMetadata基本可以满足大部分元数据的导出、导入支持。

迟延加载也是支持弱类型的元数据类型的,也可以对元数据进行过滤。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication5
{
    class Program
    {
        /// <summary>
        /// 传统加载
        /// </summary>
        [Import(typeof(ILogger))]
        public ILogger Logger { get; set; }

        [Import]
        public Lazy<ILogger> LazyLogger;

        [Import(typeof(User))]
        public Lazy<User, IMetaData> User { get; set; }

        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            container.ComposeParts(p);

            p.Logger.WriteLog("传统加载日志内容");

            p.LazyLogger.Value.WriteLog("延迟加载日志内容");

            Console.WriteLine(p.User.Value.UserName);
            Console.WriteLine(p.User.Metadata.QQ);
            Console.WriteLine(p.User.Metadata.Name);

            Console.ReadKey(true);
        }
    }

    public interface ILogger
    {
        void WriteLog(string message);
    }

    [Export(typeof(ILogger))]
    public class DBLogger : ILogger
    {

        public void WriteLog(string message)
        {
            Console.WriteLine(message);
        }
    }


    public interface IMetaData
    {
        string Name { get; }
        string QQ { get; }
    }

    [ExportMetadata("Name", "WWW.CMONO.NET")]
    [ExportMetadata("QQ", "Tencent")]
    [Export(typeof(User))]
    public class User
    {
        public string UserName = "CMONO.NET";
    }

}

MEF中的目录服务(DeploymentCatalog)

MEF提供的基于特性的编程模型,可以动态的根据目录找出程序集里面的所有程序部件。 对于MEF的目录服务MEF分别为WPF和Silverlight提供了不同的目录机制。使用目录的主要功能就是方便实现程序部件的装载,以及动态的组合应用程序部件等功能,更可以非常方便的得到程序部件的程序集、导出部件等相关数据。

待续

使用目录(Catalog)动态装载xap与目录筛选(Filtered Catalog)

如果不使用MEF进行托管扩展处理,只有通过WebClient进行程序包的下载、解析。实际上MEF的动态下载的底层实现一样是使用的WebClient,然后利用AggregateCatalog进行动态组合

待续

部件生命周期(Parts Lifetime)托管

MEF中的每一个可进行动态装配的导出部件都是具有生命周期的,在没有特别需求的情况下一般都没有对生命周期进行管理,而实际上MEF已为每一个部件进行了默认的生命周期管理,MEF的生命周期分为三种:Any、Shared及NonShared,被定义在System.ComponentModel.Composition.CreationPolicy枚举对象中。

Any表示可共享或不共享,部件的实例用MEF容器根据不同的请求需求自动控制;Shared表示共享部件,既Shared类型的插件部件可以在多个MEF组合容器中共用;其次是NonShared类型,表示不共享部件实例,每当有新的请求就会创建一个新的对象实例。在MEF中,通过PartCreationPolicyAttribute特性实现对部件的生命周期配置。

表面上看去和上一篇指南中介绍的部件的筛选过滤功能非常相似,不同是的过滤筛选是通过自定义筛选策略实现,而这里是通过MEF生命周期范围托管来实现的。在实际的项目开发中需根据不同的应用场景确定具体的技术实现方案。

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace ConsoleApplication8
{
    class Program
    {
        [ImportMany(RequiredCreationPolicy = CreationPolicy.Shared)]
        public IEnumerable<IBookService> Service { get; set; }

        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            container.ComposeParts(p);

            Console.WriteLine(p.Service.Count().ToString());

            Console.ReadKey(true);

        }
    }

    public interface IBookService
    {
        string GetBookName();
    }


    [PartCreationPolicy(CreationPolicy.Any)]
    [Export(typeof(IBookService))]
    public class MEFBookService : IBookService
    {

        public string GetBookName()
        {
            return "《MEF 程序设计指南》";
        }
    }

    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export(typeof(IBookService))]
    public class ASPNETBookService : IBookService
    {

        public string GetBookName()
        {
            return "《ASP.NET 项目案例》";
        }
    }

    [PartCreationPolicy(CreationPolicy.Shared)]
    [Export(typeof(IBookService))]
    public class SilverlightBookService : IBookService
    {

        public string GetBookName()
        {
            return "《Silverlight 高级编程》";
        }
    }

}

除了容器部件的生命周期托管,我们也得考虑部件容器自身的生命周期,容器什么时候释放资源,什么时候释放其内部部件的资源占用等。为了提高系统的整体性能,MEF 建议将每一个可导入部件实现 IDisposable 接口,用于资源的占用处理,如果部件从 MEF 容器中移除,那么所对应占用的资源也会自动的清理。需要注意的是,当组合容器被释放到后延迟加载的操作就不能再继续工作了,会抛出 System.ObjectDisposedException 异常。

重组(Recomposition)MEF部件

应用程序部件的组合都是在初始化的时候进行装配的。如果当应用程序已经初始化完成了,此时又有新的部件被导入且进行装配,按照目前的实现方式就无法实现了,我们需要一种可以进行动态装配、动态组合以及可以动态的进行新的部件被装配组合的通知的功能,于此MEF所提供的重组(Recomposition)部件特性就可以派上用场了。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;

namespace ConsoleApplication9
{
    class Program
    {
        [ImportMany(AllowRecomposition = true)]//AllowRecomposition=true参数就表示运行在有新的部件被装配成功后进行部件集的重组。
        public IBookService[] Services { get; set; }

        static void Main(string[] args)
        {
            var catalog = new AssemblyCatalog(typeof(Program).Assembly);

            var container = new CompositionContainer(catalog);

            Program p = new Program();

            container.ComposeParts(p);

            Console.WriteLine(p.Services.Length.ToString());

            Console.ReadKey(true);
        }
    }

    public interface IBookService
    {
        string GetBookName();
    }


    [PartCreationPolicy(CreationPolicy.Any)]
    [Export(typeof(IBookService))]
    public class MEFBookService : IBookService
    {

        public string GetBookName()
        {
            return "《MEF 程序设计指南》";
        }
    }

    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export(typeof(IBookService))]
    public class ASPNETBookService : IBookService
    {

        public string GetBookName()
        {
            return "《ASP.NET 项目案例》";
        }
    }

    [PartCreationPolicy(CreationPolicy.Shared)]
    [Export(typeof(IBookService))]
    public class SilverlightBookService : IBookService
    {

        public string GetBookName()
        {
            return "《Silverlight 高级编程》";
        }
    }
}

知识共享许可协议
《MEF 程序设计指南》 常伟华 创作。
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议 | 3.0 中国大陆许可协议进行许可。

站内公告

A PHP Error was encountered

Severity: Core Warning

Message: PHP Startup: zip: Unable to initialize module Module compiled with module API=20060613 PHP compiled with module API=20090626 These options need to match

Filename: Unknown

Line Number: 0

Backtrace: