MEFで独自のスコープを定義する

こんにちは、Habrの住民。

Managed Extensibility Framework別名MEFは、派手なAutofacsや他のStructureMapsのファンが言うように、アプリケーション内の構成を整理するためのシンプルで直感的な方法です。 そして、MEFの長所と短所について、尊敬されているRazaz長々と議論した後、このコンテナで独自の可視領域を定義する可能性を示したいと思います。





ご存じのように、MEFには2つのスコープしかありません-共有(コンテナ全体に1つのインスタンス)と非共有(各エクスポートリクエストの新しいインスタンス)。 Unityの後にこのコンテナを研究する人たちの最初の質問の1つは、「スレッドごとの可視性はどこにあるのでしょうか?」です。 または、WCFサービス開発者の場合、「呼び出しごと」。

構成タスクの一部としてなぜこれが必要なのかという質問には入らずに、これらの可視性ポリシーの実装の簡単な例を一般的な方法で示します。



作成の段階や技術的な詳細について読みたくない人のために、 ここでコードを手で触ることができ、 ここで -パッケージの形でそれを拾います。



これはMEF 2.0でのみ機能します。その理由-以下。



だから。

まず、一般的な方法で問題を提起してみましょう。

「エクスポートリクエストには何らかのコンテキストがあります。 このリクエストの時点で、同じコンテキストに同じエクスポートインスタンスを、異なるコンテキストに異なるインスタンスを与える必要があります。



私はあなたのことは知りませんが、すぐにこの中にキーが私たちのコンテキストである「キーバリュー」という普通の辞書を見ました。 もちろん、ディクショナリはコンテナ全体に対応する必要があるため、Sharedですが、これは後で設定します。



public abstract class AffinityStorage<TExport, TAffinity> where TExport : class { private ConcurrentDictionary<TAffinity,TExport> _partStorage = new ConcurrentDictionary<TAffinity,TExport>(); internal TExport GetOrAdd(TAffinity affinity, Func<TExport> creator) { var t = _partStorage.GetOrAdd(affinity, (a) =>creator()); return t; } internal void RemoveAffinity(TAffinity affinity) { TExport val; _partStorage.TryRemove(affinity, out val); } }
      
      







ここで説明することはおそらくないでしょう。指定されたコンテキストのエクスポートを要求するたびに、不必要にオブジェクトを作成しないようにファクトリメソッドを渡すという明らかな事実にのみ注意を払います。



しかし、このファクトリメソッドはどこで入手できますか? おそらく彼自身の輸入品で、彼は完全な部分を返すべきであることを忘れないでください。

これを行うには、MEFの機能を利用して、パーツのインスタンスを「遅延」形式で返します。 そして、コンテキストを決定するために、実際にラッパーとして、受信ポリシーという別のクラスを作成します。 彼女は非共有の視点にいます レイジーエクスポートは、リクエストごとに新しくなければなりません(作成するかどうかにかかわらず、ストレージは理解します)。



  public abstract class Policy<TExport, TAffinity> where TExport : class { private readonly AffinityStorage<TExport, TAffinity> _storage; [Import(AllowRecomposition = true, RequiredCreationPolicy = CreationPolicy.NonShared)] private Lazy<TExport> _lazyPart; private bool _wasCreated; private int _wasDisposed; protected abstract TAffinity GetAffinity(); protected Policy(AffinityStorage<TExport, TAffinity> storage) { _storage = storage; } private TExport GetExportedValue() { _wasCreated = true; return _storage.GetOrAdd(GetAffinity(), () => _lazyPart.Value); } protected void DestroyAffinity(TAffinity affinity) { var wasDisposed = Interlocked.CompareExchange(ref _wasDisposed, 1, 0); if (_wasCreated && wasDisposed == 0) { _storage.RemoveAffinity(affinity); } } public static implicit operator TExport(Policy<TExport, TAffinity> threadPolicy) { return threadPolicy.GetExportedValue(); } }
      
      







ここに、見てのとおり、次のものがあります。



最後の点について説明します。

AffinityStorageクラスの記述以来、エクスポートのインスタンスの作成と保存を制御する場合、それらのクリーニングも管理する必要があることが明らかになりました。 インスタンス自体のクリーニングの問題は、一般にIoCコンテナーにとって非常に苦痛です。要するに、問題は、コンテナーが作成したエクスポートを単にピックアップして破棄できないことです。 彼は、このエクスポートがどこにあり、作成後にどのように使用されるかを知りません。 したがって、部品のクリーニングのタスクはユーザーにあります。 私たちの場合、パーツ自体の使用方法を考えることなく、コンテキストの清算時にコンテキストにバインドされたリポジトリをクリアします。

また、コンテキストの清算の瞬間は、ポリシーの最終的な実装によって決定されます。



最終的にこの最終実装を作成します-ストリーム用です。



  [Export(typeof(ThreadStorage<>))] [PartCreationPolicy(CreationPolicy.Shared)] internal sealed class ThreadStorage<TExport> : AffinityStorage<TExport, Thread> where T : class { } [Export(typeof (ThreadPolicy<>))] [PartCreationPolicy(CreationPolicy.NonShared)] public sealed class ThreadPolicy<TExport> : Policy<TExport, Thread> where T : class { protected override Thread GetAffinity() { return Thread.CurrentThread; } [ImportingConstructor] internal ThreadPolicy( [Import(RequiredCreationPolicy = CreationPolicy.Shared)] ThreadStorage<TExport> storage) : base(storage) { } }
      
      







これは、オープンジェネリック型をエクスポートとしてサポートするMEF 2.0でのみ機能します。 コントラクトの指定の詳細のため、各ポリシーでは、コンテキストタイプによって部分的に閉じられたストレージクラスを作成し、直接エクスポートする必要があります。



これは、たとえば次のように機能します(もちろん、作成したものはすべてコンテナに入れる必要があります)。

  TestPart part = _container.GetExportedValue<ThreadPolicy<TestPart>>();
      
      







または:



 [Export] class Outer { Inner _perThreadObject; [ImportingConstructor] Outer([Import]ThreadPolicy <Inner> perThreadObject) { _perThreadObject = perThreadObject; } }
      
      







複雑にならないように、舞台裏に残っているのはギタにあります:

  1. トランザクションとWCFコンテキストの実装は同じで、TAffinityはTransaction.CurrentとOperationContext.Currentです。
  2. コンテキストがデフォルト(TAffinity)の形式で提供された場合、通常の非共有エクスポートを提供する必要があると想定します
  3. オブジェクトの作成のインターセプト-パーツを作成する場合、何かを行う必要があるかもしれません-たとえば、トランザクションの場合、そのパーツがトランザクションリソース(ISinglePhaseNotificationまたはIEnlistmentNotification)であるかどうかを確認し、そうである場合はトランザクションに揮発性リソースとして接続します。
  4. バインディングの破棄-スレッドの上記の初期化で、コンテキストストリームの終了後にDestroyAffinityを実行するスレッドを作成します。 トランザクション/操作の場合-トランザクション/操作完了イベントにバインドするだけです。




みんなのおかげで、誰かが助けてくれるかもしれません。






All Articles