Managed Extensibility Framework (MEF)Managed Extensibility Framework (MEF)

该话题为在 .NET Framework 4 中引入的 Managed Extensibility Framework 提供了一个概述。This topic provides an overview of the Managed Extensibility Framework that was introduced in the .NET Framework 4.

什么是 MEF?What is MEF?

Managed Extensibility Framework (MEF) 是用于创建可扩展的轻量级应用程序的库。The Managed Extensibility Framework or MEF is a library for creating lightweight, and extensible applications. 它让应用程序开发人员得以发现和使用扩展且无需配置。It allows application developers to discover and use extensions with no configuration required. 它还让扩展开发人员得以轻松地封装代码并避免脆弱的紧密依赖性。It also lets extension developers easily encapsulate code and avoid fragile hard dependencies. MEF 让扩展不仅可在应用程序内重复使用,还可以跨程序重复使用。MEF not only allows extensions to be reused within applications, but across applications as well.

扩展性问题The problem of extensibility

想象你是必须为扩展性提供支持的大型应用程序的设计者。Imagine that you are the architect of a large application that must provide support for extensibility. 你的应用程序必须包含可能很多的较小组件,且其负责创建和运行较小组件。Your application has to include a potentially large number of smaller components, and is responsible for creating and running them.

解决问题最简单的方法就是将组件作为源代码包括在你的应用程序并直接从代码中调用它们。The simplest approach to the problem is to include the components as source code in your application, and call them directly from your code. 这有很多明显缺点。This has a number of obvious drawbacks. 最重要的是,你无法在未调试源代码的情况下添加新组件,这是一项可能被接受的限制(例如在 Web 应用程序中),但不适用于客户端应用程序。Most importantly, you cannot add new components without modifying the source code, a restriction that might be acceptable in, for example, a Web application, but is unworkable in a client application. 同样造成问题的是,你无法访问组件的源代码,因为组件可能由第三方进行了发展,同时由于同样的原因你不能允许第三方访问你的源代码。Equally problematic, you may not have access to the source code for the components, because they might be developed by third parties, and for the same reason you cannot allow them to access yours.

一个稍许复杂的方法为提供扩展点或接口来允许应用程序和组件间的分离。A slightly more sophisticated approach would be to provide an extension point or interface, to permit decoupling between the application and its components. 在此模块下,你可能提供组件可实现的接口以及使接口能与应用程序交互使用的 API。Under this model, you might provide an interface that a component can implement, and an API to enable it to interact with your application. 这解决了要求源代码访问的问题,但它仍然存在自身问题。This solves the problem of requiring source code access, but it still has its own difficulties.

因为应用程序缺少自行发现组件的能力,所以必须明确对其指出可用且应加载的组件。Because the application lacks any capacity for discovering components on its own, it must still be explicitly told which components are available and should be loaded. 这通常通过在配置文件中明确记录可用组件来完成。This is typically accomplished by explicitly registering the available components in a configuration file. 这意味着确保组件正确变成了维护问题,尤其当要求执行更新的人员是最终用户而非开发人员时。This means that assuring that the components are correct becomes a maintenance issue, particularly if it is the end user and not the developer who is expected to do the updating.

此外,组件能够与另外的组件进行沟通,除了通过应用程序自身的严格定义的通道。In addition, components are incapable of communicating with one another, except through the rigidly defined channels of the application itself. 通常不会出现应用程序设计者未预测到特定通信的需求的情况。If the application architect has not anticipated the need for a particular communication, it is usually impossible.

最终,组件开发人员必须接受包含他们实现的接口的程序集所在的硬依赖项。Finally, the component developers must accept a hard dependency on what assembly contains the interface they implement. 这使得在一个以上的应用程序中使用组件变得困难,且它还会在你创建组件的测试框架时造成问题。This makes it difficult for a component to be used in more than one application, and can also create problems when you create a test framework for components.

MEF 所含内容What MEF provides

