블로그 이미지
차세대 개발 플랫폼인 .NET Framework 4.0 과 Visual Studio 2010 의 정보와 아티클을 제공하는 공식 팀 블로그 입니다. 엄준일(땡초)
« 2010/03 »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      


 
 

.NET Framework 4.0 에 포함될 MEF(Managed Extensibility Framework) 은 컴포넌트를 조합하는 방식으로 컴포넌트의 재사용성과 보다 컴포넌트를 동적으로 사용할 수 있는 프레임워크입니다.

하 지만 MEF 는 기존에 제네릭 타입(Generic Types) 을 지원하지 않습니다. 이미 C# 2.0 부터 지원하는 Generic Type 을 MEF 에서 지원하지 않는 것도 참 아이러니 합니다. 여기에 대한 내용은 아래의 링크를 참고하십시오.

[.NET/.NET Framework] - MEF 에 Generic Type 을 지원하기 위해서..?
[.NET/.NET Framework] - MEF 는 Generic Type 을 지원하지 않는다!

MEFGeneric 이란?

안타깝게도, MEF 에 Generic Type 을 지원하기 위해 적당한 대안이 아직 전세계적으로도 없다는 것입니다. Microsoft 의 MEF 개발 팀 리더도 MEF V1.0 버전에는 지원하지 못할거라고 합니다. 

http://codebetter.com/blogs/glenn.block/archive/2009/03/21/why-doesn-t-mef-support-open-generics-for-exports-because-mef-is-not-type-based.aspx
http://codebetter.com/blogs/glenn.block/archive/2009/08/20/open-generic-support-in-mef.aspx

MEFGeneric 은 전세계적으로 처음으로 정식으로 Generic Type 을 지원합니다. 세계 최초로 한국에서, 그리고 닷넷엑스퍼트에서, 그리고 나로부터 문제를 해결하니, 로또 맞은 기분이네요.

MEFGeneric 은 기존의 MEF 를 Core(코어) 소스 코드를 수정/확장하여 Generic Type 을 지원하도록 하였습니다. 필자는 이 소스 코드를 codeplex 사이트를 통해 공개하였으니, 아래의 사이트를 참고하세요. ^^

   

MEFGeneric is a framework to support CLR Generic types in MEF (Managed Extensibility Framework).

http://mefgeneric.codeplex.com

 

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

먼저 이전 포스트의 "MEF 는 Generic Type 을 지원하지 않는다!" 에서 언급했고, .NET CLR 2.0 부터 Generic Type 을 지원함에도 불구하고, .NET Framework 4.0 에 포함되는 MEF 가 Generic Type 을 지원하지 않는다는 것은 솔직히 납득하기가 어렵습니다. MEF 개발 PM 이 말하는 강력한 계약 기반(Strongly Contract Based) 의 모델이라는 점은 머리로는 이해는 되지만, 사실 안될 것도 없습니다. -_-;

MEF 가 갖는 대표적인 키워드인 Composable 은 현재 Generic Type 을 지원하지 않지만, 상당히 매력이 있습니다. 이미 현대적인 프레임워크는 Modular 에 집중하고 있고, MEF 는 더 나아가 Modular + Composite 이라는 상당한 매력을 가진 프레임워크입니다.

일단 서두는 이쯤에서 접어두고, MEF 가 Generic Type 을 지원하기 위한 몇 가지 공개되어 있는 방법을 알아보고, 다시 이야기를 나누어 봅시다. 
   

How to support Generic Type of MEF ?    

첫 번째 방법 - Factory Provider

가장 간단한 방법이 바로 Factory Pattern 을 이용한 방식입니다. 객체의 생성은 Factory 를 통해 생성하도록 하고, Factory 는 객체의 Type 을 받음으로써 객체의 생성을 Factory 에게 모두 의존하는 방법입니다. 우선 아래의 링크를 참고하세요.

 ExportProvider 를 재정의하여 객체의 Type 을 등록하여 원하는 Type 의 객체를 생성하도록 합니다.

1: public interface IService { }

2: public interface IUserService : IService { }

3:  

4: [Export]

5: public class UserController {

6: [ImportingConstructor]

7: public UserController(IUserService userService) { }

8: }

9:  

10: // in your application

11: private void Compose() {

12: var catalog = new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly());

13: var factoryProvider = new FactoryExportProvider<IService>(GetService);

14: var container = new CompositionContainer(catalog, factoryProvider);

15: container.AddPart(this);

16: container.Compose();

17: }

18:  

19: public IService GetService(Type type) { return ... }

   

하지만, 이 방법은 상당히 문제가 많은 방법입니다. 가장 즐겨쓰고, 흔히 볼 수 있는 Pattern 이기 때문에 추가되는 Factory 마다 객체 등을 Factory Provider 에 등록을 해 주어야 합니다. 그 뿐만이 아니죠. Factory Pattern 의 특성상 객체를 생성하는 Factory 는 일일이 각 객체의 타입을 체크하여 반환해 주어야 합니다.

그리고 위의 코드에서는 Type 인자가 1개이지만, 그 이상이라면??? 가령, Generic Type Class<T1,T2,T3,T4,T5> 가 된다면 대략 난감하겠죠. 일단 작은 코드에서는 쓸만할 수 있지만, 꾸준히 성장하는 코드라면 이러한 Factory 방식은 코드의 변경이 너무 잦아집니다.

   

두 번째 방법 - Type Mapping

MEF 는 Codeplex 에 공개가 되어있고, MEF Contrib 으로 불리우는 MEF 의 확장 라이브러리 입니다. MEF Contrib 의 가장 큰 특징 중에 하나인 ComposablePartCatalog 를 재정의 하는 Generic Catalog 를 지원해 줍니다. 이 링크에서 Type Mapping 을 통한 문서를 볼 수 있습니다.

public class GenericCatalogContext
{
protected AggregateCatalog _aggegateCatalog;
protected GenericCatalog _genericCatalog;
protected ImportDefinition _repositoryImportDefinition;

public GenericCatalogContext()
{
var typeCatalog = new TypeCatalog(typeof(OrderProcessor), typeof(RepositoryTypeLocator));
_aggegateCatalog =
new AggregateCatalog();
_aggegateCatalog.Catalogs.Add(typeCatalog);
_genericCatalog =
new GenericCatalog(_aggegateCatalog);
string orderProcessorContract = AttributedModelServices.GetContractName(typeof(OrderProcessor));
var orderProcessPartDefinition = typeCatalog.Parts.Single(p => p.ExportDefinitions.Any(d => d.ContractName == orderProcessorContract));
_repositoryImportDefinition = orderProcessPartDefinition.ImportDefinitions.First();
Context();
}

public virtual void Context()
{

}
}

[InheritedExport]
public abstract class GenericContractTypeMapping
{
public GenericContractTypeMapping(Type genericContractTypeDefinition, Type genericImplementationTypeDefinition)
{
}

public Type GenericContractTypeDefinition { get; }
public Type GenericImplementationTypeDefinition { get; }
}

