使用 MEF 完全延迟 DLL 加载的最简单方法






4.87/5 (31投票s)
使用 MEF 完全延迟加载 DLL 的(通用)最简单方法。
- 下载 MEF_Example_-_Using_Interfaces-noexe.zip - 38.6 KB
- 下载 MEF_Example_-_Using_Directories-noexe.zip - 38.6 KB
- 下载 MEF_Example_-_Simple-noexe.zip - 46.9 KB
引言
本文提供了一种使用 MEF 的简单通用方法
- 使用 MEF 的(通用)最简单方法。
- 如何使用 MEF 完全延迟加载 DLL。
背景
MEF 代表“Managed Extensibility Framework”(托管可扩展性框架) - 托管可扩展性框架 (MEF) 是一个用于创建轻量级、可扩展应用程序的库。 它允许应用程序开发人员发现和使用扩展,而无需任何配置。 它还允许扩展开发人员轻松封装代码并避免脆弱的硬依赖关系。 MEF 不仅允许在应用程序中重用扩展,还允许跨应用程序重用扩展。 请参阅 MSDN 链接。
有很多关于 MEF 的文章,尽管我从架构师/开发经理/同事讲师/学生那里收到了很多关于该主题的问题,关于如何简单且通用地使用 MEF 而不同时加载所有 dll。
MEF 加载器通用逻辑
以通用的方式将 MEF 用作黑盒子的最简单方法是使用以下代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
namespace MEF_Example.MEF
{
/// <summary>
/// MEFLoader class that responsible for :
/// The interface for all the MEF loading process, i.e. he is the black-box.
/// holding all the already imported object (for better performance)
/// holding all the already exist importers (one for each type)
/// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
/// </summary>
public class MEFLoader
{
Dictionary<string, List<object>> importers = new Dictionary<string, List<object>>();
public virtual ICollection<T> LoadByTag<T>(string path, string tag)
{
var importer = GetImporter<T>(path);
return importer.LoadByMEF(path, tag);
}
protected MEFImporter<T> GetImporter<T>(string path)
{
var importerList = GetImporterList(path);
var importer = importerList.OfType<MEFImporter<T>>().FirstOrDefault();
if (importer == null)
{
importer = new MEFImporter<T>(path);
importerList.Add(importer);
//Write to Log:
//UILogService.Instance.WriteToLog(E_ErrorSeverity.e_Information, "New MEFImporter was created for Path & Type" + path + importer.GetType().ToString());
}
return importer;
}
protected List<object> GetImporterList(string path)
{
if (importers.ContainsKey(path) == false)
importers.Add(path, new List<object>());
return importers[path];
}
public virtual ICollection<T> LoadByType<T>(string path)
{
return LoadByTag<T>(path, String.Empty);
}
}
/// <summary>
/// The imported objects metadata interface. i.e. the set of
/// properties we can filter by all the already imported objects.
/// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
/// </summary>
public interface IMetadata
{
string Name { get; }
}
/// <summary>
/// Generic Class is responsible for MEF Import of certain type T.
/// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
/// </summary>
public class MEFImporter<T>
{
[ImportMany(AllowRecomposition = true)]
public IEnumerable<Lazy<T, IMetadata>> imports { get; set; }
MEFImporter()
{
}
public MEFImporter(string path)
: this()
{
directoryCatalog = new DirectoryCatalog(path);
}
protected DirectoryCatalog directoryCatalog = null;
protected void DoImport(string path)
{
//An aggregate catalog that combines multiple catalogs
var catalog = new AggregateCatalog();
//Adds all the parts found in all assemblies in
//the same directory as the executing program
catalog.Catalogs.Add(directoryCatalog);
//Create the CompositionContainer with the parts in the catalog
CompositionContainer container = new CompositionContainer(catalog);
//Fill the imports of this object
container.ComposeParts(this);
}
public ICollection<T> LoadByMEF(string path, string name)
{
var res = new List<T>();
DoImport(path);
//Test MEF
//AppDomain MyDomain = AppDomain.CurrentDomain;
//var AssembliesLoaded = MyDomain.GetAssemblies();
foreach (Lazy<T, IMetadata> module in imports)
{
if (module.Metadata.Name == name || String.IsNullOrEmpty(name))
{
res.Add(module.Value); //Will create an instance
}
}
return res;
}
}
}
- MEFImporter Generic Class 负责某种类型 T 的 MEF 导入
- IMetadata 接口是导入对象的元数据接口。 即,我们可以通过所有已导入的对象进行筛选的属性集。
- 最后,MEFLoader 类负责
- 所有 MEF 加载过程的接口,即,他是黑盒子。
- 保存所有已导入的对象(以获得更好的性能)
- 保存所有已存在的导入器(每种类型一个)
问题是:MEF 加载“路径”文件夹中的所有 DLL!
注意:通过使用 Lazy<>,至少他不会创建所有符合我们要求的对象。
使用代码
问:如何使用?
答:示例
示例 (WPF) 应用程序的基本思想是
3 个按钮 -> 单击 -> 加载与该按钮匹配的自定义控件 -> 通过 MEF 加载自定义控件
实现,MainWindow.xaml
<Window x:Class="MEF_Example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Top" >
<Button Content="Present Blue Control" Background="Blue" Click="Button_Click" Tag="1"/>
<Button Content="Present Green Control" Background="Green" Click="Button_Click" Tag="2"/>
<Button Content="Present Yellow Control" Background="Yellow" Click="Button_Click" Tag="3"/>
</StackPanel>
<ContentPresenter x:Name="Worksapce" />
</DockPanel>
</Grid>
</Window>
代码隐藏,MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ControlInterface;
namespace MEF_Example
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MEF.MEFLoader m_Loader = new MEF.MEFLoader();
string GetPathByName(string name)
{
var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";
return res;
}
UserControl GetControlByName<T>(string name)
{
UserControl res = null;
res = m_Loader.LoadByTag<T>(GetPathByName(name), name).FirstOrDefault() as UserControl;
return res;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var btn = (e.OriginalSource as Button);
if (btn == null && btn.Tag != null)
return;
int number = 0;
Int32.TryParse(btn.Tag as string,out number);
switch (number)
{
case 1:
//Present Blue Control (dll: ControlsLibrary1.dll):
this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary1.BlueControl");
break;
case 2:
//Present Green Control (dll: ControlsLibrary1.dll):
this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary1.GreenControl");
break;
case 3:
//Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
this.Worksapce.Content = GetControlByName<IControl>("ControlsLibrary2.YellowControl");
break;
}
}
}
}
UserControl 类 (ControlsLibrary1.dll)
BlueControl.xaml
<UserControl x:Class="ControlsLibrary1.BlueControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Blue">
</Grid>
</UserControl>
BlueControl.xaml.cs
namespace ControlsLibrary1
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
[Export(typeof(IControl))]
[ExportMetadata("Name", "ControlsLibrary1.BlueControl")]
public partial class BlueControl : UserControl, IControl
{
public BlueControl()
{
InitializeComponent();
}
}
}
GreenControl.xaml
<UserControl x:Class="ControlsLibrary1.GreenControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Green">
</Grid>
</UserControl>
GreenControl.xaml.cs
namespace ControlsLibrary1
{
/// <summary>
/// Interaction logic for GreenControl.xaml
/// </summary>
[Export(typeof(IControl))]
[ExportMetadata("Name", "ControlsLibrary1.GreenControl")]
public partial class GreenControl : UserControl, IControl
{
public GreenControl()
{
InitializeComponent();
}
}
}
UserControl 类 (ControlsLibrary2.dll)
YellowControl.xaml
<UserControl x:Class="ControlLibrary2.YellowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Background="Yellow">
</Grid>
</UserControl>
YellowControl.xaml.cs
namespace ControlLibrary2
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
[Export(typeof(IControl))]
[ExportMetadata("Name", "ControlsLibrary2.YellowControl")]
public partial class YellowControl : UserControl, IControl
{
public YellowControl()
{
InitializeComponent();
}
}
}
您也可以在此处下载代码
1. 如何使用 MEF 完全延迟加载 DLL?
问:如果我们希望 MEF 也完全延迟加载 DLL,我们可以做什么?
问题是:MEF 加载“路径”文件夹中的所有 Dll!
在我们的示例中,加载 Yellow Control 时,MEF 机制加载了 3 个对象
答:解决方案 1:使用子目录
让我们将 ControlsLibrary1.dll 和 ControlsLibrary2.dll 编译到“Controls”目录的子目录中
并将 MainWindow 代码更改为
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ControlInterface;
namespace MEF_Example
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MEF.MEFLoader m_Loader = new MEF.MEFLoader();
string GetPathByName(string name)
{
var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";
var familyname = name.Split(new[] { '.' })[0];
return res + familyname;
}
UserControl GetControlByName(string name)
{
UserControl res = null;
res = m_Loader.LoadByTag<IControl>(GetPathByName(name), name).FirstOrDefault() as UserControl;
return res;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var btn = (e.OriginalSource as Button);
if (btn == null && btn.Tag != null)
return;
int number = 0;
Int32.TryParse(btn.Tag as string,out number);
switch (number)
{
case 1:
//Present Blue Control (dll: ControlsLibrary1.dll):
this.Worksapce.Content = GetControlByName("ControlsLibrary1.BlueControl");
break;
case 2:
//Present Green Control (dll: ControlsLibrary1.dll):
this.Worksapce.Content = GetControlByName("ControlsLibrary1.GreenControl");
break;
case 3:
//Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
this.Worksapce.Content = GetControlByName("ControlsLibrary2.YellowControl");
break;
}
}
}
}
现在加载 Yellow 控件时,只有 1 个对象
您也可以在此处下载代码
答:解决方案 2:使用更多导出接口
将每个 dll 添加到他自己的继承接口
namespace ControlInterface
{
public interface IControl
{
}
public interface IControlLibrary1 : IControl
{
}
public interface IControlLibrary2 : IControl
{
}
}
更改 UserControl 类的导出
BlueControl.xaml.cs (ControlsLibrary1.dll)
namespace ControlsLibrary1
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
[Export(typeof(IControlLibrary1))]
[ExportMetadata("Name", "ControlsLibrary1.BlueControl")]
public partial class BlueControl : UserControl, IControlLibrary1
{
public BlueControl()
{
InitializeComponent();
}
}
}
GreenControl.xaml.cs (ControlsLibrary1.dll)
namespace ControlsLibrary1
{
/// <summary>
/// Interaction logic for GreenControl.xaml
/// </summary>
[Export(typeof(IControlLibrary1))]
[ExportMetadata("Name", "ControlsLibrary1.GreenControl")]
public partial class GreenControl : UserControl, IControlLibrary1
{
public GreenControl()
{
InitializeComponent();
}
}
}
YellowControl.xaml.cs (ControlsLibrary2.dll)
namespace ControlLibrary2
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
[Export(typeof(IControlLibrary2))]
[ExportMetadata("Name", "ControlsLibrary2.YellowControl")]
public partial class YellowControl : UserControl, IControlLibrary2
{
public YellowControl()
{
InitializeComponent();
}
}
}
通过接口加载 (MainWindow.xaml.cs)
namespace MEF_Example
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// Written by Shai Vashdi - Shai.Vashdi.Net@gmail.com - All rights reserved.
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MEF.MEFLoader m_Loader = new MEF.MEFLoader();
string GetPathByName(string name)
{
var res = AppDomain.CurrentDomain.BaseDirectory + @"Controls\";
return res;
}
UserControl GetControlByName<T>(string name)
{
UserControl res = null;
res = m_Loader.LoadByTag<T>(GetPathByName(name), name).FirstOrDefault() as UserControl;
return res;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var btn = (e.OriginalSource as Button);
if (btn == null && btn.Tag != null)
return;
int number = 0;
Int32.TryParse(btn.Tag as string,out number);
switch (number)
{
case 1:
//Present Blue Control (dll: ControlsLibrary1.dll):
this.Worksapce.Content = GetControlByName<IControlLibrary1>("ControlsLibrary1.BlueControl");
break;
case 2:
//Present Green Control (dll: ControlsLibrary1.dll):
this.Worksapce.Content = GetControlByName<IControlLibrary1>("ControlsLibrary1.GreenControl");
break;
case 3:
//Present Yellow Control (diffrent dll: ControlsLibrary2.dll):
this.Worksapce.Content = GetControlByName<IControlLibrary2>("ControlsLibrary2.YellowControl");
break;
}
}
}
}
现在加载 Yellow 控件时,只有 1 个对象
您也可以在此处下载代码