MEF 通过组合 提供了一种隐式发现它们的方法,而不是明确记录可用组件。Instead of this explicit registration of available components, MEF provides a way to discover them implicitly, via composition. MEF 组件(称为一个部件 ),以声明方式详细说明了其依赖项(称为导入 )及其可提供的功能(称为导出 )。A MEF component, called a part, declaratively specifies both its dependencies (known as imports) and what capabilities (known as exports) it makes available. 当创建一个部分时,MEF 组合引擎利用从其他部分获得的功能满足其导入需要。When a part is created, the MEF composition engine satisfies its imports with what is available from other parts.

该方法解决了在之前部分中讨论的问题。This approach solves the problems discussed in the previous section. 因为 MEF 部分以声明方式详细说明了其可在运行时发现自身的功能,这意味着应用程序不使用硬编码引用或脆弱的配置文件也能够使用 MEF 部分。Because MEF parts declaratively specify their capabilities, they are discoverable at runtime, which means an application can make use of parts without either hard-coded references or fragile configuration files. MEF 让应用程序得以通过元数据发现和检查部分,而无须将部分实例化甚至于加载其程序集。MEF allows applications to discover and examine parts by their metadata, without instantiating them or even loading their assemblies. 因此,无须仔细说明应当何时加载扩展以及如何加载。As a result, there is no need to carefully specify when and how extensions should be loaded.

除了它提供的导出之外,部件能够指出其导入(将由其他部件填写)。In addition to its provided exports, a part can specify its imports, which will be filled by other parts. 这不仅使部件之间的通信成为可能还使其变得简单,并且实现了良好的代码分解。This makes communication among parts not only possible, but easy, and allows for good factoring of code. 例如,对于许多组件都很普通的服务可被分解为单独部分,可轻易地进行调试和替换。For example, services common to many components can be factored into a separate part and easily modified or replaced.

因为 MEF 模块在特定的应用程序集上没有硬依赖项,所以它允许扩展在不同应用程序之间重复使用。Because the MEF model requires no hard dependency on a particular application assembly, it allows extensions to be reused from application to application. 这使得开发用于测试扩展组件的测试工具(独立于应用程序)变得简单。This also makes it easy to develop a test harness, independent of the application, to test extension components.

通过使用 MEF 编写的可扩展应用程序声明了一个可由扩展组件填写的输入,它还可能为了向扩展揭示应用程序服务而声明导出。An extensible application written by using MEF declares an import that can be filled by extension components, and may also declare exports in order to expose application services to extensions. 每个扩展组件都声明一个导出,还有可能声明导入。Each extension component declares an export, and may also declare imports. 通过此方法,扩展组件自身为自动可扩展。In this way, extension components themselves are automatically extensible.

MEF 适用场景Where MEF is available

MEF 是 .NET Framework 4 的组成部分,适用于所有使用 .NET Framework 的地方。MEF is an integral part of the .NET Framework 4, and is available wherever the .NET Framework is used. 可以在客户端应用程序(不论其是否使用 Windows 窗体或其他技术)或使用 ASP.NET 的服务端应用程序里使用 MEF。You can use MEF in your client applications, whether they use Windows Forms, WPF, or any other technology, or in server applications that use ASP.NET.

MEF 和 MAFMEF and MAF

.NET Framework 的早期版本引入了 Managed Add-in Framework (MAF),旨在让应用程序隔离和托管扩展。Previous versions of the .NET Framework introduced the Managed Add-in Framework (MAF), designed to allow applications to isolate and manage extensions. MAF 与 MEF 相比,MAF 的重点级别较高,它关注扩展隔离和程序集加载及卸载,而 MEF 则关注可发现性、可扩展性和可移植性。The focus of MAF is slightly higher-level than MEF, concentrating on extension isolation and assembly loading and unloading, while MEF's focus is on discoverability, extensibility, and portability. 这两个框架可以平稳地相互操作,且一个单独的应用程序可以利用这两者。The two frameworks interoperate smoothly, and a single application can take advantage of both.

简单计算器:一个示例应用程序SimpleCalculator: An example application