public class RepositoryTypeLocator : GenericContractTypeMapping
{
public RepositoryTypeLocator()
:
base(typeof(IRepository<>), typeof(Repository<>))
{
}
}

public class Repository<T> : IRepository<T>
{
}

이러이러한 과정을 통해서 아래와 같이 Type Mapping 을 통해 Generic Type 을 사용할 수 있습니다.

[TestFixture]
public class When_querying_catalog_for_an_order_repository_and_no_closed_repository_is_present : GenericCatalogContext
{
[Test]
public void order_repository_part_definition_is_created()
{
Assert.IsNotNull(_result.Item1);
}

[Test]
public void order_repository_export_is_created()
{
Assert.IsNotNull(_result.Item2);
}

public override void Context()
{
_result = _genericCatalog.GetExports(_repositoryImportDefinition).Single();
}

private Tuple<ComposablePartDefinition, ExportDefinition> _result;
}

Contract Type 와 Mapping Type 을 매핑하여 Locator 로 등록하여 주고, 각각 Mapping Class 를 통해 실제 계약의 Generic Type 매핑이 이루어 집니다.

다시 말해서, Generic Class 별로 Locator Class, Mapping Class, 그리고 Mapping Context Class 를 만들어주어야 합니다. 배보다 배꼽이 더 커지는 격입니다. 일단, 아이디어는 좋지만 안쓰고 말랍니다.

   

세 번째 방법 - MEF + Unity 조합

아마도 가장 이상적인 방법이긴 합니다. Unity Application Block 은 Unity Container Extension 을 지원하기 때문에 객체의 Register, Resolve 등의 이벤트를 가로채서 Unity 의 기능을 확장할 수 있습니다. 이 이벤트를 MEF 에서 받도록 하여 MEF 의 ExportProvider 의 GetExportsCore 를 통해 Unity 의 객체에서 Resolve 하도록 하는 방법입니다.

UnityContainerExtension 을 재정의하여, 아래와 같이 이벤트를 받고, 이것을 MEF ExportProvider 로 전달하는 방법입니다.

UnityContainerExntension 에서는 아래와 같이...

protected override void Initialize()
{
this.Context.Registering += new EventHandler<RegisterEventArgs>(Context_Registering);
this.Context.RegisteringInstance += new EventHandler<RegisterInstanceEventArgs>(Context_RegisteringInstance);
}

MEF 의 ExportProvider 에서는 아래와 같이…

protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
{
if (definition.ContractName != null)
{
Type contractType;
if(Mapping.TryGetValue(definition.ContractName, out contractType))
{
if (definition.Cardinality == ImportCardinality.ExactlyOne || definition.Cardinality == ImportCardinality.ExactlyOne)
{
var export = new Export(definition.ContractName, () => serviceLocator.GetInstance(contractType));
return new List<Export> { export };
}

}
}
return Enumerable.Empty<Export>();
}

일단 가장 완벽해 보입니다만, 이 속에는 그 이상 많은 문제들이 생기게 됩니다. MEF 도 내부적으로 Injection(주입) 기법을 사용하고, Unity 에서도 Injection 을 사용하는데 바로 이 Injection 방법이 달라지게 되는 것입니다. 즉, MEF 기반의 코드와 Unity 기반의 코드의 Injection 선언 방법이 틀려지고, 서로 호환할 수 없다는 것입니다.

결국 DI 프레임워크는 특정 DI Container 에 의존할 수 밖에 없어지고, 더불어 Compisite 과 Injection 은 두 가지의 사용 방법이 혼재될 수 밖에 없다는 것이죠.

   

Conclusion

MEF 에서 Generic Type 을 사용하고 싶어서 안달이 난 1은 여러 가지 방법을 찾아보았지만, 사용성, 재사용성, 확장성, 유연성 등 모든 면에서 원하는 해답을 찾지 못했습니다. 그리고 현재까지 MEF 에서 Generic Type 을 지원하기 위한 대략적인 3가지 방법을 정리해보도록 하죠.

  

장점

단점

MEF Factory Export Provider

  • 구현이 쉽다
  • Factory 의 관리가 힘들다
  • Factory 의 확장이 힘들다
  • 모든 Factory 를 Catalog 로 관리해야 한다.

MEF Contrib Type Mapping

  • 합리적이다
  • Type Mapping 코드가 복잡하다
  • Mapping/Locator/Context 클래스를 구현해야 한다
  • 상속 기반이다

MEF + Unity Integrated

  • 합리적이고 , 구현이 쉽다
  • Injection 기법이 서로 달라진다
  • Injection 코드가 서로 달라진다
  • Injection 이 호환되지 않는다
  • 각각의 객체간의 Composite 이 불가능하다

이제 슬슬 머리가 아파옵니다. 향후 .NET Framework 4.0 에서 가장 큰 빛을 보게 될 MEF 이지만, Generic Type 을 지원하지 않는다는 것은 가장 큰 오점이 아닐까 생각합니다. 우선 이쯤에서 마무리하고 어떻게 해야 할지 생각해 보도록 하지요.


MEF 에서 Generic Type 문제는 코드 플랙스에 MEFGeneric 으로 공개하였습니다.
[.NET/.NET Framework] - MEFGeneric 코드 플랙스에 공개합니다.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

.NET Framework 4.0 에 포함이 될 Managed Extensibility Framework(이하 MEF) 는 Generic Type 을 지원하지 않습니다. ( MEF is not supporting Generic Type!!!! )   

상당히 충격입니다. MEF 는 현재 Generic Type 을 지원하지 않습니다. 이것을 가지고 현재 중요한 프로젝트를 진행하기 위해 여러 가지 리뷰를 해 보고 있습니다만, MEF 가 Generic Type 을 지원하지 않는 것은 쉽게 말해 'MEF 는 아직…' 이라는 결론이 나는군요.    


Managed Extensibility Framework Basic

이것을 이해하기 위해서는 MEF 의 기본부터 이해해야 할 필요가 있습니다. 자세한 내용은 아래의 필자의 블로그 링크를 클릭하시면 Managed Extensibility Framework 에 대한 아티클을 볼 수 있습니다.

우선 이러한 원인은 MEF 가 Contract Model(계약 모델) 기반이라는 있다는 이유 입니다. 우리가 흔히 사용하는 계약 모델은 쉽게 이야기하면 제공자와 소비자로 구분할 수 있습니다. 제공자와 소비자의 거래가 성립이 되기 위해서는 바로 계약이라는 것이 필요하죠. MEF 로 비유하자만 Import/Export 가 바로 그것이며 그 계약을 성립시켜 주는 것이 MEF Container 와 Composition Batch 로 볼 수 있습니다.

