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

В этой статье приводятся общие сведения о библиотеке Managed Extensibility Framework, которая появилась на платформе .NET Framework 4.This topic provides an overview of the Managed Extensibility Framework that was introduced in the .NET Framework 4.

Что такое MEFWhat is MEF?

Платформа Managed Extensibility Framework (MEF) — это библиотека для создания простых расширяемых приложений.The Managed Extensibility Framework or MEF is a library for creating lightweight, 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. Самое главное, что нельзя добавлять новые компоненты без изменения исходного кода — данное ограничение может быть приемлемым, например, для веб-приложения, но не для клиентского приложения.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.

Сведения о возможностях MEFWhat 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 декларативно указывают свои возможности, они могут быть обнаружены во время выполнения, то есть, приложение может применять части без жестко кодированных ссылок или ненадежных файлов конфигурации.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 Is MEF Available?

MEF является неотъемлемой частью .NET Framework 4.NET Framework 4 и присутствует везде, где применяется платформа.NET Framework.MEF is an integral part of the .NET Framework 4.NET Framework 4, and is available wherever the .NET Framework is used. MEF можно использовать в клиентских приложениях, независимо от того, применяют они Windows Forms, WPF или любую другую технологию, либо в серверных приложениях, где применяется ASP.NET.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, и отвечает за изоляцию расширения, а также загрузку и выгрузку сборки, тогда как 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.

Пример приложения SimpleCalculatorSimpleCalculator: An Example Application

Чтобы узнать о возможностях MEF, проще всего создать простое приложение MEF.The simplest way to see what MEF can do is to build a simple MEF application. В этом примере выполняется создание очень простого калькулятора, который называется SimpleCalculator.In this example, you build a very simple calculator named SimpleCalculator. 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.To download the complete code for this example, see the SimpleCalculator sample.

Примечание

Пример с SimpleCalculator предназначен просто для демонстрации концепции и синтаксиса платформы 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, являются более сложными, чем SimpleCalculator.Many of the applications that would benefit most from the power of MEF are more complex than SimpleCalculator. Более сложные примеры см. в разделе Managed Extensibility Framework в GitHub.For more extensive examples, see the Managed Extensibility Framework on GitHub.

  • Чтобы приступить к работе, создайте проект консольного приложения в Visual Studio и назовите его SimpleCalculator.To start, in Visual Studio, create a new Console Application project and name it SimpleCalculator.

  • Добавьте ссылку на сборку System.ComponentModel.Composition, где находится MEF.Add a reference to the System.ComponentModel.Composition assembly, where MEF resides.

  • Откройте файл Module1.vb или Program.cs и добавьте операторы Imports или using для System.ComponentModel.Composition и System.ComponentModel.Composition.Hosting.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, который вы будете использовать для SimpleCalculator.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, in Module1.vb, add a public class named Program.

Добавьте следующую строку в класс Program в модуле Module1.vb или Program.cs: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. Разработчики приложений могут легко создавать новые каталоги для обнаружения частей из других источников, например, веб-служб.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 Exception
        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 из заданного типа (в данном случае интерфейс ICalculator).The 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(ByVal 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()
    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)
{
    Program p = new Program(); //Composition is performed in the constructor
    String s;
    Console.WriteLine("Enter Command:");
    while (true)
    {
        s = Console.ReadLine();
        Console.WriteLine(p.calculator.Calculate(s));
    }
}

Этот код просто считывает строку входных данных и вызывает функцию Calculate калькулятора ICalculator с результатом, который он записывает в консоль.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

Чтобы приложение SimpleCalculator было расширяемым, оно должно импортировать список операций.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(ByVal left As Integer, ByVal 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(ByVal left As Integer, ByVal 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(ByVal input As String) As String Implements ICalculator.Calculate
    Dim left, right As Integer
    Dim operation As Char
    Dim fn = FindFirstNonDigit(input) 'Finds the operator
    If fn < 0 Then
        Return "Could not parse command."
    End If
    operation = input(fn)
    Try
        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;
    int fn = FindFirstNonDigit(input); //finds the operator
    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.

Для завершения работы над калькулятором также нужен вспомогательный метод, который возвращает позицию первого нецифрового символа в строке.To complete the calculator, you also need a helper method that returns the position of the first non-digit character in a string. Добавьте в класс MySimpleCalculator следующий вспомогательный метод:Add the following helper method to the MySimpleCalculator class:

Private Function FindFirstNonDigit(ByVal s As String) As Integer
    For i = 0 To s.Length
        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. Любой другой оператор вернет сообщение "Operation Not Found" (Операция не найдена).Any other operator results in the "Operation Not Found!" !".message.

Расширение приложения SimpleCalculator с помощью нового классаExtending 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(ByVal left As Integer, ByVal 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.

Расширение приложения SimpleCalculator с помощью новой сборкиExtending 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. Чтобы продемонстрировать это, необходимо изменить приложение SimpleCalculator для поиска в каталоге, а также в его собственной сборке, частей путем добавления 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.

В проекте ExtendedOperations добавьте ссылки на приложение SimpleCalculator и System.ComponentModel.Composition.In the ExtendedOperations project, add references to SimpleCalculator and System.ComponentModel.Composition. В файле класса ExtendedOperations добавьте оператор Imports или using для System.ComponentModel.Composition.In the ExtendedOperations class file, add an Imports or a using statement for System.ComponentModel.Composition. В Visual Basic также добавьте оператор Imports для SimpleCalculator.In Visual Basic, also add an Imports statement for SimpleCalculator. Затем добавьте следующий класс в файл класса ExtendedOperations: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(ByVal left As Integer, ByVal 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.

Что теперь предстоит делать?Where Do I Go Now?

Чтобы скачать полный исходный код для этого примера, см. раздел Пример SimpleCalculator.To download the complete code for this example, see the SimpleCalculator sample.

Дополнительные сведения и примеры кода см. в разделе Managed Extensibility Framework.For 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.