查看 MEF 能做什么最简单的方法就是构建一个简单的 MEF 应用程序。The simplest way to see what MEF can do is to build a simple MEF application. 在此示例中,你构建了一个叫作简单计算器的非常简单的计算器。In this example, you build a very simple calculator named SimpleCalculator. 简单计算器旨在创建一个接受形式为“5+3”或“6-2”的基础运算命令然后返回正确答案的控制台应用程序。The goal of SimpleCalculator is to create a console application that accepts basic arithmetic commands, in the form "5+3" or "6-2", and returns the correct answers. 使用 MEF,能够添加新的操作人员而无须更改应用程序代码。Using MEF, you'll be able to add new operators without changing the application code.

要下载此示例的完整代码,请参阅 SimpleCalculator 示例 (Visual Basic)To download the complete code for this example, see the SimpleCalculator sample (Visual Basic).

备注

简单计算器旨在演示 MEF 的概念和语法而非必须为其使用提供现实情况。The purpose of SimpleCalculator is to demonstrate the concepts and syntax of MEF, rather than to necessarily provide a realistic scenario for its use. 许多将从 MEF 的功能受益最大的应用程序比简单计算器更加复杂。Many of the applications that would benefit most from the power of MEF are more complex than SimpleCalculator. 有关更多扩展性示例,请参阅 GitHub 上的 Managed Extensibility FrameworkFor more extensive examples, see the Managed Extensibility Framework on GitHub.

  • 首先,在 Visual Studio 中,创建一个新的控制台应用程序项目,并将其命名为 SimpleCalculatorTo start, in Visual Studio, create a new Console Application project and name it SimpleCalculator.

  • 添加对 MEF 所驻留的 System.ComponentModel.Composition 程序集的引用。Add a reference to the System.ComponentModel.Composition assembly, where MEF resides.

  • 打开 Module1.vb 或 Program.cs,再为 System.ComponentModel.CompositionSystem.ComponentModel.Composition.Hosting 添加 Importsusing 语句 。Open Module1.vb or Program.cs and add Imports or using statements for System.ComponentModel.Composition and System.ComponentModel.Composition.Hosting. 这两个名称空间包含你开发可扩展应用程序将需要的 MEF 类型。These two namespaces contain MEF types you will need to develop an extensible application.

  • 如果使用的是 Visual Basic,则将 Public 关键字添加到声明 Module1 模块的行中。If you're using Visual Basic, add the Public keyword to the line that declares the Module1 module.

撰写容器和目录Composition container and catalogs

MEF 组合模型的核心是包含所有可用部件并执行组合的组合容器 。The core of the MEF composition model is the composition container, which contains all the parts available and performs composition. 组合是对导入到导出进行的匹配。Composition is the matching up of imports to exports. 撰写容器最常用的类型是 CompositionContainer,它将用于简单计算器。The most common type of composition container is CompositionContainer, and you'll use this for SimpleCalculator.

如果使用的是 Visual Basic,则在 Module1.vb 中添加一个名为 Program 的公共类 。If you're using Visual Basic, add a public class named Program in Module1.vb.

将以下行添加到 Module1.vb 或 Program.cs 中的 Program 类 :Add the following line to the Program class in Module1.vb or Program.cs:

Dim _container As CompositionContainer
private CompositionContainer _container;

为了发现对其可用的部件,组合容器使用目录 。In order to discover the parts available to it, the composition containers makes use of a catalog. 目录是让你从某些来源中发现可用部件的对象。A catalog is an object that makes available parts discovered from some source. MEF 提供目录来发现来自提供的类型、程序集或目录的部件。MEF provides catalogs to discover parts from a provided type, an assembly, or a directory. 应用程序开发人员可以轻松地创建新的目录来发现来自其他来源(例如 Web 服务)的部件。Application developers can easily create new catalogs to discover parts from other sources, such as a Web service.

将下面的构造函数添加到 Program 类中:Add the following constructor to the Program class:

Public Sub New()
    ' An aggregate catalog that combines multiple catalogs.
     Dim catalog = New AggregateCatalog()

    ' Adds all the parts found in the same assembly as the Program class.
    catalog.Catalogs.Add(New AssemblyCatalog(GetType(Program).Assembly))

    ' Create the CompositionContainer with the parts in the catalog.
    _container = New CompositionContainer(catalog)

    ' Fill the imports of this object.
    Try
        _container.ComposeParts(Me)
    Catch ex As CompositionException
        Console.WriteLine(ex.ToString)
    End Try
