2

.Net 4.0 で MEF を使用して、かなりの量の抽象ファクトリ コードと構成ガビンを節約しています。展開されていないため、.net 4.5 に移行できません。

クラス

/// <summary>
/// Factory relies upon the use of the .net 4.0 MEF framework
/// All processors need to export themselves to make themselves visible to the 'Processors' import property auto MEF population
/// This class is implemented as a singleton
/// </summary>
public class MessageProsessorFactory
{
    private static readonly string pluginFilenameFilter = "Connectors.*.dll";
    private static CompositionContainer _container;
    private static MessageProsessorFactory _instance;
    private static object MessageProsessorFactoryLock = new object();

    /// <summary>
    /// Initializes the <see cref="MessageProsessorFactory" /> class.
    /// Loads all MEF imports
    /// </summary>
    /// <exception cref="System.NotSupportedException"></exception>
    private MessageProsessorFactory()
    {
        lock (MessageProsessorFactoryLock)
        {
            if (_container == null)
            {
                RemoveDllSecurityZoneRestrictions();

                //Create a thread safe composition container
                _container = new CompositionContainer(new DirectoryCatalog(".", pluginFilenameFilter), true, null);

                _container.ComposeParts(this);
            }
        }
    }

    /// <summary>
    /// A list of detected class instances that support IMessageProcessor
    /// </summary>
    [ImportMany(typeof(IMessageProcessor), RequiredCreationPolicy = CreationPolicy.NonShared)]
    private List<Lazy<IMessageProcessor, IMessageProccessorExportMetadata>> Processors { get; set; }

    /// <summary>
    /// Gets the message factory.
    /// </summary>
    /// <param name="messageEnvelope">The message envelope.</param>
    /// <returns><see cref="IMessageProcessor"/></returns>
    /// <exception cref="System.NotSupportedException">The supplied target is not supported:  + target</exception>
    public static IMessageProcessor GetMessageProcessor(MessageEnvelope messageEnvelope)
    {
        if (_instance == null)
            _instance = new MessageProsessorFactory();

        var p = _instance.Processors.FirstOrDefault(
                    s => s.Metadata.ExpectedType.AssemblyQualifiedName == messageEnvelope.AssemblyQualifiedName);

        if (p == null)
            throw new NotSupportedException(
                "The supplied type is not supported: " + messageEnvelope.AssemblyQualifiedName);

       return p.Value;

    }

    /// <summary>
    /// Removes any zone flags otherwise MEF wont load files with
    /// a URL zone flag set to anything other than 'MyComputer', we are trusting all pluggins here.
    /// http://msdn.microsoft.com/en-us/library/ms537183(v=vs.85).aspx
    /// </summary>
    private static void RemoveDllSecurityZoneRestrictions()
    {
        string path = System.IO.Path.GetDirectoryName(
                            System.Reflection.Assembly.GetExecutingAssembly().Location);

        foreach (var filePath in Directory.EnumerateFiles(path, pluginFilenameFilter))
        {
            var zone = Zone.CreateFromUrl(filePath);

            if (zone.SecurityZone != SecurityZone.MyComputer)
            {
                var fileInfo = new FileInfo(filePath);
                fileInfo.DeleteAlternateDataStream("Zone.Identifier");
            }
        }
    }
}

が呼び出された後_container.ComposeParts(this);、見つかったすべての IMessageProcessor 実装が Processors に取り込まれます。偉大な。

ノート

  • GetMessageProcessor は多くのスレッドによって呼び出されます。
  • 開発者が IMessageProcessor のクラス実装をどのように構築するかを制御することはできません。したがって、それらがスレッド セーフ (再入可能) であることを保証することはできません。ただし、クラスは Export 属性でインストルメント化する必要があります。

エクスポート属性

 [MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MessageProccessorExportAttribute : ExportAttribute
{
    public MessageProccessorExportAttribute()
        : base(typeof(IMessageProcessor))
    {
    }

    public Type ExpectedType { get; set; }
}
  • ExpectedType は、IMessageProcessor.ProcessMessage() が処理することを期待するもの、純粋に実装の詳細を示す単なるメタデータです。

私の問題は、Lazy<> 参照を介して構築されたときのアクティブ化ポリシーに関係なく、インポートされた各インスタンスがシングルトンになることをどこでも読んでいることです。

したがって、複数のスレッドが同じインスタンスを取得しようとしており、これは望ましくないため、MEF からのインスタンスを GetMessageProcessor から返すことはできません。ああ!次の「回避策」が最善のアプローチなのか、それとも MEF の主張の概念が間違っているのか疑問に思っていました。

私の回避策は、一見無意味に見えるRequiredCreationPolicy = CreationPolicy.NonShared属性設定をに変更することCreationPolicy.Sharedです。

次に、関数 GetMessageProcessor を変更して、新しいインスタンスを手動で作成し、MEF から完全に分離します。タイプのリストとして MEF 共有インスタンス prulry を使用します。

IMessageProcessor newInstance = (IMessageProcessor)Activator.CreateInstance(p.Value.GetType());

完全な方法。

public static IMessageProcessor GetMessageProcessor(MessageEnvelope messageEnvelope)
    {
        if (_instance == null)
            _instance = new MessageProsessorFactory();

        var p = _instance.Processors.FirstOrDefault(
                    s => s.Metadata.ExpectedType.AssemblyQualifiedName == messageEnvelope.AssemblyQualifiedName);

        if (p == null)
            throw new NotSupportedException(
                "The supplied type is not supported: " + messageEnvelope.AssemblyQualifiedName);

        // we need to create a new instance from the singleton instance provided by MEF to assure we get a instance un-associated with the MEF container for
        // currently as of .net 4.0 it wants to keep references on objects which may impact memory consumption.
        // As we have no control over how a developer will organise there class that exposes an Export,
        // this could lead to multithreading issues as an imported lazy instance is a singleton regardless 
        // of the RequiredCreationPolicy.
        // MEF is still invaluable in avoided a tone of abstract factory code and its dynamic detection of all supporting 
        // Exports conforming to IMessageProcessor means there is no factory config for us to maintain.

        IMessageProcessor newInstance = (IMessageProcessor)Activator.CreateInstance(p.Value.GetType());

        return newInstance;



    }
4

1 に答える 1

2

このようなものが動作するはずです:

public class MessageProsessorFactory
{
   private const string pluginFilenameFilter = "Connectors.*.dll";
   private static readonly Lazy<CompositionContainer> _container 
      = new Lazy<CompositionContainer>(CreateContainer, true);

   private static CompositionContainer CreateContainer()
   {
      RemoveDllSecurityZoneRestrictions();
      var catalog = new DirectoryCatalog(".", pluginFilenameFilter);
      return new CompositionContainer(catalog, true, null);
   }

   public static IMessageProcessor GetMessageProcessor(MessageEnvelope messageEnvelope)
   {
      var processors = _container.Value.GetExports<IMessageProcessor, IMessageProccessorExportMetadata>();
      var p = processors.FirstOrDefault(s => s.Metadata.ExpectedType.AssemblyQualifiedName == messageEnvelope.AssemblyQualifiedName);
      if (p == null) throw new NotSupportedException("The supplied type is not supported: " + messageEnvelope.AssemblyQualifiedName);
      return p.Value;
   }

   private static void RemoveDllSecurityZoneRestrictions()
   {
      // As before.
      // PS: Nice to see someone found a use for my code! :)
      // http://www.codeproject.com/Articles/2670/Accessing-alternative-data-streams-of-files-on-an
      ...
   }
}
于 2012-12-04T19:44:10.940 に答える