바로 이러한 계약 기반과 Composable Part 라는 개념으로 기존의 컴포넌트의 재사용성을 높일 수 있게 되며, 좀 더 동적이며, 추상화가 가능한 프레임워크 입니다. 더 쉽게 얘기하면, 새로운 C 라는 컴포넌트는 A 와 B 라는 컴포넌트와 계약하여 결합시키거나, 기존 컴포넌트를 변형시키는 등 Composable Application 을 만들기 위해 계약의 명세만 알면 다양한 컴포넌트를 재생산, 변형, 다양성, 재활용 등을 할 수 있습니다.

 

MEF 는 내부적으로 이러한 명확한 계약을 위해 여러 가지 방법으로 계약을 정의할 수 있습니다. 기본적으로 ExportAttribute 을 사용하여 String, CLR Type, ExportMetadata 를 사용하게 되어 있지요. 하지만 MEF 는 모든 계약의 명세는 바로 String 을 사용하는 데에서 문제가 발생하게 됩니다. 그리고 이것이 Dependency Injection(DI) 와 Inversion Of Control(IoC) 와 다른 점입니다. 대부분의 DI 프레임워크는 Object 의 Lifecycle 을 관리하고 객체의 의존성을 낮추기 위해 역제어 하는 것에 초점이 맞추어져 있기 때문에 CLR Type 기반으로 Container 에 등록이 됩니다.

예를 들어 보면, 아래와 같은 것이 MEF 에서는 계약 명세 규격에 어긋난다는 의미입니다. (특정 DI 프레임워크에 종속되지 않는 코드입니다)

var container = new Container();
container.Register<IUMC<>>();

var obj = container.Resolve<IUMC<string>>();
obj.SayHello();

   

Why MEF is not supporting Generic Type?

MEF 가 Generic Type 을 지원하지 않는 것에 이미 많은 사람들이 문제를 발견했고, 몇 가지 해결 방법이 있긴 있습니다.

이미 Ayende Rahien 이라는 사람의 블로그에는 MEF 가 Generic Type 을 지원하지 않는 것에 대한 이야기를 합니다. 내용을 보면 처음부터 Microsoft 의 MEF 개발 팀은 Generic Type 을 배제하고 있었던 것 같습니다. 하지만 Ayende Rahien 씨는 이 문제에 대해 반드시 해결해야 한다는 이야기를 MEF 개발 팀과 나누었습니다. 저도 이 문제가 반드시 해결 되리라 생각합니다만… 현재로써는 글쎄 ^^;

여기에서 MEF 개발 팀은 조금 구차한 변명을 합니다. 위에서 얘기한 MEF 의 기본은 계약 기반의 프레임워크라는 것입니다. 이 문제에 대해 추측을 해보면, MEF 가 Generic Type 을 지원한다는 것은 Strongly Contract Based 가 될 수 없기 때문이고, Generic Type 으로 인해 명확한 계약이 이루어질 수 없다는 것입니다. 특히 MEF 는 계약의 명세가 모두 MEF 가 내부적으로 관리하고 있기 때문에, Generic Type 에 의한 객체 의 계약 관리는 엄청난 메모리 사용량을 증가로 이어질 가능성이 충분합니다.

실제로 Microsoft 에서 MEF 개발 팀의 PM 을 맡고 있는 Glenn Block 씨는 이 아티클에서는 MEF v1 에서는 Generic Type 을 지원하지 못할 것이라고 합니다. 만약에 Generic Type 을 지원하게 된다면 차기 버전이 될 듯 합니다.

하지만, 다시 한번 MEF 는 계약 기반의 모델이라는 것을 생각하지 않을 수 없습니다. 만약 계약이 명확하지 않다면 계약 자체가 불명확하다는 의미입니다. C# 2.0 부터 지원하는 Generic Type 의 명확하지 않는 타입이 계약에 존재한다면 이것은 계약 자체가 성립되기 힘들다는 전제 조건을 포함하게 됩니다.

MEF 의 예를 들어 봅시다. 아래와 같은 Generic Type 의 계약이 존재합니다. (현재의 MEF 로는 전혀 불가능한 코드입니다^^;)

public interface IUMC<T>
{

void SayHello<T>();
}

[Export(typeof(IUMC<>))]
public class UMC<T> : IUMC<T>
{
public void SayHello()
{
// TODO Impl...
}
}

CLR(Common Language Runtime) 의 Generic Type 의 특성상 Generic T Parameter 는 굉장히 다형적입니다. UMC<string> 또는 UMC<int> 또는 모든 Class Type 이 T Parameter 에 대입될 수 있습니다. 단순히 어떤 타입도 올 수 있다는 것을 떠나 물건을 팔 사람은 도대체 소비자가 누구와 계약한 것인지 알 수 없고, 실제 상거래와 같은 상황이라면 사기와도 같다는 것이죠. 굳이 예를 들자면, 주민등록번호가 다름에도 불구하고, 주민등록증의 이름이 같은 동명인에게 언제든지 계약을 할 수 있다는 것이죠.

DI(Dependency Injection / IoC) 는 CLR Type 을 기반으로 합니다. 일부 DI 프레임워크는 Tag 와 같은 Contract Data 를 제공하기는 하지만 이것은 Metadata 그 이상의 역활을 하지 않습니다. 즉 Contract(계약) 와는 전혀 무관하다는 이야기 입니다. 객체를 질의(Query) 하기 위함이지 Composable 을 위한 것은 아닙니다.

 

OK! I'm understand. But…!!

처음부터 MEF 는 계약 기반의 Composable/Plugin Model/Contract Based 라는 용어를 자주 만나게 됩니다. 그리고 계약 자체라는 의미에서 Generic Type 은 가장 큰 장애 요소임이 확실합니다. 그렇기 때문에 현존하는 모든 DI(Dependency Injection) 프레임워크는 계약(Contract) 라는 용어를 절대 사용하지 않습니다. 목적 자체가 계약과는 전혀 무관하기 때문입니다.

하지만, MEF 의 계약 모델은 내부적으로 String Based Contract 를 사용하고 있고, Generic Type 또한 String 으로 표현이 가능하기 때문에, 문자열의 Parsing 만으로 어느 정도의 Generic Type 을 지원할 수 있을 거라고 생각했습니다.

필자는 처음 MEF 를 본 순간 "이것을 물건이다!" 라는 걸 느꼈습니다만, 아마도 MEF 개발 팀은 두 가지의 고민을 했을 거라고 생각합니다. Silverlight 를 지원할지, Generic Type 을 지원할지에 대한 범용성에 대해서 말입니다. 하지만, Generic 에 대해 많은 피드백을 받음에도 불구하고 MEF v1 에 지원하지 않을 듯한 대답은 사실 "구차한 변명" 으로 밖에 들리지 않는답니다. 결국, 현재 MEF 는 Silverlight 를 지원하는 등 .NET Framework 의 범용성에 치중하였고, 결국 Generic Type 은 현재 시점에서 릴리즈 시점까지 구현이 불가능할 거라고 예상합니다.