End Sub
private Program()
{
    // An aggregate catalog that combines multiple catalogs.
    var catalog = new AggregateCatalog();
    // Adds all the parts found in the same assembly as the Program class.
    catalog.Catalogs.Add(new AssemblyCatalog(typeof(Program).Assembly));

    // Create the CompositionContainer with the parts in the catalog.
    _container = new CompositionContainer(catalog);

    // Fill the imports of this object.
    try
    {
        this._container.ComposeParts(this);
    }
    catch (CompositionException compositionException)
    {
        Console.WriteLine(compositionException.ToString());
   }
}

ComposeParts 的调用通知撰写容器撰写一组特定组件,在此情况下即为 Program 的当前实例。The call to ComposeParts tells the composition container to compose a specific set of parts, in this case the current instance of Program. 但此时不会发生任何情况,因为 Program 没有需要填充的导入。At this point, however, nothing will happen, since Program has no imports to fill.

具有属性的导入和导出Imports and Exports with attributes

首先,你已向 Program 导入一个计算器。First, you have Program import a calculator. 这使得用户界面顾虑(例如将转到 Program 的控制台输入和导出)从计算机逻辑中隔离出来。This allows the separation of user interface concerns, such as the console input and output that will go into Program, from the logic of the calculator.

将下面的代码添加到 Program 类中:Add the following code to the Program class:

<Import(GetType(ICalculator))>
Public Property calculator As ICalculator
[Import(typeof(ICalculator))]
public ICalculator calculator;

请注意,calculator 对象的声明并非异常,但它使用了 ImportAttribute 属性进行修饰。Notice that the declaration of the calculator object is not unusual, but that it is decorated with the ImportAttribute attribute. 属性声明了某些操作为导入;在撰写对象时,它将由组合引擎进行填写。This attribute declares something to be an import; that is, it will be filled by the composition engine when the object is composed.

每个导入都有一个决定其与什么导出相匹配的协定 。Every import has a contract, which determines what exports it will be matched with. 协定可以是显示指定的字符串,还可由 MEF 在给定类型中自动产生,在此情况下即为界面 ICalculatorThe contract can be an explicitly specified string, or it can be automatically generated by MEF from a given type, in this case the interface ICalculator. 任一使用匹配协定进行声明的导出将完成此导入。Any export declared with a matching contract will fulfill this import. 请注意,尽管 calculator 对象的类型实际上为 ICalculator,但对此并无要求。Note that while the type of the calculator object is in fact ICalculator, this is not required. 协定独立于导入对象的类型。The contract is independent from the type of the importing object. (在此情况下,可以略去 typeof(ICalculator)(In this case, you could leave out the typeof(ICalculator). 除非你明确指定,否则 MEF 会自动假定协定基于导入的类型。)MEF will automatically assume the contract to be based on the type of the import unless you specify it explicitly.)

将此非常简单的界面添加到模块或 SimpleCalculator 名称空间中:Add this very simple interface to the module or SimpleCalculator namespace:

Public Interface ICalculator
    Function Calculate(input As String) As String
End Interface
public interface ICalculator
{
    String Calculate(String input);
}

既然你已经定义了 ICalculator,则需要一个执行它的类。Now that you have defined ICalculator, you need a class that implements it. 将下面的类添加到模块或 SimpleCalculator 命名空间中:Add the following class to the module or SimpleCalculator namespace:

<Export(GetType(ICalculator))>
Public Class MySimpleCalculator
   Implements ICalculator

End Class
[Export(typeof(ICalculator))]
class MySimpleCalculator : ICalculator
{

}

