rainit2006 / C-Program

C# Program knowledge
0 stars 0 forks source link

MEF #14

Open rainit2006 opened 7 years ago

rainit2006 commented 7 years ago

MEF作为IOC的方式之一,它的主要作用是解耦,MEF加上面向接口编程,可以使得你的设计更加灵活。

image

rainit2006 commented 7 years ago

ImportingConstructor 我们知道类的构造函数是可以重载的,我们通过构造函数可以向对象传递参数。那么如果我们的MEF也需要通过构造函数传参怎么办呢?别担心,有我们神奇的ImportingConstructor为您解决。

[ImportingConstructor]
        public PowerManagerDomainService(IUserRepository oUserRepository, IRoleRepository oRoleRepository)
        {
            _userRepository = oUserRepository;
            _roleRepository = oRoleRepository;
        }

要求:IUserRepository 和 IRoleRepository 类型要有对应的Export。
这样在构造PowerManagerDomainService时会调用IUserRepository和IRoleRepository的构造函数。
[Export]
public sealed class A
{
}

[Export]
public sealed class B
{
    [ImportingConstructor]
    public B(A a)
    {
    }
}

Then you call container.GetExportedValue(), it will look in the catalog to find the export for B, then find the importing constructor, then try to resolve the import for A. Assuming that class A is part of your available exports (either added explicitly or, more typically, part of a catalog), it will then get the exported value of A (possibly constructing it) and use that to construct B.

rainit2006 commented 7 years ago

2つ以上インスタンスが欲しいんだけど?:PartCreationPolicy属性指定 MEFでは、デフォルトだとコンテナ内でのインスタンスは1つということになります。ある意味シングルトンパターンみたいなことをしなくてもMEFのコンテナ内で勝手にシングルトンとして管理してくれるので有りがたい面もあります。しかし、全てがシングルトンだと困るということもあります。そういう時にどうするのか?というのを見てみましょう。

  • 作成時のポリシーを設定する Export属性と同時に使用できる属性としてPartCreationPolicy System.ComponentModel.Composition.PartCreationPolicyAttribu: Exportされたクラスのインスタンスを生成するときの動作を設定する

PartCreationPolicyにはCreationPolicy列挙体の値を設定します。CreationPolicyの値は以下のようなものがあります。

CreationPolicy構造体 ーー Shared : 全体で共有する。つまりシングルトン。 ーー NonShared : 共有しない。つまり毎回インスタンスを作る。 ーー Any : Sharedを求められるならSharedでNonSharedを求められるならNonSharedにもなる柔軟な感じ

例:NonSharedに設定された場合、インスタンスは違うものになる。

// Export属性を使ってカタログに収集してもらう対象にする
[Export]
// インスタンスを共有しないようにする
[PartCreationPolicy(CreationPolicy.NonShared)]
public class A
{
    // このプロパティの型に一致するクラスのインスタンスを
    // コンテナ内から探してセットしてという意味
    [Import]
    public B Value { get; set; }

    // Bクラスを使って処理をする
    public void Foo()
    {
        this.Value.Bar();
    }
}

Aクラスのインスタンス同士の比較とAクラスが参照しているBクラスのインスタンス同士を比較しています。


// 現在実行中のアセンブリからカタログを作成する
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());

// 現在実行中のアセンブリのカタログを元にコンテナを作る
var container = new CompositionContainer(catalog);

// Aクラスのインスタンスを2つ取得する
var typeA1 = container.GetExportedValue<A>();
var typeA2 = container.GetExportedValue<A>();

// typeA1とtypeA2を比較してみると・・・
Console.WriteLine("typeA1 == typeA2 -> {0}", typeA1 == typeA2);
// ついでにAクラスが参照しているBクラスが同一なのか確認
Console.WriteLine("typeA1.Value == typeA2.Value -> {0}", typeA1.Value == typeA2.Value);

実行すると、以下のような結果になります。


typeA1 == typeA2 -> False
typeA1.Value == typeA2.Value -> True
rainit2006 commented 7 years ago

MEF的基础知识

  • MEF基础导入导出的使用: MEF的使用步骤主要分三步:宿主MEF并组合部件、标记对象的导出、对象的导入使用。