아쉽긴 하지만, 현재 MEF 가 불가능한 Generic Type 에 대한 영역은 몇 가지 Open Source 에서 제공을 하고 있습니다. 단지 실제 사용성에 대한 의구심과 필자의 견해로는 안쓰는게 나을 것 같다는 판단입니다.

다음에 당장 지원하지 않는 Generic Type 을 어떻게 사용할지 알아보고 함께 돌파구를 찾아보도록 하겠습니다. 


MEF 에서 Generic Type 문제는 코드 플랙스에 MEFGeneric 으로 공개하였습니다.
[.NET/.NET Framework] - MEFGeneric 코드 플랙스에 공개합니다.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

MEF Preview 6 공개

.NET Framework 4.0/Managed Extensibility Framework | 2009/07/20 12:36 | Posted by 엄준일(땡초)

MEF(Managed Extensibility Framework) 이 2009년 7월 13일에 릴리즈되어 14일에 공개가 되었습니다. 특히 MEF 는 .NET Framework 4.0 에 포함이 되어있으며, CodePlex 에서 굉장히 빠른 속도로 발전하고 있는 프레임워크 중에 하나 입니다.    

CodePlex MEF 사이트에 등록된 릴리즈 노트 입니다.

그 중에서 몇 가지만 살펴보도록 하겠습니다.

실버라이트 3 지원

특히 이번에 눈여겨 볼 만한 것이 Silverlight 3 를 지원하는 것입니다. 이미 Preview 5 이전의 MEF 에서는 실버라이트를 지원하기 위해 코드에서 전처리 명령 구문을 사용하여 프레임워크 실버라이트가 지원되긴 했습니다. 그것을 기반으로 이번 Preview 6 버전에서는 실버라이트 3 용 솔루션이 제공됩니다. 이 솔루션을 통해 실버라이트용 MEF 프레임워크를 빌드 하시면 됩니다.

   

ExportAttribute 의 seal 키워드 제거

이번 MEF Preview 6 에서 ExportAttribute 의 seal 키워드가 제거됩니다. 이 전 Preview 5 에서는 ExportAttribute 을 절대 상속할 수 없는 구조였기 때문에 독자적인 Export 를 제공할 수 없었기, Metadata 를 ExportAttribute 와 같은 기능울 개별적으로 지정해야 하는 등의 Export 기능의 확장에 불합리한 구조로 되어있었습니다. 하지만 이번 Preview 6 버전에서는 ExportAttribute 클래스의 seal 키워드가 제거 됨으로써 ExportAttribute 의 확장이 용이하게 되었습니다.

 

ImportMany 로 통일된 컬렉션 처리

이제 모든 컬렉션은 ImportMany 로 통일되었습니다. Preview 5 에서는 ExportCollection<T> 또는 IEnumerable<T> 와 같이 사용하기도 까다롭고 이것으로 반환된 객체를 처리하기에도 편리한 구조는 아니였습니다. Preview 5 에서도 ImportMany 의 기능이 존재하였지만, 이번 Preview 6 는 이것으로 모든 컬렉션 처리를 하도록 통일되었다는게 혼란스러웠던 부분을 간소화할 수 있을 것 같습니다.

   

기타 그 밖에도 구조적으로 보다 안정되었고, 메서드나 클래스의 네이밍이 변경되었고, 예외 시에 Composition 동작의 분석을 추적하기 쉽도록 덤프(Dump) 를 뜰 수 있는 로깅 기능 등 다양한 기능이 추가되었습니다. 정식 버전의 MEF 는 어떤 모습으로 나올지 정말 기대가 됩니다. 더 자세한 내용은 아래의 링크를 통해 더 쉽게 설명이 되어있으니 참고 하십시오.


참고문헌
MEF Preview 6 Available
http://blogs.msdn.com/nblumhardt/archive/2009/07/09/mef-preview-6-available.aspx

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Querying the CompositionContainer
 
CompositionContainer 의 Exported 구성 요소를 취득하기 위해서는 컨테이너에 하나의 구성 요소만이 존재해야 합니다. 쿼리(Query) 를 통해 이러한 객체들이 여러 개 존재할 경우 MEF 는 예외를 발생하게 됩니다.
 
바로 아래와 같은 경우이죠.
l 하나의 인스턴스를 요청할 때, 인스턴스를 찾지 못했을 경우
l 하나의 인스턴스를 요청할 때, 인스턴스가 여러 개일 경우
 
GetExportedObject
 
일반적으로 ExportAttribute 에 인자가 전달되지 않은 경우는 클래스의 타입이 키 값이 되어, 아래와 같이 구성 요소를 취득할 수 있습니다.
 
class Program
{
        static void Main(string[] args)
        {
               var container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
              
               var export = container.GetExportedObject<Export1>();
               export.Say();
 
               Console.ReadKey();
        }
}
 
[Export]
class Export1
{
        public void Say()
        {
               Console.WriteLine("Export1 Say..");
        }
}
 
 
만약, ExportAttribute 에 Contract Name 이 선언이 될 경우는 클래스의 타입 정보와 Contract Name 이 일치해야 구성요소를 취득할 수 있습니다.
 
class Program
{
        static void Main(string[] args)
        {
               var container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
              
               var export = container.GetExportedObject<Export2>("export2");
               export.Say();
 
               Console.ReadKey();
        }
}
 
[Export("export2")]
public class Export2
{
        public void Say()
        {
               Console.WriteLine("Export2 Say..");
        }
}
 
 
GetExport
 
만약 여러 개의 구성 요소의 인스턴스가 필요하다면 GetExport 메서드를 통해 Export 의 구성 정보를 가져오면 됩니다.
 
var exports = container.GetExport<Export1>();
exports.GetExportedObject().Say();
 
필요하다면 ExportAttribute 의 Contract Name 으로 질의(Query) 할 수 있습니다.
 
var exports = container.GetExport<Export2>("export2");
exports.GetExportedObject().Say();
 
아쉽게도 MEF Preview 4 까지 지원하던 C# 3.0 의 Expression 을 통해 질의하는 방법은 MEF Preview 5 이후 없어진 것이 아쉽네요.
 
GetExportedObjectOrDefault
 
일반적으로 MEF 에서는 질의(Query) 결과가 없을 경우 예외를 발생하게 되는데, GetExportedObjectOrDefault 메서드를 통해 결과가 없을 경우 Null 값으로 대체할 수 있습니다.
 
var obj = container.GetExportedObjectOrDefault<Export1>("A");
if( obj != null )
        obj.Say();
 
 
저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

[MEF] 9. Recomposition

.NET Framework 4.0/Managed Extensibility Framework | 2009/04/19 22:47 | Posted by 엄준일(땡초)

Recomposition

 