下面是将与 Program 中的导入相匹配的导出。Here is the export that will match the import in Program. 为了使导出与导入相匹配,导出必须使用相同的协定。In order for the export to match the import, the export must have the same contract. 在基于 typeof(MySimpleCalculator) 的协定下导出将生成不匹配,也不会填写导入,因此协定需要完全匹配。Exporting under a contract based on typeof(MySimpleCalculator) would produce a mismatch, and the import would not be filled; the contract needs to match exactly.

鉴于此程序集中的所有可用部件将填充撰写容器,MySimpleCalculator 部件将可使用。Since the composition container will be populated with all the parts available in this assembly, the MySimpleCalculator part will be available. 当用于 Program 的构造函数在 Program 上执行组合时,MySimpleCalculator 对象(将处于此目的进行创建)将填写其导入。When the constructor for Program performs composition on the Program object, its import will be filled with a MySimpleCalculator object, which will be created for that purpose.

用户界面层(Program)无需了解其他内容。The user interface layer (Program) does not need to know anything else. 因此,可以填写 Main 方法中的用户界面逻辑的剩余部分。You can therefore fill in the rest of the user interface logic in the Main method.

将以下代码添加到 Main 方法中:Add the following code to the Main method:

Sub Main()
    ' Composition is performed in the constructor.
    Dim p As New Program()
    Dim s As String
    Console.WriteLine("Enter Command:")
    While (True)
        s = Console.ReadLine()
        Console.WriteLine(p.calculator.Calculate(s))
    End While
End Sub
static void Main(string[] args)
{
    // Composition is performed in the constructor.
    var p = new Program();
    string s;
    Console.WriteLine("Enter Command:");
    while (true)
    {
        s = Console.ReadLine();
        Console.WriteLine(p.calculator.Calculate(s));
    }
}

该代码仅读出一行导入并在结尾调用 CalculateICalculator 函数(由代码写回控制台)。This code simply reads a line of input and calls the Calculate function of ICalculator on the result, which it writes back to the console. 这是你在 Program 中所需的全部代码。That is all the code you need in Program. 剩余工作将在部件中进行。All the rest of the work will happen in the parts.

进一步的导入和 ImportManyFurther Imports and ImportMany

为了使简单计算器获得可扩性,它需要导入一组操作。In order for SimpleCalculator to be extensible, it needs to import a list of operations. 一般 ImportAttribute 属性由且只能由 ExportAttribute 填写。An ordinary ImportAttribute attribute is filled by one and only one ExportAttribute. 如果可用数目超过一,则组合引擎生成错误。If more than one is available, the composition engine produces an error. 可以使用 ImportManyAttribute 属性来创建可由任意数目的导出填写的导入。To create an import that can be filled by any number of exports, you can use the ImportManyAttribute attribute.

将下列操作属性添加到 MySimpleCalculator 类:Add the following operations property to the MySimpleCalculator class:

<ImportMany()>
Public Property operations As IEnumerable(Of Lazy(Of IOperation, IOperationData))
[ImportMany]
IEnumerable<Lazy<IOperation, IOperationData>> operations;

Lazy<T,TMetadata> 是由 MEF 提供来保存对导出的间接引用的类型。Lazy<T,TMetadata> is a type provided by MEF to hold indirect references to exports. 除了导出对象本身,还可以获取导出元数据 ,或描述导出对象的信息。Here, in addition to the exported object itself, you also get export metadata, or information that describes the exported object. 每个 Lazy<T,TMetadata> 都包含一个代表实际操作的 IOperation 对象和一个代表元数据的 IOperationData 对象。Each Lazy<T,TMetadata> contains an IOperation object, representing an actual operation, and an IOperationData object, representing its metadata.

将下列简单界面添加到模块或 SimpleCalculator 名称空间中:Add the following simple interfaces to the module or SimpleCalculator namespace:

Public Interface IOperation
    Function Operate(left As Integer, right As Integer) As Integer
End Interface

Public Interface IOperationData
    ReadOnly Property Symbol As Char
End Interface
public interface IOperation
{
     int Operate(int left, int right);
}

public interface IOperationData
{
    Char Symbol { get; }
}