代码实例:

class Program2
    {
     //导入对象使用
        [Import("chinese_hello")] ////////////
        public Person oPerson { set; get; }
        ////////这里的chinese_hello是和Export里面的chinese_hello对应的,由此可知,每一个
         [Import("chinese_hello")]这种Import一定可以找到一个对应的Export,如果找不到,程序就会报异
          常。当然如果这里的Import如果改成[Import("american_hello")],那么oPerson肯定就对应一个
          American对象。
          上面的[Import("chinese_hello")]等价于Person oPerson=new Chinese();。看到这里可能有人就会说
            这个Import是多此一举了,既然我们可以new,为什么非要用这种奇怪的语法呢,怪别扭的。其实
            如果我们站在架构的层面,它的好处就是可以减少dll之间的引用。
          //////////

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();
            var strRes = oProgram.oPerson.SayHello("李磊");
            Console.WriteLine(strRes);

            Console.Read();
        }

  //宿主MEF并组合部件///////////
        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //将部件(part)和宿主程序添加到组合容器
            container.ComposeParts(this);
          //////////////添加到组合容器中之后,如果该类里面有Import,MEF才会自动去寻找对应的Export。
           这也就是为什么使用MEF前必须要组合部件的原因。
            //////////////////
        }
    }

   public interface Person
    {
        string SayHello(string name);
    }

   //声明对象可以导出
    [Export("chinese_hello", typeof(Person))]//////////
    public class Chinese : Person
    {
        public string SayHello(string name)
        {
            return "你好:" + name ;
        }
    }

    [Export("american_hello", typeof(Person))]
    public class American : Person
    {
        public string SayHello(string name)
        {
            return "Hello:" + name ;
        }
    }
  • MEF导入导出扩展: 按照MEF的约定,任何一个类或者是接口的实现都可以通过[System.ComponentModel.Composition.Export] 属性将其他定义组合部件(Composable Parts),在任何需要导入组合部件的地方都可以通过在特定的组合部件对象属性上使用[System.ComponentModel.Composition.Import ]实现部件的组合,两者之间通过契约(Contracts)进行通信。

通过上面的例子我们可以知道,对象是可以通过Import和Export来实现导入和导出的。 同理,对象的属性、字段、方法、事件等也可以通过[Import/Export]进行导入和导出。 想要一次导入多个对象:[ImportMany]。注意:使用ImportMany的时候对应的Export不能有chinese_hello这类string参数。直接使用 [Export(typeof(Person))]

class Program2
    {
        [ImportMany]
        public IEnumerable<Person> lstPerson { set; get; }

        static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();

            Console.WriteLine(oProgram.lstPerson.Count());

            Console.Read();
        }

        void MyComposePart()
        {
            var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);
            //将部件(part)和宿主程序添加到组合容器
            container.ComposeParts(this);
        }
    }

public interface Person
    {
        string SayHello(string name);
    }

    [Export(typeof(Person))]
    public class Chinese : Person
    {
        public string SayHello(string name)
        {
            return "你好:" + name ;
        }
    }

    [Export(typeof(Person))]
    public class American : Person
    {
        public string SayHello(string name)
        {
            return "Hello:" + name ;
        }
    }

输出结果为2
  • MEF的延迟加载 当装配一个组件的时候,当前组件里面的所有的Import的变量都自动去找到对应的Export而执行了实例化,有些时候,出于程序效率的考虑,不需要立即实例化对象,而是在使用的时候才对它进行实例化。MEF里面也有这种延迟加载的机制。

从下面的例子可以看到:oPerson对象已经实例化了,而oPerson2.Value对象没有实例化,当程序执行var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei")这一句的时候,oPerson2.Value对象才进行实例化。这种需要在某些对程序性能有特殊要求的情况下面有一定的作用。

部分代码略
[Import("chinese_hello")]
        public Person oPerson { set; get; }

        [Import("american_hello")]
        public Lazy<Person> oPerson2 { set; get; }

     static void Main(string[] args)
        {
            var oProgram = new Program2();
            oProgram.MyComposePart();

            var strRes = oProgram.oPerson.SayHello("李磊");
            var strRes2 = oProgram.oPerson2.Value.SayHello("Lilei");
            Console.WriteLine(strRes);

            Console.Read();
        }