이전 포스트의 MEF 의 특징 중에 MEF 의 플러그인 모델(Plugin Model) 은 교체가 용이하다고 하였습니다. Composable Part 는 구성 요소로써 고유의 기능을 구현합니다. 그리고 MEF 는 각각의 Composable Part 를 조립하여 다양한 컴포넌트 또는 애플리케이션을 완성합니다.

 

어떠한 경우에는 특정한 구성 구성요소를 사용하다가 그것이 필요 없어질 경우 구성 요소를 언로드(Unload) 하거나 다른 구성요소로 교체할 필요가 있습니다.

 

MEF 의 이러한 유연함의 예를 들어보죠.

 

예를 들어, 어플리케이션의 로그(Log) 기능을 생각해볼 수 있습니다. 만약 어플리케이션이 동작하는 환경이 인터넷에 연결되지 않는 환경이라면 사용자의 컴퓨터 로컬에 로그를 기록하다가, 인터넷에 연결될 경우 외부 데이터베이스로 로그를 기록하는 시나리오를 가정할 수 있습니다. 그리고 이러한 기능 또는 요구 사항이 언제 변경될지도 모르는 일입니다.

 

일단 위의 시나리오를 구현하기 여러 가지 고려해야 할 사항이 있고, 기능 또는 요구 사항이 변경될 경우도 고려해야 합니다. 결국, MEF 는 이러한 변화에 굉장히 유연하게 대처할 수 있습니다.

 

MEF 에서 ImportAttribute AllowRecomposition 프로퍼티를 통해 구성 요소를 런타임(Runtime) 시 동적(Dynamic) 으로 교체할 수 있도록 합니다.

 

아래는 위의 예로 든 시나리오를 구현하기 위해 작성한 간단한 소스 코드 입니다.

 

ILogger 인터페이스

public interface ILogger

{

        void Write();

}

 

ILogger 인터페이스는 Write 메서드를 통해 로그를 기록할 수 있는 예입니다.

 

 

TextLogger 클래스

[Export(typeof(ILogger))]

public class TextLogger : ILogger

{

        public void Write()

        {

               Console.WriteLine("Logged TextLogger");

        }

}

 

 

DatabaseLogger 클래스

[Export(typeof(ILogger))]

public class DatabaseLogger : ILogger

{

        public void Write()

        {

               Console.WriteLine("Logged DatabaseLogger");

        }

}

 

 

LoggerContext 클래스

[Export]

public class LoggerContext

{

        [Import(AllowRecomposition=true)]

        public ILogger Context { get; set; }

 

        [ImportingConstructor]

        public LoggerContext(ILogger sender)

        {

        }

}

 

여기에서 ImportAttribute AllowRecomposition 프로퍼티의 값을 true 로 지정해 주었습니다. AllowRecomposition 프로퍼티가 true 일 경우 Imported 된 구성 요소를 동적(Dynamic)하게 교체할 수 있도록 합니다.

 

 

Main 어플리케이션

class Program

{

        static void Main(string[] args)

        {

               Program p = new Program();

               p.Run();

        }

 

        void Run()

        {

               var catalog    = new AggregateCatalog(new TypeCatalog(typeof(LoggerContext)));

 

               var container = new CompositionContainer(catalog);

                var batch = new CompositionBatch();

               var defaultPart = batch.AddPart(new TextLogger());

               container.Compose(batch);

 

               var obj = container.GetExportedObject<LoggerContext>();

 

               obj.Context.Write();

              

               batch = new CompositionBatch();

               batch.RemovePart(defaultPart);

               batch.AddPart(new DatabaseLogger());

               container.Compose(batch);

 

               obj.Context.Write();

        }

}

 

이 코드는 처음에 TextLogger 를 사용하다가, 필요 없어진 TextLogger 를 제거하고 DatabaseLogger 로 교체하는 코드입니다.

 

아래는 위의 소스 코드를 실행한 결과입니다.

 

[그림1] 소스 코드 실행 결과

 

 

Wow! Recomposition

 

사실 이러한 것이 기존에는 기능의 정의 또는 요구 사항에 따라 직접 구현할 수 있었지만, MEF 의 특징인 플러그인 모델(Plugin Model) 은 이러한 고민에 대해 좋은 방법을 제공해 줍니다. 개발자는 자신이 구현해야 할 기능에 더 충실하고, 비즈니스 로직에 대한 고민만을 하면 됩니다. 그리고 정책에 따라 그것을 집행하는 결정권을 가진 자는 구현된 구성 요소를 조립하고 교체하여 기존의 정적인(Static) 어플리케이션에게 동적인(Dynamic) 유연함을 제공합니다.

 

기존에 정적인 어플리케이션은 변화에 따라 유지 보수를 위해 지속적인 많은 리소스가 필요하였지만, 플러그인 모델(Plugin Model) 의 동적인 어플리케이션은 그러한 변화에 능동적으로 대처할 수 있는 큰 기쁨을 줄 수 있을 것입니다.

저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

'.NET Framework 4.0 > Managed Extensibility Framework' 카테고리의 다른 글

MEF Preview 6 공개  (0) 2009/07/20
[MEF] 10. Querying the CompositionContainer  (0) 2009/05/18
[MEF] 9. Recomposition  (1) 2009/04/19
[MEF] 8. Strongly Typed Metadata  (0) 2009/04/16
[MEF] 7. Exports and Metadata  (0) 2009/04/16
[MEF] 6. Lazy Exports  (0) 2009/04/13
Strongly Typed Metadata
 
지난 포스트의 [MEF] 7. Exports and Metadata를 통해 Export 의 Contract 에 Metadata 를 제공하는 방법을 알아보았습니다.
 
MetadataAttribute 을 선언하여 Export 의 Metadata 를 제공하는 방법입니다.
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Email")]
[ExportMetadata("Logging", true)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
Export 에 Metadata 를 제공해 주어서 무척 고맙지만, 위의 방법처럼 MetadataAttribute 를 선언하는 방법을 사용하기에 그다지 내키지 않는 구석이 있습니다.
 
아무래도 아래와 같은 이유 때문이겠죠?
 
l 메타데이터의 타입 불안정
l 빌드 시 오류를 해결이 어려움
l 사용상 모든 키값을 외우기 어렵고, 오타 발생 위험
 
위의 이유 때문에 Metadata 를 사용하기 위해 강력한 타입을 원하게 될 것입니다. 그리고 강력한 타입의 Metadata 를 사용하길 권장 드립니다.
 
 
Declaring Strongly Typed Metadata
 
Attribute 을 상속받아 Export 의 Metadata 를 Strongly Typed 으로 확장시키는 방법입니다.
 
아래의 Stringly Typed Metadata 를 위해 Attribute 클래스를 만드는 소스 코드 입니다.
 
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MessageSenderTypeAttribute : Attribute
{
        public MessageSenderTransport Transport { get; set; }
        public bool IsSecure { get; set; }
 
        public MessageSenderTypeAttribute() { }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport)
               : this(transport, false)
        {
        }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport, bool isSecure)
        {
               this.Transport = transport;
               this.IsSecure = IsSecure;
        }
}
 
