[MEF] 7. Exports and Metadata

Managed Extensibility Framework 2009. 4. 16. 01:11 Posted by POWERUMC
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");
                       }
               }
        }
}
 
 
실행 결과는 예상할 수 있듯이 아래와 같이 런타임 시에 결과를 보여줍니다.


'Managed Extensibility Framework' 카테고리의 다른 글

[MEF] 9. Recomposition  (1) 2009.04.19
[MEF] 8. Strongly Typed 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

안녕하세요. 이번에 Visual Studio Team System 2010 공식 블로그에 새롭게 참여하게 된 LazyDeveloper.Net의 kkongchi라고 합니다. Better Code 시리즈를 통해서 Code Analysis, Unit Test 등에 대한 포스팅을 해보도록 하겠습니다. 부족한 부분 많이 지적해 주시길 바랍니다.

 

TDD?

eXtreme Programming의 창시자 중 하나인 Kent Beck의 eXtreme Programming explained라는 책을 보면 Test를 작성하는 방법에 대해서 이렇게 기술하고 있습니다.

  • If the interface for a method is at all unclear, you write a test before you write the method. (메서드의 인터페이스가 클리어하지 않다면, 메서드를 작성하기 전에 테스트를 먼저 작성해라)

  • If the interface is clear, but you imagine that the implementation will be the least bit complicated, you write a test before you write the method. (메서드의 인터페이스가 클리어하더라도 당신이 생각하기에 구현이 조금 복잡할 것 같다면, 메서드를 작성하기 전에 먼저 테스트를 작성해라)

  • 이런 eXtreme Programming의 Test-Before-Writing 전략을 개발 프로세스에 전면적으로 도입하는 것을 Test Driven Development, 즉 TDD라고 합니다. TDD에 관한 위키피디아 페이지에서 소개하는 TDD의 개발 사이클은 다음과 같습니다. “Red, Green, Refactor”라고 표현하기도 합니다.

    다들 아시다시피, Visual Studio에서는 2005 버전에서부터 Team System의 일부로써 Testing Framework을 제공하고 지원해왔습니다. 그리고 드디어 이번 Visual Studio Team System 2010에서는 완벽하게 TDD의 개념이 Visual Studio Team System안으로 녹아 들어가게 된 것 같습니다. 바로 새롭게 추가된 기능 “Generate” 기능을 통해서, Test를 먼저 작성한 후에 그 Test로부터 코드를 자동으로 Generate해주는 기능이 추가된 것입니다.

     

    TDD Development in VSTS 2010 by “Generate”

    지난 11월에 나온 Visual Studio 2010 and .NET Framework 4.0 Training Kit의 Lab을 통해서 이 기능에 대해서 좀 더 자세히 알아보도록 하겠습니다.

     

    당연히 먼저 테스트를 작성하는 것부터 시작합니다. 하지만, 아직 만들어지지 않은 클래스이기 때문에 아래 그림처럼 빨간색 물결 라인으로 경고가 뜹니다. 여기서 마우스 오른쪽 버튼을 눌러보면, 새로운 “Generate” 기능을 볼 수가 있습니다.

    Generate class를 선택하면 같은 프로젝트에 Class가 추가됩니다. 하지만 Generate other..를 선택하면 아래와 같은 팝업 윈도우가 나옵니다. 클래스를 만들 수 있는 Wizard 개념이라고 보시면 되겠습니다.

    이 Wizard를 통해서 클래스의 Access 한정자, Type, 그리고 파일을 만들 프로젝트까지 설정을 할 수가 있습니다. 이 과정을 통해서 우리는 완벽하게 Test로부터 시작해서 뼈대 코드를 만들어 낼 수가 있습니다. 아래 그림처럼 말이죠..

    이제 이 자동으로 만들어진 뼈대 코드에 구현을 추가하게 되면 여러분들은 다음과 같이 Test를 통과했다는 기분 좋은 화면을 보실 수 있으실 것입니다.


    위에서 보신 Demo Code의 시연은 http://channel9.msdn.com/shows/10-4/10-4-Episode-5-Code-Focused-in-Visual-Studio-2010/#Page=4 에서 Video로도 감상하실 수 있고, http://www.microsoft.com/downloads/details.aspx?FamilyID=752CB725-969B-4732-A383-ED5740F02E93&displaylang=en 에서 Lab Document와 소스 코드도 얻으실 수 있습니다.
     

    지금까지 보신 것처럼 앞으로 출시될 VSTS 2010에서는 IDE 자체에서 완벽한 TDD 지원 기능이 통합되었습니다. TDD가 만능의 도구는 아닙니다. 하지만, 적어도 개발자가 자신의 코드에 대한 이해도가 통상적인 개발 방법보다는 훨씬 크고 깊을 것이라 기대합니다. 어설픈 문서보다는 잘 만들어진 테스트 코드들이 오히려 실제 구현 코드를 이해하는 데 더 도움이 되는 경우도 많습니다. Visual Studio Team System 2010은 효율적인 Test Driven Development를 가능하게 해주는 최고의 도구가 될 것 같습니다.

    부족한 글 읽어주셔서 감사하고, 많은 의견 부탁 드립니다.

    [C# 4.0] Generic Covariance And Contra Variance

    C# 2009. 4. 13. 20:22 Posted by 알 수 없는 사용자
    처음에 이 용어를 보고 정확하게 무슨 뜻인지 잘 몰랐습니다.
    그래서 이곳 저곳 검색해 본 결과, 좋은 예제를 하나 소개해 드립니다.
    참고 : http://playdotnet.spaces.live.com/blog/cns!7F811570C85CF4EA!4600.entry
    public class Employee { }
    public class Manager:Employee { }
    
    Employee employee = new Employee();
    Manager manager = new Manager();
    //Covariance.
    Employee manager = new Manager(); 
    
    위 예제의 마지막 코드를 보면 Manager 타입의 객체가 Employee 타입의 Manager 변수로 대입 되었습니다. 생각해 보건대, 매니져도 월급을 받는 직원이므로 당연한 것이지만, 프로그래밍 세계에서는 이것을 Covariance 로 부르더군요. 저도 알게 모르게 이런 코드를 많이 사용했었습니다.

    그럼 이건 어떤가요?
    public void GetSalary(Employee employee) { }
    Employee employee = new Employee();
    GetSalary(employee);
    Manager manager = new Manager();
    
    //Contra variance.
    GetSalary(manager);
    
    이번 코드에서는 직원에 월급을 주고 있습니다. 매니져도 직원이므로 월급을 주는 것이 당연합니다. 이런 경우를 Contra Variance라 하네요. 참 재미있는 용어들입니다. 언뜻 생각하면 참 당연한 말인데요. ^^;  
    참 그리고 보니, InVariance 도 있습니다.
    //AppointNewManager() 는 Manager 타입의 변수를 return 합니다.
    Product product = AppointNewManager();
    
    새로운 매니저를 제품으로 볼 수는 없지요. 가끔 그러고 싶은 생각도 있습니다. ㅋㅋㅋ

    C# 버젼 별로 variance의 의미를 종합해 보면은요,

    Variance in Arrays (C# 1.0)

    Value 타입의 Integer 타입의 Array는 당연히 Double Type의 변수를 Element로 사용할 수 없습니다. Invariance 합니다. 
    Int32[] numbers = new Double[3];
     
    Reference 타입의 경우 아래  코드는 Covariance합니다. 단 매니져가 직원이어야 합니다., 쉽게 말하면 매니져의 부모 타입이 Employee 이지요, 근데 가끔 프리랜서도 있을수 있으므로 주의!
    Employee[] employees = new Manager[3];
      
    public class Animal { }
    public class Lion:Animal { }
    Animal[] animals = new Lion[3];
     
    위 예제는 Covariance 하지만, 만약 이런경우에는 
      
    public class Deer Animal }
    Animal[] animals = new Lion[3];
    animals[0] = new Deer();

    동물에 사자도 들어 갈수 있고, 사슴도 가능하지만(Covariance), 실제 코드에서는 문제가 발생할 수 있습니다.  animals array 가 사자 우리라고 생각한다면, 들어간 사슴은 잡아 먹히겠지요. T.T

    Variance in Delegate-Member Association (C# 2.0)
     
    Func<Employee> generateEmployee = AppointNewManager;
    Action<Manager> salaryAction = GetSalary;
     
    Delegate Func<T> 는 인자는 없고, return type은 타입 T입니다. 
    AppointNewManager는 Manager 타입을 return 하고 Manager 타입의 부모는  당연히 employee입니다. 즉 Covariance 합니다
     
    Delegate Action<T> 는 타입 T의 변수를 인자로 받고, return 값은 없습니다.
    GetSalary는 Manager 타입의 변수를 인자로 받는데, 이것 역시 매니져는 직원이므로 월급을 받을 수 있고, 아까 전에 언급한대로 Contra Variance라 부릅니다.

    Variance in Generic Delegate (C# 3.0)
     
    Func<Manager> generateManager = AppointNewManager;
    Func<Employee> generateEmployee = genrateManager;

    Action<Employee> salaryAction = GetSalary;
    Action<Manager> salary = salaryAction;
     
    C# 2.0 예제와는 달리 이 예제들은 Invariance 합니다. Generic delegate에 대해서는 compile error가 발생합니다. Generic 의 업격한 타입 검사가 이런 문제는 발생시키는 건가요? 
    C# 4.0 에서는 이런 문제를 해결하기 위해 Generic Covariance And Contra Variance 가 등장합니다.

    Variance for Generic Delegates (C# 4.0)

    Covariance 를 지원하려면. OUT 
    public delegate Func1<out T>();
     
    Func1<Manager> generateManager = AppointNewManager;
    Func1<Employee> generateEmployee = generateManager;
     
    Contra Variance 의 경우는 IN
    public delegate void Action1<in T>(T a);
    Action1<Employee> salaryAction = GetSalary;
    Action1<Manager> salary = salaryAction;

    약 처음부터 이 부분을 보셨다면, C#에 익숙하지 않은 저 같은 사람은 이해 하기 가 많이 어려웠을겁니다. 그래서 공부하면서 정리한 내용과 함꼐,  가장 쉽다고 생각한 블로그에서 인용 한 것인데,  제가 잘 이해했는지 모르겠네요.  말로 설명하는 것 보다, 하나의 구체적인 예를 들고 적용해 보면 더 확실히 의미가 다가올 것 같습니다. 제 노트북에서는 Virtual PC 돌아가는 것도 상당히 버거워서 실제 컴파일은 해 보지도 못 했답니다. 양해부탁드립니다. 





    'C#' 카테고리의 다른 글

    Welcome to Dynamic C#(2) - Wanna be a polyglot.  (2) 2009.05.17
    Welcome to Dynamic C#(1) - 첫만남.  (1) 2009.05.04
    [C# 4.0] New Extension Method “Zip”  (1) 2009.04.08
    [C# 4.0] Duck Typing  (6) 2009.04.06
    [C# 4.0] Named and Optional Parameters  (1) 2009.04.06