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
导出方法和属性
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