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

ここでは、.NET Framework 4 で導入された Managed Extensibility Framework の概要を説明します。This topic provides an overview of the Managed Extensibility Framework 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, 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. これにより、1 つのコンポーネントを複数のアプリケーションで使用することが困難となり、コンポーネントのテスト フレームワークを作成するときに問題が生じる可能性もあります。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 パートは、その機能を宣言的に指定するため、実行時に検出できます。これは、アプリケーションがハードコーディングされた参照または脆弱な構成ファイルを使用せずにパートを使用できることを意味します。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 フォームや 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

アプリケーションが拡張機能を特定および管理できるように設計された MAF (Managed Add-in Framework) は、以前のバージョンの .NET Framework で導入されました。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 では発見可能性、拡張性、および移植性に重点が置かれているのに対し、MAF では拡張機能の特定とアセンブリの読み込みおよびアンロードに重点が置かれています。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. この 2 つのフレームワークは円滑に相互運用でき、1 つのアプリケーションで両方を活用することができます。The two frameworks interoperate smoothly, and a single application can take advantage of both.

SimpleCalculator: サンプル アプリケーションSimpleCalculator: 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 will 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. その他のサンプルについては、GitHub の「Managed Extensibility Framework」をご覧ください。For more extensive examples, see the Managed Extensibility Framework on GitHub.

まず、Visual Studio 2010Visual Studio 2010SimpleCalculator という名前の新しいコンソール アプリケーション プロジェクトを作成します。To start, in Visual Studio 2010Visual Studio 2010, create a new Console Application project named SimpleCalculator. MEF が存在する System.ComponentModel.Composition アセンブリへの参照を追加します。Add a reference to the System.ComponentModel.Composition assembly, where MEF resides. Module1.vb または Program.cs を開き、System.ComponentModel.Composition および System.ComponentModel.Composition.Hosting の Imports ステートメントまたは using ステートメントを追加します。Open Module1.vb or Program.cs and add Imports or using statements for System.ComponentModel.Composition and System.ComponentModel.Composition.Hosting. これらの 2 つの名前空間には、拡張可能なアプリケーションの開発に必要な MEF 型が含まれています。These two namespaces contain MEF types you will need to develop an extensible application. Visual Basic では、Public モジュールを宣言する行に Module1 キーワードを追加します。In 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. (つまり、インポートとエクスポートの組み合わせ)。最も一般的な種類の合成コンテナーは CompositionContainer で、SimpleCalculator にはこれを使用します (That is, the matching up of imports to exports.) The most common type of composition container is CompositionContainer, and you will use this for SimpleCalculator.

Visual Basic では、Module1.vb に Program という名前のパブリック クラスを追加します。In Visual Basic, in Module1.vb, add a public class named Program. それから、Module1.vb ファイルまたは Program.cs ファイルの Program クラスに次の行を追加します。Then 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 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));  
    }  
}  

このコードは、単純に入力行を読み取り、結果に対して 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

SimpleCalculator に拡張性を持たせるためには、演算の一覧をインポートする必要があります。In order for SimpleCalculator to be extensible, it needs to import a list of operations. 通常の ImportAttribute 属性は、1 つの 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 でメタデータにアクセスする方法にはいくつかありますが、これはその 1 つです)。(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. この MySimpleCalculatorIOperation オブジェクトのコレクションをインポートし、このインポートは 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

3 つのパートを作成したので、残っている作業は電卓ロジックを作成することです。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!";  
}  

1 つ目のステップでは、入力文字列が解析され、左オペランド、右オペランド、および演算子文字が特定されます。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 の場合、PublicModule1 キーワードを追加していることを確認してください。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 will return the results. それ以外の演算子を入力すると、"Operation Not Found!" というメッセージが表示されます。Any other operator will result 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. この例を示すためには、DirectoryCatalog を追加することで、独自のアセンブリだけではなくディレクトリでもパートを検索するように SimpleCalculator を変更する必要があります。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.

SimpleCalculator プロジェクトに Extensions という名前の新しいディレクトリを追加します。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 プロジェクト ディレクトリの Extensions ディレクトリ (..\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"));  

例に示されているパスを、Extensions ディレクトリのパスに置き換えます。Replace the example path with the path to your Extensions directory. (この絶対パスはデバッグにのみ使用します。(This absolute path is for debugging purposes only. 実稼働アプリケーションでは、相対パスを使用します)。これで、DirectoryCatalog により、Extensions ディレクトリ内のアセンブリで見つかったすべてのパートが合成コンテナーに追加されます。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 クラス ファイルで、System.ComponentModel.Composition の Imports ステートメントまたは using ステートメントを追加します。In the ExtendedOperations class file, add an Imports or a using statement for System.ComponentModel.Composition. Visual Basic では、SimpleCalculator の Imports ステートメントも追加します。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.