C#

MEF 学习笔记

Posted by Kerwen Blog on July 17, 2018

MEF,全称Managed Extensibility Framework(托管可扩展框架). MEF是专门致力于解决扩展性问题的框架,MSDN中对MEF有这样一段说明:

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

导出类

MEF的使用范围广泛,在Winform、WPF、Win32、Silverlight中都可以使用, 下面先新建一个win32控制台项目MEFDemo,添加一个IBookService接口,写一个简单的Demo:
需要添加对System.ComponentModel.Composition命名空间的引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public interface IBookService  
{  
    string BookName { get; set; }  
    string GetBookName();  
}  
  
[Export("MusicBook", typeof(IBookService))]  
public class MusicBook : IBookService  
{  
    public string BookName { get; set; }  
  
    public string GetBookName()  
    {  
        return "MusicBook";  
    }  
}  
  
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();  
    }  
  
    private void Compose()  
    {  
        var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());  
        CompositionContainer container = new CompositionContainer(catalog);  
        container.ComposeParts(this);  
    }  
}   点击F5运行,结果如下:  
  
MusicBook  

可以看到调用了MusicBook类的GetBookName方法,但是我们并没有实例化MusicBook类. 这就是实现了主程序和类之间的解耦,大大提高了代码的扩展性和易维护性!

添加两个类,HistoryBook和MathBook,都继承自IBookService接口,注意他们的契约名都相同,都为Book, 注意Program class 中Import和Main函数的变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[Export("Book", typeof(IBookService))]  
public class MusicBook : IBookService  
{  
    public string BookName { get; set; }  
  
    public string GetBookName()  
    {  
        return "MusicBook";  
    }  
}  
  
[Export("Book", typeof(IBookService))]  
public class MathBook : IBookService  
{  
    public string BookName { get; set; }  
  
    public string GetBookName()  
    {  
        return "MathBook";  
    }  
}  
  
[Export("Book", typeof(IBookService))]  
public class HistoryBook : IBookService  
{  
    public string BookName { get; set; }  
  
    public string GetBookName()  
    {  
        return "HistoryBook";  
    }  
}  
  
class Program  
{  
    [ImportMany("Book")]  
    public IEnumerable<IBookService> Services { get; set; }  
  
    static void Main(string[] args)  
    {  
        Program pro = new Program();  
        pro.Compose();  
        if (pro.Services != null)  
        {  
            foreach (var s in pro.Services)  
            {  
                Console.WriteLine(s.GetBookName());  
            }  
        }  
        Console.Read();  
    }  
  
    private void Compose()  
    {  
        var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());  
        CompositionContainer container = new CompositionContainer(catalog);  
        container.ComposeParts(this);  
    }  
}  

[ImportMany(“MusicBook”)]还有下面的声明变成了IEnumerable<>,因为要导出多个实例,所以要用到集合
IEnumerable中的类型必须和类的导出类型匹配,如类上面标注的是[Exprot(typeof(object))],那么就必须声明为IEnumerable才能匹配到导出的类。

导出方法和属性

MEF 不仅可以导出类,还可以导出方法和属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
[Export("Book", typeof(IBookService))]  
public class MusicBook : IBookService  
{  
    //Export private property  
    [Export(typeof(string))]  
    private string _privateBookName = "Private Music BookName";  
  
    //Export public property  
    [Export(typeof(string))]  
    public string _publicBookName = "Public Music BookName";  
  
    public string BookName { get; set; }  
  
    public string GetBookName()  
    {  
        return "MusicBook";  
    }  
}  
  
class Program  
{  
    [ImportMany("Book")]  
    public IEnumerable<IBookService> Services { get; set; }  
  
    [ImportMany]  
    public List<string> InputString { get; set; }  
  
    static void Main(string[] args)  
    {  
        Program pro = new Program();  
        pro.Compose();  
        if (pro.Services != null)  
        {  
            foreach (var s in pro.Services)  
            {  
                Console.WriteLine(s.GetBookName());  
            }  
        }  
  
        foreach (var str in pro.InputString)  
        {  
            Console.WriteLine(str);  
        }  
  
        Console.Read();  
    }  
  
    private void Compose()  
    {  
        var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());  
        CompositionContainer container = new CompositionContainer(catalog);  
        container.ComposeParts(this);  
    }  
}  

导出方法,无论是公有方法还是私有方法都是可以导出的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
[Export("Book", typeof(IBookService))]  
public class MusicBook : IBookService  
{  
    //Export private property  
    [Export(typeof(string))]  
    private string _privateBookName = "Private Music BookName";  
  
    //Export public property  
    [Export(typeof(string))]  
    public string _publicBookName = "Public Music BookName";  
  
    public string BookName { get; set; }  
  
    //Exprot public method  
    [Export(typeof(Func<string>))]  
    public string GetBookName()  
    {  
        return "MusicBook";  
    }  
  
    //Export private method  
    [Export(typeof(Func<int, string>))]  
    private string GetBookPrice(int price)  
    {  
        return "$" + price;  
    }  
}  
  
class Program  
{  
    [ImportMany("Book")]  
    public IEnumerable<IBookService> Services { get; set; }  
  
    [ImportMany]  
    public List<string> InputString { get; set; }  
  
    //Import non-parameter method  
    [Import]  
    public Func<string> methodWithoutPara { get; set; }  
  