rainit2006 commented 7 years ago

MEF应用例子 http://www.cnblogs.com/landeanfen/p/4760740.html

  • 1、仓储模式:也叫Repository模式。 Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间。它的存在让领域层感觉不到数据访问层的存在,它提供一个类似集合的接口提供给领域层进行领域对象的访问。Repository是仓库管理员,领域层需要什么东西只需告诉仓库管理员,由仓库管理员把东西拿给它,并不需要知道东西实际放在哪。Repository模式一般是用来封装数据访问层的,这也就是为什么很多地方看到说的什么“数据仓储”。

http://www.cnblogs.com/landeanfen/p/4770609.html

rainit2006 commented 7 years ago

初期化処理ーーIPartImportsSatisfiedNotificationインターフェース http://blog.okazuki.jp/entry/20110425/1303740414 https://code.msdn.microsoft.com/Managed-Extensibility-3332350b IPartImportsSatisfiedNotificationインターフェース:パーツのインポートが満たされたときに、そのパーツに通知します。 OnImportsSatisfiedメソッド:MEFのコンテナ内で初期化が終わったタイミングで呼び出されます。

namespace MEFInitSample
{
    using System;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            // コンテナ初期化
            var c = new CompositionContainer(
                new AssemblyCatalog(Assembly.GetExecutingAssembly()));

            // IPartImportsSatisfiedNotification.OnImportsSatisfiedの動作確認
            Console.WriteLine("PersonClient取得前");
            var client = c.GetExportedValue<PersonClient>();
            Console.WriteLine("PersonClient取得後");
        }
    }

    [Export]
    public class PersonClient : IPartImportsSatisfiedNotification
    {
        [Import]
        public Person Person { get; set; }

        public PersonClient()
        {
            Console.WriteLine("PersonClient.ctor() called");
            Console.WriteLine("  this.Person = " + this.Person);
        }

        public void OnImportsSatisfied()
        {
            // ここでは、Personクラスの設定が完了しているので好きなように使える
            Console.WriteLine("PersonClient.OnImportsSatisfied() called");
            Console.WriteLine("  this.Person = " + this.Person);
        }
    }

    [Export]
    public class Person
    {
    }    
}

先ほど説明したIPartImportsSatisfiedNotificationをPersonClientに実装しています。OnImportsSatistfiedでPersonプロパティの値を表示しています。これを実行すると、以下の結果が表示されます。

PersonClient取得前
PersonClient.ctor() called
  this.Person =
PersonClient.OnImportsSatisfied() called
  this.Person = MEFInitSample.Person
PersonClient取得後

最初のthis.Personの出力は、まだ値が設定されていないので何も表示されていません。そして、OnImportsSatisfiedではインスタンスが設定されているので、きちんと表示されています。

rainit2006 commented 7 years ago

遅延初期化のLazy http://blog.okazuki.jp/entry/20110305/1298432049 ー延期初期化の必要 大規模なアプリケーションには、数百以上のプラグインから構成されることも珍しくありません。これらのプラグインが全て起動時に初期化されると、パフォーマンス影響が必至である。

このような問題を回避するために、とりあえず不要なものは初期化しないというアプローチがよくとられます。起動時には必要最低限の初期化処理だけやって、必要になったタイミングでプラグインのインスタンスを生成するという感じです。

ー やりかた Importする側をLazyにする LazyのValueプロパティにアクセスしたタイミングでインスタンスが作られる

// 遅延初期化の場合
[Export]
class LazyApplication
{
    [ImportMany]
    public IEnumerable<Lazy<IPlugin>> Plugins { get; set; }

    public void Run()
    {
        foreach (var plugin in this.Plugins)
        {
            // ここで初めてクラスのインスタンスが作成される
            plugin.Value.Execute();
        }
    }
}
rainit2006 commented 7 years ago

面向接口编程 image

rainit2006 commented 6 years ago

关于GetExportedValue的使用示例

// 挨拶屋
interface IGreeter
{
    void Greet();
}