public enum MessageSenderTransport
{
        Email,
        Phone,
        Sms
}
 
Strongly Typed Metadata 를 위한 Attribute 클래스를 완성하였으면, 이것을 그대로 ExportAttribute 과 함께 추가적으로 선언하면 됩니다.
 
그리고 ExportAttribute 을 선언한 코드에 위의 Attribute 특성을 부여합니다. 아래는 그 소스 코드의 일부입니다.
 
[Export(typeof(IMessageSender))]
[MessageSenderType(MessageSenderTransport.Email)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
[MessageSenderType(MessageSenderTransport.Phone, true)]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
[MessageSenderType(Transport=MessageSenderTransport.Sms, IsSecure=true)]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
그렇다면 아래와 같이 Metadata 를 Strongly Typed 으로 질의(Query) 할 수 있게 됩니다.
 
아래의 소스 코드는 지난 포스트의 소스 전체 소스 코드를 참고하십시오.
 
foreach (var export in program.Sender)
{
        if ((MessageSenderTransport)export.Metadata["Transport"] == MessageSenderTransport.Sms)
        {
               export.GetExportedObject().Say();
 
               if ((bool)export.Metadata["IsSecure"] == true)
               {
                       Console.WriteLine("Security message");
               }
        }
}
 
위의 소스 코드 실행 결과는 원하던 결과대로 다음과 같습니다.
 

[그림1] Strongly Typed 질의 결과
 
 
하지만 아직도 문제는 남아 있는 것 같아 보이네요. Strongly Typed 으로 Export Metadata 를 선언하였지만 여전히 질의(Query) 과정은 똑같은 문제점을 가지고 있습니다.
 
l 메타데이터의 질의(Query) 과정의 타입 불안정
l 빌드 시 오류를 해결이 어려움
l 사용상 모든 키값을 외우기 어렵고, 오타 발생 위험
 
 
More Strongly Typed Metadata Query
 
Metadata 를 질의(Query) 하기 위해 MEF 는 보다 강력하게 Import 하는 방법을 제공해 줍니다. 확장된 MetadataAttribute 에 인터페이스(Interface) 를 구현하도록 하여 Import 시에 인터페이스(Interface) 를 통한 Metadata 를 질의(Query) 하는 방법입니다.
 
우선 Attribute 에 사용되는 프로퍼티를 인터페이스로 선언합니다.
 
public interface IMessageSenderTypeAttribute
{
        bool IsSecure { get; }
        MessageSenderTransport Transport { get; }
}
 
단, 여기에서 반드시 Getter 만 선언하셔야 합니다. Setter 를 선언하시면 MEF 는 Setter 프로퍼티로 인해 유효하지 않는 Attribute 으로 인식하여 예외를 발생하게 됩니다.
 
아래의 소스 코드는 Metadata Attribute 클래스에 위의 인터페이스를 구현한 코드 입니다.
 
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MessageSenderTypeAttribute : Attribute, IMessageSenderTypeAttribute
{
        public MessageSenderTransport Transport { get; set; }
        public bool IsSecure { get; set; }
 
        public MessageSenderTypeAttribute() { }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport)
               : this(transport, false)
        {
        }
 
        public MessageSenderTypeAttribute(MessageSenderTransport transport, bool isSecure)
        {
               this.Transport = transport;
               this.IsSecure = IsSecure;
        }
}
 
 
이제 Export 의 MetadataView 로 질의할 수 있도록 MetadataView 의 인터페이스를 알려주도록 해야 합니다.
 
[Import(typeof(IMessageSender))]
ExportCollection<IMessageSender, IMessageSenderTypeAttribute> Sender { get; set; }
 
아래의 소스 코드는 MetadataView 를 통해 Strongly Typed 으로 Export Metadata 를 질의(Query) 하는 소스 코드 입니다.
 
[Import(typeof(IMessageSender))]
ExportCollection<IMessageSender, IMessageSenderTypeAttribute> Sender { get; set; }
                                             
static void Main(string[] args)
{
        Program program = new Program();
        program.Run();
 
        foreach (var export in program.Sender)
        {
               if (export.MetadataView.Transport == MessageSenderTransport.Email)
               {
                       export.GetExportedObject().Say();
               }
        }
}
 


저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

'.NET Framework 4.0 > Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 10. Querying the CompositionContainer  (0) 2009/05/18
[MEF] 9. Recomposition  (1) 2009/04/19
[MEF] 8. Strongly Typed Metadata  (0) 2009/04/16
[MEF] 7. Exports and Metadata  (0) 2009/04/16
[MEF] 6. Lazy Exports  (0) 2009/04/13
[MEF] 5. Catalog 사용  (0) 2009/04/09
Exports and Metadata
 
Export 에 Metadata 를 등록하고 제어하는 방법입니다. 지난 포스트에서 알 수 있듯이 Export 는 구성 요소간에 Contact 를 제공하여 이들을 구성(Composition) 할 수 있는 플러그인 모델(Plugin Model) 을 제공해 줍니다.
 
하지만 Contract 가 제공되어 구성 요소를 확장하고 구성하는 것은 매우 용이하다는 것을 알게 되었으나 Contract 로 인해 파생된 다양한 구성 요소를 어떻게 제어하느냐의 고민을 하게 됩니다. 즉, 다양한 구성 요소 가운데 내가 필요로 하는 구성 요소를 골라낼 수 있도록 Metadata 를 제공하여 구성 요소를 질의(Query)할 수 있도록 하는 것입니다.
 

예를 들어 아래와 같은 Export 구성 요소 중 어떻게 EmailMessageSender 로 Say() 를 호출할 것인가가 문제인 것이죠.

[Export(typeof(IMessageSender))]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
이런 경우, 원초적인 방법으로 리플랙션(Reflection) 을 이용하여 Type 검사를 통해 EmailMessageSender 를 골라내서 사용하면 되지만, 직접적으로 Type 을 검사하기 위해서는 Tightly Coupling 이 발생하여 결국 유연한 플러그인 모델을 구현하기 위해 아무런 도움이 되지 않습니다.
 
이러한 방법을 해소하기 위해 또 다른 우회 방법은 리플랙션을 통해 Modules Name 으로 비교하는 방법이지만, 이것 또한 플러그인 모델에서 구성 요소가 교체 되었을 경우를 생각하면 전혀 대응할 수 없는 방법입니다.
 
MEF 에서는 이런 문제를 해소하기 위해 Contract 에 Metadata 를 제공하며 정적/동적인 방법을 제공해 줍니다.
 
 
Attaching Metadata to an Export
 
Export 에 Metadata 를 제공해 주기 위해서 MEF 에서는 ExportMetadataAttribute 특성을 제공해 줍니다.
 
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Field,
                    AllowMultiple = true, Inherited = false)]