在此情况下,每项操作的元数据是表示该操作的符号,例如 +、-、*。In this case, the metadata for each operation is the symbol that represents that operation, such as +, -, *, and so on. 为了运行加法操作,将下面的类添加到模块或 SimpleCalculator 名称空间中:To make the addition operation available, add the following class to the module or SimpleCalculator namespace:

<Export(GetType(IOperation))>
<ExportMetadata("Symbol", "+"c)>
Public Class Add
    Implements IOperation

    Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
        Return left + right
    End Function
End Class
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '+')]
class Add: IOperation
{
    public int Operate(int left, int right)
    {
        return left + right;
    }
}

ExportAttribute 属性函数和之前一致。The ExportAttribute attribute functions as it did before. ExportMetadataAttribute 属性将采用名称值对形式的元数据附加到导出。The ExportMetadataAttribute attribute attaches metadata, in the form of a name-value pair, to that export. 尽管 Add 类执行 IOperation,但执行 IOperationData 的类并无明确定义。While the Add class implements IOperation, a class that implements IOperationData is not explicitly defined. 相反,由 MEF 隐式创建的类具有基于提供的元数据名称的属性。Instead, a class is implicitly created by MEF with properties based on the names of the metadata provided. (这是用于访问 MEF 中的元数据的方法之一。)(This is one of several ways to access metadata in MEF.)

MEF 中的组合是递归的 。Composition in MEF is recursive. 你明确撰写了 Program 对象(导入了结果为 ICalculator 类型的 MySimpleCalculator)。You explicitly composed the Program object, which imported an ICalculator that turned out to be of type MySimpleCalculator. 反过来,MySimpleCalculator 导入一组 IOperation 对象,且该导入将在创建 MySimpleCalculator 时进行填写,同时进行 Program 的导入。MySimpleCalculator, in turn, imports a collection of IOperation objects, and that import will be filled when MySimpleCalculator is created, at the same time as the imports of Program. 如果 Add 类声明了一项进一步的导入,则该导入也须进行填写,等等。If the Add class declared a further import, that too would have to be filled, and so on. 任何将空缺结果留在撰写错误中的导入。Any import left unfilled results in a composition error. (然而,有可能声明导入为可选的或为其分配默认值。)(It is possible, however, to declare imports to be optional or to assign them default values.)

计算器逻辑Calculator Logic

准备好这些部件,剩下的就是计算器逻辑本身了。With these parts in place, all that remains is the calculator logic itself. 将下面的代码添加到 MySimpleCalculator 类来执行 Calculate 方法中:Add the following code in the MySimpleCalculator class to implement the Calculate method:

Public Function Calculate(input As String) As String Implements ICalculator.Calculate
    Dim left, right As Integer
    Dim operation As Char
    ' Finds the operator.
    Dim fn = FindFirstNonDigit(input)
    If fn < 0 Then
        Return "Could not parse command."
    End If
    operation = input(fn)
    Try
        ' Separate out the operands.
        left = Integer.Parse(input.Substring(0, fn))
        right = Integer.Parse(input.Substring(fn + 1))
    Catch ex As Exception
        Return "Could not parse command."
    End Try
    For Each i As Lazy(Of IOperation, IOperationData) In operations
        If i.Metadata.symbol = operation Then
            Return i.Value.Operate(left, right).ToString()
        End If
    Next
    Return "Operation not found!"
End Function
public String Calculate(string input)
{
    int left;
    int right;
    char operation;
    // Finds the operator.
    int fn = FindFirstNonDigit(input); 
    if (fn < 0) return "Could not parse command.";

    try
    {
        // Separate out the operands.
        left = int.Parse(input.Substring(0, fn));
        right = int.Parse(input.Substring(fn + 1));
    }
    catch
    {
        return "Could not parse command.";
    }

    operation = input[fn];

    foreach (Lazy<IOperation, IOperationData> i in operations)
    {
        if (i.Metadata.Symbol.Equals(operation)) return i.Value.Operate(left, right).ToString();
    }
    return "Operation Not Found!";
}