// IGreeterとしてTnakaクラスをExport
[Export(typeof(IGreeter))]
class Tanaka : IGreeter
{
    public void Greet()
    {
        Console.WriteLine("こんにちは!たなかです!");
    }
}
static void Main(string[] args)
{
    // コンテナを作るところはお馴染み
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);

    // Tanakaさんをくださいな
    var g = container.GetExportedValue<IGreeter>();
    // 挨拶実行
    g.Greet();
}

この状態でプログラムを実行すると、以下のような結果になります。 こんにちは!たなかです!

如果把[Export(typeof(IGreeter))]换成[Export],

interface IGreeter
{
    void Greet();
}

// まずは普通にExport
[Export]
class Tanaka : IGreeter
{
    public void Greet()
    {
        Console.WriteLine("こんにちは!たなかです!");
    }
}

那么,下面代码会报错

var g = container.GetExportedValue<IGreeter>();
    // 挨拶実行
    g.Greet();

但是,下面代码会成功

var g = container.GetExportedValue<Tanaka>();
    // 挨拶実行
    g.Greet();

同じ型で複数個Export

// IGreeterとしてTnakaクラスをExport
[Export("Tanaka", typeof(IGreeter))] //Export时,类名也要声明
class Tanaka : IGreeter
{
    public void Greet()
    {
        Console.WriteLine("こんにちは!たなかです!");
    }
}

// IGreeterとしてOkazukiクラスをExport
[Export("Okazuki", typeof(IGreeter))]
class Okazuki : IGreeter
{
    public void Greet()
    {
        Console.WriteLine("犬のアイコンです");
    }
}
static void Main(string[] args)
{
    // コンテナを作るところはお馴染み
    var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);

    // Tanakaさんをくださいな
    var t = container.GetExportedValue<IGreeter>("Tanaka");
    // 挨拶実行
    t.Greet();

    // Okazukiさんをくださいな
    var o = container.GetExportedValue<IGreeter>("Okazuki");
    // 挨拶実行
    o.Greet();
}

结果:
こんにちは!たなかです!
犬のアイコンです

CompositionContainer.GetExports 取出多个exports对象

GetExports() : Gets all the exports with the contract name derived from the specified type parameter.(Inherited from ExportProvider.) GetExports(String) : Gets all the exports with the specified contract name.(Inherited from ExportProvider.)

rainit2006 commented 6 years ago

Import

[Export(typeof(IAction))] 
public class MyAction : IAction 
{     
    public void DoAction() {         
          Console.WriteLine("My Action Invoked");     
   } 
}   

public class ActionManager 
{      
    [Import(typeof(IAction))]     
    private IAction _action;  
}

这便是最最基本的导入和导出,我们都约定了IAction作为契约,所以这样的导入导出可以得到匹配。

ImportMany 複数のクラスをImportする専用のImportManyという属性になります。 ImportManyはIEnumerable型に指定することで動作します。

[Export]
class GreeterClient
{
    // IGreeterとしてExportされているものを全てかき集める
    [ImportMany]
    public IEnumerable<IGreeter> Greeters { get; set; }

    // 全員に挨拶させる
    public void Execute()
    {
        foreach (var g in this.Greeters)
        {
            g.Greet();
        }
    }
}

このGreeterClientはIGreeter型としてExportされているものを全てImportして、Executeメソッドで全員に挨拶をさせるクラスです。

[ImportMany] public IEnumerable<Lazy<IMyPlugin, IMyExportAttribute>> xxxxx 的时候

《IMyPlugin.cs》
public interface IMyPlugin
{
    void Execute();
}

《IMyExportAttribute.cs》
public interface IMyExportAttribute
{
    string ExtensionName { get; } //< 拡張機能の名称
}

《MyExportAttribute.cs》
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class MyExportAttribute: ExportAttribute, IMyExportAttribute
{
    public MyExportAttribute(string extensionName)
        : base(typeof(IMyPlugin))
    {
        this.ExtensionName = extensionName;
    }

    public string ExtensionName { get; private set; }
}

《MyPluginA.cs》
[MyExport("MyPluginA")] //注意使用的是MyExport
public class MyPluginA : IMyPlugin
{
    public MyPluginA()
    {
        Console.WriteLine("PluginAのコンストラクタ");
    }

    public void Execute()
    {
        Console.WriteLine("PluginAのExecute");
    }
}