public ExportMetadataAttribute(string name, object value)
 
왜 ExportMetadata 클래스는 sealed 로 선언이 되었나요?
 
일반적으로 sealed 로 클래스를 봉인할 경우 리플랙션의 성능이 향상됩니다.
 
 
ExportMetadata 는 키와 값(Key/Value) 을 지정하여 Contract 에 Metadata 를 제공해 줄 수 있습니다. 그리고 하나의 Export 에 여러 개의 Metadata 를 제공할 수 있도록 AllowMultiple 을 지원합니다.
 
Metadata 는 여러 가지의 정보를 포함할 수 있습니다. Export 가 제공하는 기능의 특성을 기술할 수 있으며, 예를 들어, 권한, 로깅, 구성 요소 분류 방법 등이 될 수 있을 것입니다.
 
아래의 소스 코드는 Metadata 를 지정하는 예를 보여줍니다.
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Email")]
[ExportMetadata("Logging", true)]
public class EmailMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import EmailMessageSender");
        }
}
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Phone")]
public class PhoneMessageSneder : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import PhoneMessageSneder");
        }
}
 
[Export(typeof(IMessageSender))]
[ExportMetadata("SenderType", "Sms")]
public class SmsMessageSender : IMessageSender
{
        public void Say()
        {
               Console.WriteLine("Import SmsMessageSender");
        }
}
 
 
 
Constraining Imports statically
 
MEF Preview 5 에서는 ImportRequiredMetadataAttribute 클래스가 제거되었습니다.
 
MEF Preview 4 에서는 선언적인 방법으로 ImportRequiredMetadataAttribute 를 통해 Metadata 를 질의할 수 있었으나, MEF Preview 5 에서는 ImportRequiredMetadataAttribute 클래스가 제거되었습니다.
 
아마도 추측으로는 ImportRequiredMetadataAttribute 를 선언 시에 여러 개의 구성 요소가 검색될 경우 Exception 이 발생하는데, Exception 을 최소화 하고자 제거가 된 것 같습니다.
 
혹시 Statically 한 방법으로 ImportRequiredMetadataAttribute 에 대응되는 클래스를 아시면 저에게 알려주세요.
 
 
Constraining Imports dynamically
 
이 방법은 Export 의 ExportMetadata 를 런타임 시에 질의(Query) 하는 방법입니다.
 
Import 시 ExportCollection<T> 을 사용하여 Export 를 수동적으로 질의(Query) 하는 방법입니다. 이 방법은 지난 포스트의 Lazy Load 를 이용한 방법으로 단지 Metadata 만 질의(Query) 뿐이고, 객체의 생성에 대한 판단은 필요 시에만 GetExportedobject() 메서드를 이용하여 생성할 수 있습니다.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
 
namespace MetadataSample
{
        class Program
        {
               [Import(typeof(IMessageSender))]
               ExportCollection<IMessageSender> Sender { get; set; }
 
               static void Main(string[] args)
               {
                       Program program = new Program();
                       program.Run();
 
                       foreach (var export in program.Sender)
                       {
                              if ((string)export.Metadata["SenderType"] == "Email")
                                      export.GetExportedObject().Say();
 
                              if (export.Metadata.ContainsKey("Logging") &&
                                      (bool)export.Metadata["Logging"] == true)
                                      Console.WriteLine("Logged success");
                       }
               }
 
               void Run()
               {
                       var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
 
                       var container = new CompositionContainer(catalog);
                       var batch = new CompositionBatch();
                       batch.AddPart(this);
                       container.Compose(batch);
               }
 
               public interface IMessageSender
               {
                       void Say();
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Email")]
               [ExportMetadata("Logging", true)]
               public class EmailMessageSender : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import EmailMessageSender");
                       }
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Phone")]
               public class PhoneMessageSneder : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import PhoneMessageSneder");
                       }
               }
 
               [Export(typeof(IMessageSender))]
               [ExportMetadata("SenderType", "Sms")]
               public class SmsMessageSender : IMessageSender
               {
                       public void Say()
                       {
                              Console.WriteLine("Import SmsMessageSender");
                       }
               }
        }
}
 
 
실행 결과는 예상할 수 있듯이 아래와 같이 런타임 시에 결과를 보여줍니다.


저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

'.NET Framework 4.0 > Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 9. Recomposition  (1) 2009/04/19
[MEF] 8. Strongly Typed Metadata  (0) 2009/04/16
[MEF] 7. Exports and Metadata  (0) 2009/04/16
[MEF] 6. Lazy Exports  (0) 2009/04/13
[MEF] 5. Catalog 사용  (0) 2009/04/09
[MEF] 4. Import 선언  (0) 2009/04/07

[MEF] 6. Lazy Exports

.NET Framework 4.0/Managed Extensibility Framework | 2009/04/13 00:38 | Posted by 엄준일(땡초)
일반적인 Exports
 
Composable Part 를 구성하는 동안 특정 Part 에 대한 요청으로 객체 내부의 객체가 필요할 때가 있습니다. 만약 객체가 이런 연관 관계가 있을 경우 객체 내부에 ImportAttribute 을 선언하여 외부 Part 를 Import 할 수 있습니다. 이런 경우는 객체 내부에 Export 간의 Related 관계를 갖게 됨으로써 자동적으로 객체를 초기화할 수 있게 됩니다. ([MEF] 3. Export 선언 참조)
 
일반적인 Export 를 통해 객체 내부에 Import 를 선언하는 방법입니다.
 
class Program
{
        static void Main(string[] args)
        {
               Program p = new Program();
               p.Run();
        }
 
        void Run()
        {
               var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
 
               var container = new CompositionContainer(catalog);
               var batch = new CompositionBatch();
               batch.AddPart(this);
 
               container.Compose(batch);
 
               var obj = container.GetExportedObject<EmailMessageSender>();
               obj.Sender.Say();
        }
}
 
[Export]
public class MessageSender
{
        public void Say()
        {
               Console.WriteLine("Say Method");
        }
}
 
[Export]
public class EmailMessageSender
{
        [Import]
        public MessageSender Sender { get; set; }
}
 
 
Lazy Exports
 
하지만 비용이 비싼 객체를 생성할 경우 위의 [일반적인 Exports] 는 매우 비효율적일 수 있습니다. 객체 내부의 Import Object 를 사용하지 않을 가능성이 크거나, 객체의 생성 비용이 클 경우는 Lazy Export 를 고려해야 합니다. 그리고 내부 객체가 동기적(Synchronicity)이 아닌 비동기성(Asynchronism) 성격의 객체일 경우에도 반드시 필요한 기법입니다.
 
이런 경우 내부 객체의 생성을 선택적, 수동적으로 제어하여, 게으른 초기화(Lazy Initialization) 를 할 필요가 있습니다.
 