初始步骤将输入字符串解析到左右操作数和一个运算符字符。The initial steps parse the input string into left and right operands and an operator character. foreach 循环中,operations 集合的每个成员都要检查。In the foreach loop, every member of the operations collection is examined. 这些对象是 Lazy<T,TMetadata> 类型,其元数据值和导出对象可分别使用 Metadata 属性和Value 属性进行访问。These objects are of type Lazy<T,TMetadata>, and their metadata values and exported object can be accessed with the Metadata property and the Value property respectively. 在此情况下,如果发现 Symbol 对象的 IOperationData 属性为匹配项,则计算器调用 Operate 对象的 IOperation 方法并返回结果。In this case, if the Symbol property of the IOperationData object is discovered to be a match, the calculator calls the Operate method of the IOperation object and returns the result.

还需要返回字符串中的首个非数字字符位置的 helper 方法来完成计算器。To complete the calculator, you also need a helper method that returns the position of the first non-digit character in a string. 将下面的 helper 方法添加到 MySimpleCalculator 类中:Add the following helper method to the MySimpleCalculator class:

Private Function FindFirstNonDigit(s As String) As Integer
    For i = 0 To s.Length - 1
        If Not Char.IsDigit(s(i)) Then Return i
    Next
    Return -1
End Function
private int FindFirstNonDigit(string s)
{
    for (int i = 0; i < s.Length; i++)
    {
        if (!Char.IsDigit(s[i])) return i;
    }
    return -1;
}

现在,应该可以编译和运行项目了。You should now be able to compile and run the project. 确保在 Visual Basic 里将 Public 关键字添加到 Module1 中。In Visual Basic, make sure that you added the Public keyword to Module1. 在控制台窗口中键入“5+3”等加法运算计算器会返回结果。In the console window, type an addition operation, such as "5+3", and the calculator returns the results. 任何其他运算符会导致“找不到运算!”Any other operator results in the "Operation Not Found!" 消息。message.

采用新类扩展 SimpleCalculatorExtending SimpleCalculator using a new class

既然计算器能够运行,则可以简单地添加新操作。Now that the calculator works, adding a new operation is easy. 将下面的类添加到模块或 SimpleCalculator 命名空间中:Add the following class to the module or SimpleCalculator namespace:

<Export(GetType(IOperation))>
<ExportMetadata("Symbol", "-"c)>
Public Class Subtract
    Implements IOperation

    Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
        Return left - right
    End Function
End Class
[Export(typeof(IOperation))]
[ExportMetadata("Symbol", '-')]
class Subtract : IOperation
{
    public int Operate(int left, int right)
    {
        return left - right;
    }
}

编译并运行该项目。Compile and run the project. 键入“5-3”等减法运算。Type a subtraction operation, such as "5-3". 现在,计算器既支持减法运算也支持加法运算。The calculator now supports subtraction as well as addition.

采用新程序集扩展 SimpleCalculatorExtending SimpleCalculator using a new assembly

将类添加到源代码非常简单,但 MEF 提供了观察应用程序部件源之外的内容的功能。Adding classes to the source code is simple enough, but MEF provides the ability to look outside an application’s own source for parts. 为了演示它,将需要通过添加 DirectoryCatalog 来修改简单计算器从而为部件搜索目录以及其自身的程序集。To demonstrate this, you will need to modify SimpleCalculator to search a directory, as well as its own assembly, for parts, by adding a DirectoryCatalog.

将叫作 Extensions 的新目录添加到 SimpleCalculator 项目中。Add a new directory named Extensions to the SimpleCalculator project. 确保将其添加到项目级别(而非解决方法级别)中。Make sure to add it at the project level, and not at the solution level. 然后将新类库项目添加到叫作 ExtendedOperations 的解决方法中。Then add a new Class Library project to the solution, named ExtendedOperations. 新项目将编译为一个单独的程序集。The new project will compile into a separate assembly.

打开 ExtendedOperations 项目的项目属性设计器,然后单击“编译” 或“生成” 选项卡。更改“生成输出路径”或“输出路径”以指向扩展目录,它位于 SimpleCalculator 项目目录中 (..\SimpleCalculator\Extensions\) 。Open the Project Properties Designer for the ExtendedOperations project and click the Compile or Build tab. Change the Build output path or Output path to point to the Extensions directory in the SimpleCalculator project directory (..\SimpleCalculator\Extensions\).

