예제를 통해 MEF 겉을 사알짝 핥아 보자.
1. 먼저 컴포넌트를 정의 해보자.
namespace Contract
{
public interface IComponent
{
string Description { get; }
string ManipulateOperation(params double[] args);
}
public interface IMetadata
{
char Symbol { get; }
}
}
위에서 정의한 컴포넌트는 Description이란 이름으로 컴포넌트에 대한 설명 string과,
ManipulateOperation이라는 메서드(무언가 동작하는)를 갖는다.
추가적으로 IMetadata 인터페이스를 정의하여 Symbol이라는 이름의 프러퍼티를 정의하자.
2. 컴포넌트의 정의대로 특정 기능을 맞추어 외부로 export 하는 놈을 만들자.
using System.ComponentModel.Composition;
using Contract;
namespace ExportMultiplyLib
{
[Export(typeof(IComponent))]
[ExportMetadata("Symbol", '*')]
public class MultiplyOfNumberComponent : IComponent
{
public string Description
{
get { return "Multiplication of components"; }
}
public string ManipulateOperation(params double[] args)
{
string result = "";
double count = 1;
foreach (double d in args)
{
count *= d;
result += d.ToString() + " * ";
}
return result.Trim('*', ' ') + " = " + count.ToString();
}
}
}
위에서 보는대로 위 클래스는 IComponent를 구현한다. 클래스의 첫단에 attribute를 두어
IComponent타입으로 export함을 표시하고, 추가적으로 Metadata도 export한다.
이런 attribute 정보는 외부에서 검색(?)할때 판별할 정보가 된다. 즉 이러한 정보를 기준으로
원하는 컴포넌트를 찾게 되는 것이다.
3. 이제 이렇게 export된 놈을 갖다 쓰는 놈을 만들자.
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.IO;
using System.Reflection;
using Contract;
namespace ImportingLib
{
public class Importer
{
[ImportMany(typeof(IComponent))]
private IEnumerable<Lazy<IComponent, IMetadata>> operations;
public void DoImport()
{
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)));
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
public int AvailableNumberOfOperation
{
get { return operations != null ? operations.Count() : 0; }
}
public List<string> CallAllComponent(params double[] args)
{
var result = new List<string>();
foreach (Lazy<IComponent, IMetadata> com in operations)
{
Console.WriteLine(com.Value.Description);
Console.WriteLine(com.Metadata.Symbol);
result.Add(com.Value.ManipulateOperation(args));
}
return result;
}
}
}
위의 Importer는 클래스 정의 위에는 특별한 attribute는 없다.
멤버중 "operations"는 IComponent 타입의 컴포넌트를 여러개(ImportMany) 갖는 열거형 멤버이다.
Lazy<IComponent, IMetadata>를 통해 IComponent와 IMetadata타입을 참조한다.
DoImport 메서드에서는 카탈로그를 정의하고 카탈로그에 컴포넌트가 있는 위치를 지정한다.
즉 해당 위치에서 목록(카탈로그)을 구성하라는 의미이다.
CompositionContainer타입의 컨테이너를 생성하며 위에서 준비해 놓은 카탈로그를 기반으로 한다.
최종적으로 컨테이너의 ComposeParts를 호출하여 dll들을 검색하여 컴포넌트를 컨테이너에 담아둔다.
프러퍼티로서 AvailableNumberOfOperation은 컴포넌트의 수를 나타낸다.
메서드 CallAllComponent는 모든 컴포넌트의 동작을 실행시킨다.
동작에는 우선 컴포넌트의 Description을 표시하고,
메타데이터의 심볼을 표시한다.
그리고 각 컴포넌트 고유의 기능을 수행한 결과를 표시한다.
4. 위의 Importer를 실행시켜 보자. 간단한 콘솔프로그램으로 한다.
using System;
namespace MEFTest
{
class Program
{
static void Main(string[] args)
{
var t = new ImportingLib.Importer();
t.DoImport();
Console.WriteLine("{0} component(s) are imported successfully.", t.AvailableNumberOfOperation);
var result = t.CallAllComponent(125, 5, 10, 27, 45, 19, 10);
foreach (string s in result)
{
Console.WriteLine(s);
}
Console.Read();
}
}
}
Importer객체를 하나 생성해서 DoImport를 실행시킨다.
그리고 CallAllComponent를 실행시킨다. 인자로서 숫자들을 넘긴다.
이는 CallAllComponent 메서드의 인자인 double[]로 넘겨진다.
CallAllComponent는 string타입 List를 반환하므로, 각 string을 표시한다.
5. 예시 결과는 아래와 같다.
위에서는 곱셈 컴포넌트 하나만 코드로 살펴봤다. 동일한 방식으로 뺄셈, 덧셈도 만들어 실행한 결과이다.
* 실행시 주의할점.
각 exporter들의 output path를 실행파일의 경로로 일치시켜 주던가, 아니면 컴파일 후 dll들을 실행파일 경로에 복사해 넣어주어야 한다.
각 컴포넌트를 구성하는 dll들은 동적으로 검색되어 실행되므로, 몇가지 dll들을 빼고 실행시키면 존재하는 dll들에 대해서만 실행된다.
* 프로젝터 소스를 첨부한다.
MEFTest.zip
* 코드올리기
찾아보니 티스토리에 코드를 이쁘게 올리는 방법을 소개한 자료가 많더라(SyntaxHighlighter ?).
난 그렇게 하기도 귀찮아서, 아래 온라인 사이트에 코드 붙여넣어 스타일 얻어 다시 복붙했다.
(http://www.pvladov.com/p/syntax-highlighter.html)