《MyPluginB.cs》
[MyExport("MyPluginB")]   //注意使用的是MyExport
class MyPluginB : IMyPlugin
{
    public MyPluginB()
    {
        Console.WriteLine("PluginBのコンストラクタ");
    }

    public void Execute()
    {
        Console.WriteLine("PluginBのExecute");
    }
}

《Client.cs》
[Export]
class Client
{
    [ImportMany]
    public IEnumerable<Lazy<IMyPlugin, IMyExportAttribute>> Plugins { get; set; }

    public void Run()
    {
        foreach (var ex in this.Plugins)
        {
            Console.WriteLine($"[{ex.Metadata.ExtensionName}] ");
            ex.Value.Execute();
        }
    }
}

《Program.cs》
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("アプリケーションを開始します。");

        if (!Directory.Exists("Extensions"))
            Directory.CreateDirectory("Extensions");

        var assm = new AssemblyCatalog(Assembly.GetExecutingAssembly()); // "Application"アセンブリ内にある拡張機能を読み込む
        var extensions = new DirectoryCatalog("Extensions"); // "Extensions"フォルダ内にある拡張機能(DLLファイル)を読み込む

        var agg = new AggregateCatalog(assm, extensions); // 2つのカタログをマージしたカタログを作成する
        var container = new CompositionContainer(agg);

        var app = container.GetExportedValue<Client>();
        app.Run();

        Console.WriteLine("アプリケーションを終了します。");
        Console.ReadLine();
    }
}

执行结果:
アプリケーションを開始します。
[MyPluginA]
PluginAのコンストラクタ
PluginAのExecute
[MyPluginB]
PluginBのコンストラクタ
PluginBのExecute
アプリケーションを終了します。

ImportingConstructor https://docs.microsoft.com/en-us/dotnet/framework/mef/attributed-programming-model-overview-mef By default, when creating a part, the composition engine uses the parameter-less constructor. To make the engine use a different constructor, you can mark it with the ImportingConstructor attribute.

 [Export(typeof(IPowerManagerDomainService))]
    public class PowerManagerDomainService:IPowerManagerDomainService
    {
        private IUserRepository _userRepository = null;
        private IRoleRepository _roleRepository = null;
        private IUserRoleRepository _userroleRepository = null;

        [ImportingConstructor]
        public PowerManagerDomainService(IUserRoleRepository oUserRoleRepository)
        //IUserRoleRepository 声明了[Export(typeof(IUserRepository))]
        {
            _userroleRepository = oUserRoleRepository;
        }
}

如果某一个接口(IUserRepository)存在多个实现类(UserRepository_A和UserRepository_B)的导出, 则可以写为:

 [ImportingConstructor]
        public PowerManagerDomainService([Import("userRepository_A", typeof(IUserRepository))]IUserRepository oUserRepository, IRoleRepository oRoleRepository)
        {
            _userRepository = oUserRepository;
            _roleRepository = oRoleRepository;
        }

注意: 确保该类型具有默认构造函数或有一个标记有“System.ComponentModel.Composition.ImportingConstructorAttribute”的构造函数。 MEF的ImportingConstructorAttribute特性不支持这种多个构造函数同时标注的情况。

中文讲解: http://www.cnblogs.com/landeanfen/p/4848885.html

PartCreationPolicy:MEF部件的生命周期 CreationPolicy.Any:表示可共享或不共享,部件的实例用MEF容器根据不同的请求需求自动控制。 CreationPolicy.Shared表示共享部件,既Shared类型的插件部件可以在多个MEF组合容器中共用,而且多次请求该部件返回的其实是同一个对象。 CreationPolicy.NonShared类型,表示不共享部件实例,每当有新的请求就会创建一个新的对象实例。

如果再系统中需要关闭某个界面,再重新打开时需要新创建一个实例的就不能忘记设置PartCreationPolicy为CreationPolicy.NonShared。