在 Module1.vb 或 Program.cs 中,将以下行添加到 Program 构造函数中 :In Module1.vb or Program.cs, add the following line to the Program constructor:

catalog.Catalogs.Add(New DirectoryCatalog("C:\SimpleCalculator\SimpleCalculator\Extensions"))
catalog.Catalogs.Add(new DirectoryCatalog("C:\\SimpleCalculator\\SimpleCalculator\\Extensions"));

将示例路径替换为指向扩展目录的路径。Replace the example path with the path to your Extensions directory. (此绝对路径仅供调试目的使用。(This absolute path is for debugging purposes only. 在生产应用程序中,应该使用相对路径。)现在,DirectoryCatalog 将把在扩展目录中的所有程序集中发现的部件添加到撰写容器中。In a production application, you would use a relative path.) The DirectoryCatalog will now add any parts found in any assemblies in the Extensions directory to the composition container.

在扩展操作项目中,将参考添加到简单计算器和 System.ComponentModel.Composition 中。In the ExtendedOperations project, add references to SimpleCalculator and System.ComponentModel.Composition. 在扩展操作类文件中,添加一个 System.ComponentModel.Composition 的 Importsusing 语句。In the ExtendedOperations class file, add an Imports or a using statement for System.ComponentModel.Composition. 在 Visual Basic 中,也添加一个简单计算器的 Imports 语句。In Visual Basic, also add an Imports statement for SimpleCalculator. 然后将下面的类添加到扩展操作类文件中:Then add the following class to the ExtendedOperations class file:

<Export(GetType(SimpleCalculator.IOperation))>
<ExportMetadata("Symbol", "%"c)>
Public Class Modulo
    Implements IOperation

    Public Function Operate(left As Integer, right As Integer) As Integer Implements IOperation.Operate
        Return left Mod right
    End Function
End Class
[Export(typeof(SimpleCalculator.IOperation))]
[ExportMetadata("Symbol", '%')]
public class Mod : SimpleCalculator.IOperation
{
    public int Operate(int left, int right)
    {
        return left % right;
    }
}

请注意,为了使协定匹配,ExportAttribute 属性必须与 ImportAttribute 的类型相同。Note that in order for the contract to match, the ExportAttribute attribute must have the same type as the ImportAttribute.

编译并运行该项目。Compile and run the project. 测试新的 Mod (%) 运算符。Test the new Mod (%) operator.

结束语Conclusion

本主题包括 MEF 的基础概念。This topic covered the basic concepts of MEF.

  • 部件,目录和撰写容器Parts, catalogs, and the composition container

    部件和撰写容器是 MEF 应用程序的基础构造块。Parts and the composition container are the basic building blocks of a MEF application. 部件是所有导入或导出值(最多包含自己)的对象。A part is any object that imports or exports a value, up to and including itself. 目录提供了一组来自特定源的部件。A catalog provides a collection of parts from a particular source. 撰写容器使用目录提供的部件来执行将导入绑定到导出的组合。The composition container uses the parts provided by a catalog to perform composition, the binding of imports to exports.

  • 导入和导出Imports and exports

    导入和导出是组件进行通信的方式。Imports and exports are the way by which components communicate. 组件使用导入指定对特定值或对象的需求,使用导出指定值的可用性。With an import, the component specifies a need for a particular value or object, and with an export it specifies the availability of a value. 每个导入都通过其协定匹配了一组导出。Each import is matched with a list of exports by way of its contract.

后续步骤Next steps

要下载此示例的完整代码,请参阅 SimpleCalculator 示例 (Visual Basic)To download the complete code for this example, see the SimpleCalculator sample (Visual Basic).

有关详细信息和代码示例,请参阅 Managed Extensibility FrameworkFor more information and code examples, see Managed Extensibility Framework. 有关 MEF 类型的列表,请参阅 System.ComponentModel.Composition 命名空间。For a list of the MEF types, see the System.ComponentModel.Composition namespace.