お約束:Windows 8 CP時点の内容です。正式版では変わってるかもしれません。
WinRTのMEFってCompositionContainerクラスがいなくなってCompositionServiceクラスでSatisfyImportsOnceメソッド使って対象クラスに何かをImportするということしかできないっぽいです。なのでMVVM Light 4のDIコンテナにMEF使ってやろうと思ったらいきなり挫折してしまいましたorz
それじゃぁ悔しいので、無理やりCompositionServiceクラスを使った状態でIServiceLocatorインターフェースを実装してみました。ちなみにMVVM Light 4に添付されてるMicrosoft.Practices.ServiceLocation.dllを使います。
実装
ということでさくっと実装してみました。SatisfyImportsOnceメソッドにRegistrationBuilderのインスタンスを渡せば実行時にImportの挙動とかをいじれるみたいなので、それを使ってごにょごにょっとしてます。
namespace SampleImplementation { using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.Primitives; using System.ComponentModel.Composition.Registration; using System.Linq; using System.Reflection; using Microsoft.Practices.ServiceLocation; public class MefServiceLocator : IServiceLocator { private CompositionService service; public MefServiceLocator(params ComposablePartCatalog[] catalogs) { // カタログからCompositionServiceを作成 var catalog = new AggregateCatalog(catalogs); this.service = catalog.CreateCompositionService(); } private IEnumerable<object> DoGetAllInstances(Type serviceType) { // ManyValueHolder<T>型を作成 var holderType = typeof(ManyValueHolder<>).MakeGenericType(serviceType); // ValuesプロパティにImportManyをつけてるイメージ var b = new RegistrationBuilder(); b.ForType(holderType) .ImportProperties(p => true, (p, c) => c.AsMany(true)); // ManyValueHolder<T>型のインスタンスを作成してImport var holderInstance = Activator.CreateInstance(holderType); this.service.SatisfyImportsOnce(holderInstance, b); // Valuesプロパティの値を取得して返却 var valuePropertyInfo = holderType.GetTypeInfo().GetDeclaredProperty("Values"); return (IEnumerable<object>) valuePropertyInfo.GetValue(holderInstance); } private object DoGetInstance(Type serviceType, string key) { // SingleValueHolder<T>型を作成 var holderType = typeof(SingleValueHolder<>).MakeGenericType(serviceType); // ValueプロパティにImport属性をつけてるイメージ // コントラクト名が指定されている場合はそれも追加する var b = new RegistrationBuilder(); b.ForType(holderType) .ImportProperties(p => true, (p, c) => { if (!string.IsNullOrEmpty(key)) { c.AsContractName(key); } }); // SingleValueHolder<T>型のインスタンスを作成してImport var holderInstance = Activator.CreateInstance(holderType); this.service.SatisfyImportsOnce(holderInstance, b); // Valueプロパティの値を取得して返却 var valuePropertyInfo = holderType.GetTypeInfo().GetDeclaredProperty("Value"); return valuePropertyInfo.GetValue(holderInstance); } public IEnumerable<TService> GetAllInstances<TService>() { return DoGetAllInstances(typeof(TService)).Cast<TService>(); } public IEnumerable<object> GetAllInstances(Type serviceType) { return DoGetAllInstances(serviceType); } public TService GetInstance<TService>(string key) { return (TService)DoGetInstance(typeof(TService), key); } public TService GetInstance<TService>() { return (TService) DoGetInstance(typeof(TService), null); } public object GetInstance(Type serviceType, string key) { return DoGetInstance(serviceType, key); } public object GetInstance(Type serviceType) { return DoGetInstance(serviceType, null); } } /// <summary> /// 単一のインスタンスをImportするためのクラス /// </summary> /// <typeparam name="T"></typeparam> class SingleValueHolder<T> { public T Value { get; set; } } /// <summary> /// 複数のインスタンスをImportするためのクラス /// </summary> /// <typeparam name="T"></typeparam> class ManyValueHolder<T> { public IEnumerable<T> Values { get; set; } } }
こんな感じで実装完了!通したテストは以下のような感じ。
namespace MefServiceLocatorImpl.Test { using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Linq; using System.Reflection; using Microsoft.VisualStudio.TestTools.UnitTesting; using SampleImplementation; [TestClass] public class MefServiceLocatorTest { private MefServiceLocator locator; [TestInitialize] public void Initialize() { this.locator = new MefServiceLocator( new AssemblyCatalog(typeof(MefServiceLocatorTest).GetTypeInfo().Assembly)); } [TestCleanup] public void Cleanup() { this.locator = null; } [TestMethod] public void InitTest() { Assert.IsNotNull(this.locator); } [TestMethod] public void SingleExportTest() { var target = locator.GetInstance<ExportTarget>(); Assert.IsNotNull(target); } [TestMethod] public void NamedSingleExportTest() { var target = locator.GetInstance<NamedExportTarget>("Sample"); Assert.IsNotNull(target); } [TestMethod] [ExpectedException(typeof(CompositionException))] public void NamedSingleExportMissingTest() { locator.GetInstance<NamedExportTarget>("MissingName"); } [TestMethod] public void ManyExportTest() { var targets = locator.GetAllInstances<IManyExportTarget>(); Assert.IsNotNull(targets); Assert.AreEqual(targets.Count(), 3); } } [Export] public class ExportTarget { } [Export("Sample")] public class NamedExportTarget { } public interface IManyExportTarget { } [Export(typeof(IManyExportTarget))] public class ManyExportTarget1 : IManyExportTarget { } [Export(typeof(IManyExportTarget))] public class ManyExportTarget2 : IManyExportTarget { } [Export(typeof(IManyExportTarget))] public class ManyExportTarget3 : IManyExportTarget { } }
これでオールグリーン!ということでMVVM Light 4のSimpleIoCじゃなくてMEFを使えるようになりそうです。