[Export("ViewB", typeof(IView))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public partial class ViewB : UserControl,IView
    {
        public ViewB()
        {
            InitializeComponent();
        }
    }
rainit2006 commented 6 years ago

IPartImportsSatisfiedNotification Notifies a part when its imports have been satisfied. public interface IPartImportsSatisfiedNotification

成员函数: OnImportsSatisfied():Called when a part's imports have been satisfied and it is safe to use.

MEFから設定されるインスタンスが設定されたあとに初期か処理を行いたいときはIPartImportsSatisfiedNotificationを実装するということ。

namespace MEFInitSample
{
    using System;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Reflection;

    class Program
    {
        static void Main(string[] args)
        {
            // コンテナ初期化
            var c = new CompositionContainer(
                new AssemblyCatalog(Assembly.GetExecutingAssembly()));

            // IPartImportsSatisfiedNotification.OnImportsSatisfiedの動作確認
            Console.WriteLine("PersonClient取得前");
            var client = c.GetExportedValue<PersonClient>();
            Console.WriteLine("PersonClient取得後");
        }
    }

    [Export]
    public class PersonClient : IPartImportsSatisfiedNotification
    {
        [Import]
        public Person Person { get; set; }

        public PersonClient()
        {
            Console.WriteLine("PersonClient.ctor() called");
            Console.WriteLine("  this.Person = " + this.Person);
        }

        public void OnImportsSatisfied()
        {
            // ここでは、Personクラスの設定が完了しているので好きなように使える
            Console.WriteLine("PersonClient.OnImportsSatisfied() called");
            Console.WriteLine("  this.Person = " + this.Person);
        }
    }

    [Export]
    public class Person
    {
    }    
}
rainit2006 commented 6 years ago

MEF plugin的初始化 经过Demo确认,plugin在被加载时会自动被调用constructor函数从而实现初始化; 如果plugin有多个constructor函数, 则优先选择[ImportConstrustor]属性的constructor函数(当然,其参数对应的类必须要相应的声明Export)。 如果不存在[ImportConstrustor]属性,则选择无参数的constructor函数。 发现plugin实例在被container load后就被初始化了(默认用无参数的constructor函数)。 如果constructor函数有参数,并需要import, 则必须声明importingConstruct.

自己写代码确认:

<IPlugin.cs>
 public interface IPlugin
    {
        void Execute();
    }

<Plugin1.cs>
[Export(typeof(IPlugin))]
    public class Plugin1 : IPlugin
    {
        private ObjectA objA { get; set; }

        public Plugin1()
        {
            Console.WriteLine("Plugin1 Contructor.");
        }

        [ImportingConstructor]    //不声明这个的话,会抱错。
        public Plugin1(ObjectA  a)
        {
            objA = a;
            Console.WriteLine("Plugin1 with objA Contructor.");
        }

        void IPlugin.Execute()
        {
            Console.WriteLine("Plugin1 exected.");
        }

    }

    [Export]
    public class ObjectA
    {
        private ObjectA()
        {
            Console.WriteLine("ObjectA Constructor.");
        }
    }

<Program.cs>
class Program
    {
        [Import]
        private IPlugin plugin { get; set; }

        static void Main(string[] args)
        {
            var oProgram = new Program();
            Console.WriteLine("Begin");
            oProgram.LoadPlugin();
            oProgram.plugin.Execute();

            Console.ReadKey();
        }

        private void LoadPlugin()
        {
            AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);  

            container.ComposeParts(this); //此时plugin实例的construct函数被调用。
            Console.WriteLine("LoadPlugin finish.");

        }
    }
rainit2006 commented 6 years ago

MEF plugin的初始化 经过Demo确认,plugin在被加载时会自动被调用constructor函数从而实现初始化; 如果plugin有多个constructor函数, 则优先选择[ImportConstrustor]属性的constructor函数(当然,其参数对应的类必须要相应的声明Export)。 如果不存在[ImportConstrustor]属性,则选择无参数的constructor函数。 发现plugin实例在被container load后就被初始化了(默认用无参数的constructor函数)。 如果constructor函数有参数,并需要import, 则必须声明importingConstruct.

自己写代码确认:

<IPlugin.cs>
 public interface IPlugin
    {
        void Execute();
    }