아래의 굵은 표시의 코드가 Lazy Export 로 제어하는 코드입니다.
 
class Program
{
        static void Main(string[] args)
        {
               Program p = new Program();
               p.Run();
        }
 
        void Run()
        {
               var catalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
 
               var container = new CompositionContainer(catalog);
               var batch = new CompositionBatch();
               batch.AddPart(this);
 
               container.Compose(batch);
 
               var obj = container.GetExportedObject<EmailMessageSender>();
               obj.Sender.Say();
 
               var lazyObj = container.GetExportedObject<EmailMessageSenderByLazyExport>();
               lazyObj.Sender.GetExportedObject().Say();
        }
}
 
[Export]
public class MessageSender
{
        public void Say()
        {
               Console.WriteLine("Say Method");
        }
}
 
[Export]
public class EmailMessageSender
{
        [Import]
        public MessageSender Sender { get; set; }
}
 
[Export]
public class EmailMessageSenderByLazyExport
{
        [Import]
        public Export<MessageSender> Sender { get; set; }
}
 
ImportAttribute 의 프로퍼티를 Export<T> 타입으로 변경하면 내부 객체를 GetExportedObject() 메서드를 이용하여 지연하여 객체를 생성할 수 있습니다.
저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

'.NET Framework 4.0 > Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 8. Strongly Typed Metadata  (0) 2009/04/16
[MEF] 7. Exports and Metadata  (0) 2009/04/16
[MEF] 6. Lazy Exports  (0) 2009/04/13
[MEF] 5. Catalog 사용  (0) 2009/04/09
[MEF] 4. Import 선언  (0) 2009/04/07
[MEF] 3. Export 선언  (0) 2009/03/29

[MEF] 5. Catalog 사용

.NET Framework 4.0/Managed Extensibility Framework | 2009/04/09 21:31 | Posted by 엄준일(땡초)
Catalog
 
Catalog 는 동적으로 Composable Part 를 찾아 Container 에 등록합니다. Composable Part 는 ExportAttribute 으로 Contract 를 선언할 수 있는데 개별적으로 일일이 Composable Part 를 등록하는 것은 너무나 큰 반복 작업이 될 수 있지만, MEF 의 Catalog 로 쉽게 자동적으로 등록을 할 수 있습니다.
 
Catalog 를 사용하지 않는 Composable Part 의 등록는 매우 고단한 작업입니다. 아래의 예제 소스 코드는 수동으로 Composable Part 를 등록하는 방법입니다.
 
 
예제에서는 단지 하나의 Export 를 등록하였지만 실제로 이러한 Composable Part 는 수십에서 수백개를 넘을 수 도 있습니다. 많은 Composable Part 를 동적이고 또는 자동적으로 등록해 주기 위해서 Catalog 를 사용하면 쉽게 해결할 수 있습니다.
 
 
이 외에도 Composable Part 를 Catalog 에 등록하는 여러 가지 방법이 있습니다. 아래는 MEF 에서 지원하는 Catalog 입니다.
 
 
Assembly Catalog
 
Assembly Catalog 는 닷넷 어셈블리를 Catalog 로 사용할 수 있습니다.
 
public AssemblyCatalog(string codeBase)
public AssemblyCatalog(Assembly assembly)
 
var catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
 
AssemblyCatalog 의 시그너처(Signature) 에서 보듯이 CodeBase 로 Catalog 를 사용할 수 있지만, 실버라이트에서는 CodeBase 를 지원하지 않으므로 실버라이트에서는 CodeBase 로 AssemblyCatalog 를 사용할 수 없습니다.
 
 
Directory Catalog
 
Directory Catalog 를 사용하면 특정 폴더의 어셈블리를 모두 검색하여 Composable Part 를 사용할 수 있습니다.
 
public DirectoryCatalog(string path)
public DirectoryCatalog(string path, string searchPattern)
 
Directory Catalog 는 내부적으로 System.IO 의 Directory.GetFiles() 메서드를 사용하여 지정한 폴더의 모든 어셈블리를 검색하도록 하여 Composable Part 를 가져오도록 합니다.
 
var catalog = new DirectoryCatalog("Addins");
 
기본적으로 *.DLL 확장자의 어셈블리만 검색하도록 설정되어있고, 어셈블리 파일의 검색 패턴을 지정하여 검색할 수 있습니다.
 
var catalog = new DirectoryCatalog("Addins", "*.exe");
 
아마 MEF 의 예제중에 XFileExplorer 처럼 특정 폴더에 어셈블리를 복사해 넣으면 자동으로 Composable Part 가 로드되는 예제가 있는데, 이것은 DirectoryCatalog 가 자동으로 처리해 주는 것이 아니라 FileSystemWatcher 클래스를 이용하여 직접 구현해야 합니다.
 
FileSystemWatcher 로 파일의 변경이 감지되면 DirectoryCatalog 의 Refresh() 메서드를 이용하여 폴더를 재검색 또는 새로고침을 할 수 있습니다.
 
 
Aggregation Catalog
 
Aggregation Catalog 는 복합적인 Catalog 를 사용할 수 있도록 합니다. 만약 Assembly Catalog 와 Directory Catalog 등을 동시에 사용하여 Composable Part 를 가져오도록 하려면 Aggregation Catalog 를 사용하면 됩니다.
 
public AggregateCatalog(params ComposablePartCatalog[] catalogs)
 
Aggregation Catalog 의 생성자는 params 로 인자를 받을 수 있습니다.
 
var catalog = new AggregateCatalog(   new AssemblyCatalog(Assembly.GetExecutingAssembly()),
                                                                            new DirectoryCatalog("."));
 
 
Type Catalog
 
Type Catalog 는 Composable Part 를 Type 으로 개별적으로 사용하도록 합니다. 가장 단순하고 무식(?)한 방법일 수도 있지만 세세한 제어가 필요할 때 사용 가능한 Catalog 일 것 같습니다.
 
public TypeCatalog(params Type[] types)
public TypeCatalog(IEnumerable<Type> types)
 
var catalog = new TypeCatalog(typeof(EMailMessageSender), typeof(PhoneMessageSender));
 
 
Using catalog with a Container
 
이제 Catalog 를 Composable Container 에서 Composition 하도록 넘겨주는 일만 남았습니다.
 
public CompositionContainer()
public CompositionContainer(params ExportProvider[] providers)
public CompositionContainer(ComposablePartCatalog catalog, params ExportProvider[] providers)
 
var container = new CompositionContainer(catalog);
 
저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License

'.NET Framework 4.0 > Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 7. Exports and Metadata  (0) 2009/04/16
[MEF] 6. Lazy Exports  (0) 2009/04/13
[MEF] 5. Catalog 사용  (0) 2009/04/09
[MEF] 4. Import 선언  (0) 2009/04/07
[MEF] 3. Export 선언  (0) 2009/03/29
[MEF] 2. Parts 와 Contracts 선언  (0) 2009/03/22