    //Import parameter method  
    [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 s in pro.Services)  
            {  
                Console.WriteLine(s.GetBookName());  
            }  
        }  
  
        foreach (var str in pro.InputString)  
        {  
            Console.WriteLine(str);  
        }  
          
        if (pro.methodWithoutPara != null)  
        {  
            Console.WriteLine(pro.methodWithoutPara());  
        }  
  
        if (pro.methodWithPara != null)  
        {  
            Console.WriteLine(pro.methodWithPara(3000));  
        }  
  
        Console.Read();  
    }  
  
    private void Compose()  
    {  
        var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());  
        CompositionContainer container = new CompositionContainer(catalog);  
        container.ComposeParts(this);  
    }  
}  

MEF魅力

实际开发中,我们往往要采用分层架构,就拿最简单的三层架构来说吧,我们通常把业务逻辑写在DLL中,现在就来写一个例子,看看如何在不编译整个项目的情况下,轻松的实现扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
namespace 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 double Money { get;set; }  
  
		public void CheckOutMoney(double money)  
		{  
			this.Money -= money;  
		}  
  
		public string GetCountInfo()  
		{  
			return "Bank Of China";  
		}  
  
		public void SaveMoney(double money)  
		{  
			this.Money += money;  
		}  
	}  
}  
  
// 主程序  
namespace MEFDemo  
{  
	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);  
		}  
	}  
}  

在MEFDemo.exe 所在文件夹创建Cards子文件夹, 将BankOfChina.dll 拷进去
如果现在需求改变了,需要支持建行、农行等银行卡,怎么办呢?通常我们要改项目,把整个项目都编译再重新发布。但是现在不需要这么做了,我们只需要添加一个类库项目,把生成的dll拷贝到Cards目录下即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace NongHang  
{  
	[Export(typeof(ICard))]  
	class NHCard : ICard  
	{  
		public double Money { get;set; }  
  
		public void CheckOutMoney(double money)  
		{  
			this.Money -= money;  
		}  
  
		public string GetCountInfo()  
		{  
			return "Nong Ye Yin Hang";  
		}  
  
		public void SaveMoney(double money)  
		{  
			this.Money += money;  
		}  
	}  
}  

MEF 进阶1

前面讲的导出都是在每个类上面添加Export注解,实现导出的,那么有没有一种比较简便的方法呢?答案是有的,就是在接口上面写注解,这样只要实现了这个接口的类都会导出,而不需要在每个类上面都写注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
namespace BankInterface  
{  
	[InheritedExport]  
	public interface ICard  
	{  
		double Money { get; set; }  
		string GetCountInfo();  
		void SaveMoney(double money);  
		void CheckOutMoney(double money);  
	}  
}  
  
namespace BankOfChina  
{  
	public class ZHCard : ICard  
	{  
		public double Money { get;set; }  
  
		public void CheckOutMoney(double money)  
		{  
			this.Money -= money;  
		}  
  
		public string GetCountInfo()  
		{  
			return "Bank Of China";  
		}  
  
		public void SaveMoney(double money)  
		{  
			this.Money += money;  
		}  
	}  
}  

这种方法虽然比较简单,但是只适用于比较简单的应用

MEF 进阶2

MEF中如何访问某个具体的对象

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace BankInterface  
{  
	[MetadataAttribute]  
	[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]  
	public class ExportCardAttribute : ExportAttribute  
	{  
		public ExportCardAttribute() : base(typeof(ICard))  
		{  
		}  
		public string CardType { get; set; }  
	}  
  
	public interface ICard  
	{  
		double Money { get; set; }  
		string GetCountInfo();  
		void SaveMoney(double money);  
		void CheckOutMoney(double money);  
	}  
  
	public interface IMetaData  
	{  
		string CardType { get; }  
	}      
}  

又添加了接口IMetaData,只有一个属性,注意这个属性要和刚写的ExportCardAttribute类中的属性名称要一致,这样才能实现导出。
下面利用我们的ExportCardAttribute属性来标记我们要导出的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace BankOfChina  
{  
	[ExportCardAttribute(CardType = "BankOfChina")]  
	public class ZHCard : ICard  
	{  
		public double Money { get;set; }  
  
		public void CheckOutMoney(double money)  
		{  
			this.Money -= money;  
		}  
  
		public string GetCountInfo()  
		{  
			return "Bank Of China";  
		}  
  
		public void SaveMoney(double money)  
		{  
			this.Money += money;  
		}  
	}  
}  

修改主程序的代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
namespace MEFDemo  
{  
	class Program  
	{  
		//其中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("Here is a card of Bank Of China ");  
					Console.WriteLine(c.Value.GetCountInfo());  
				}  
				if (c.Metadata.CardType == "NongHang")  
				{  
					Console.WriteLine("Here is a card of Nong Ye Yin Hang ");  
					Console.WriteLine(c.Value.GetCountInfo());  
				}  
			}  
			Console.Read();  
		}  
  
		private void Compose()  
		{  
			var catalog = new DirectoryCatalog("Cards");  
			var container = new CompositionContainer(catalog);  
			container.ComposeParts(this);  
		}  
	}  
}  

这里我用到了Lazy延迟加载机制,可以看到我们可以根据MetaData的属性访问到CardType属性,从而判断出Card的类型,从而区分导入的类型。

C#可扩展编程之MEF学习笔记(一):MEF简介及简单的Demo
C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import)
C#可扩展编程之MEF学习笔记(三):导出类的方法和属性
C#可扩展编程之MEF学习笔记(四):见证奇迹的时刻
C#可扩展编程之MEF学习笔记(五):MEF高级进阶
Windows Forms Modular App Using MEF