<Plugin1.cs>
[Export(typeof(IPlugin))]
    public class Plugin1 : IPlugin
    {
        private ObjectA objA { get; set; }

        public Plugin1()
        {
            Console.WriteLine("Plugin1 Contructor.");
        }

        [ImportingConstructor]    //不声明这个的话,会抱错。
        public Plugin1(ObjectA  a)
        {
            objA = a;
            Console.WriteLine("Plugin1 with objA Contructor.");
        }

        void IPlugin.Execute()
        {
            Console.WriteLine("Plugin1 exected.");
        }

    }

    [Export]
    public class ObjectA
    {
        private ObjectA()
        {
            Console.WriteLine("ObjectA Constructor.");
        }
    }

<Program.cs>
class Program
    {
        [Import]
        private IPlugin plugin { get; set; }

        static void Main(string[] args)
        {
            var oProgram = new Program();
            Console.WriteLine("Begin");
            oProgram.LoadPlugin();
            oProgram.plugin.Execute();

            Console.ReadKey();
        }

        private void LoadPlugin()
        {
            AssemblyCatalog catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            var container = new CompositionContainer(catalog);  
           Console.WriteLine("new container finish.");
            container.ComposeParts(this); //此时plugin实例的construct函数被调用。
            Console.WriteLine("container.ComposeParts(this) finish.");

        }
    }

输出结果:
Begin
new container finish.
ObjectA constructor.
Plugin1 with objA Constructor.
container.ComposePart(this ) finished.
Plugin1 exected.
rainit2006 commented 6 years ago

rainit2006 commented 6 years ago

メタデータ

  • ExportMetadataAttribute メタデータを付けるには、名前の通りExportMetadataAttributeという属性を使います。使い方は簡単で、Exportしてるものにつけるだけです。メタデータには名前と値を渡す形で指定出来ます。
// Labelという名前であいさつマシーンというデータをつけてる
[Export]
[ExportMetadata("Label", "あいさつマシーン")]
public class Greeter
{
    public void Execute()
    {
        Console.WriteLine("こんにちは");
    }
}

// publicじゃないとダメ
public interface IGreeterMetadataView
{
    // メタデータと同じ名前、型の読み取り専用プロパティを定義する
    string Label { get; }
}

.....
// 現在のアセンブリからコンテナを作る
var c = new CompositionContainer(
    new AssemblyCatalog(Assembly.GetExecutingAssembly()));

// GetExportで取得する型とメタデータの型を定義する
Lazy<Greeter, IGreeterMetadataView> g = c.GetExport<Greeter, IGreeterMetadataView>();

// LazyのMetadataプロパティでメタデータを取得できる
Console.WriteLine(g.Metadata.Label);

// もちろんGreeterの処理も実行できる
g.Value.Execute();
  • ExportAttribute継承して拡張してしまう方法
// 挨拶インターフェース
interface IGreeter
{
    void Execute();
}

// メタデータのインターフェース
public interface IGreeterMetadataView
{
    string Label { get; }
}

// IGreeterでExportして追加でラベルというメタデータを指定するカスタム属性
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
class GreetreExportAttribute : ExportAttribute, IGreeterMetadataView
{
    public GreetreExportAttribute(string label)
        : base(typeof(IGreeter))
    {
        this.Label = label;
    }

    public string Label { get; private set; }
}

// 適当に3つほどIGreeterを実装してGreeterExport属性をつけておく
[GreetreExport("日本人")]
class JapaneseGreeter : IGreeter
{
    public void Execute()
    {
        Console.WriteLine("こんにちは");
    }
}

[GreetreExport("割と普通")]
class NormalianGreeter : IGreeter
{
    public void Execute()
    {
        Console.WriteLine("エロース、エロース");
    }
}

[GreetreExport("アメリカ人")]
class EnglishGreeter : IGreeter
{
    public void Execute()
    {
        Console.WriteLine("Hello.");
    }
}

..........

[Export]
class Client
{
    // 複数受け取る
    [ImportMany]
    public IEnumerable<Lazy<IGreeter, IGreeterMetadataView>> Greeters { get; set; }

    public void Run()
    {
        foreach (var g in this.Greeters)
        {
            // メタデータを表示してから挨拶をしてもらう
            Console.Write(g.Metadata.Label + " : ");
            g.Value.Execute();
        }
    }
}

結果:
日本人 : こんにちは
割と普通 : エロース、エロース
アメリカ人 : Hello.