在.NET中使用MEF只获取必要的插件

本文关键字:获取 插件 MEF NET | 更新日期: 2023-09-27 17:59:20

我有IMessageSender接口。

using System.ComponentModel.Composition;
public interface IMessageSender
{
    void Send(string message);
}

我有两个插件来实现这个接口。这是插件.cs.

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;
[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message);
    }
}

这是plugin.cs

[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message + "!!!!");
    }
}

我有这个代码来用MEF运行这些插件。

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Collections.Generic;
using System;
public class Program
{
    [ImportMany]
    public IEnumerable<IMessageSender> MessageSender { get; set; }
    public static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
        foreach (var message in p.MessageSender) {
            message.Send("hello, world");
        }
    }
    public void Run()
    {
      Compose();
    }
    private void Compose()
    {
        var catalog = new AggregateCatalog(); 
        catalog.Catalogs.Add(new DirectoryCatalog(@"./"));
        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
    }
}

编译后,我得到了我想要的。

> mono program.exe 
hello, world
hello, world!!!!

我的问题是如何选择性地用完许多插件。这个例子只是让所有可用的插件都能运行,但当我只想运行第一个插件或第二个插件时,我该怎么办?

例如,我可以按如下方式仅运行plugin2.dll吗?

public static void Main(string[] args)
{
    Program p = new Program();
    p.Run();
    var message = messageSender.GetPlugin("plugin"); // ???
    message.Send("hello, world");
}

已解决

基于这个网站,以及Matthew Abbott的回答。我可以想出这个工作代码。

接口代码(interface.cs)

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;
public interface IMessageSender
{
    void Send(string message);
}
public interface IMessageSenderMetadata
{
    string Name {get; }
    string Version {get; }
}
[MetadataAttribute]  
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MessageMetadataAttribute : ExportAttribute, IMessageSenderMetadata
{
    public MessageMetadataAttribute( string name, string version)  
            : base(typeof(IMessageSender))  
        {  
            Name = name;  
            Version = version;  
        }  
    public string Name { get; set; }  
    public string Version { get; set; }  
}

插件代码(Plugin.cs…)

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System;
[MessageMetadataAttribute("EmailSender1", "1.0.0.0")]
public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        Console.WriteLine(message + "????");
    }
}

程序.cs

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.Collections.Generic;
using System;
using System.Linq;
public class Program
{
    [ImportMany(typeof(IMessageSender), AllowRecomposition = true)]  
    public IEnumerable<Lazy<IMessageSender, IMessageSenderMetadata>> Senders { get; set; }
    public static void Main(string[] args)
    {
        Program p = new Program();
        p.Run();
        var sender1 = p.GetMessageSender("EmailSender1","1.0.0.0");
        sender1.Send("hello, world");
        sender1 = p.GetMessageSender("EmailSender2","1.0.0.0");
        sender1.Send("hello, world");
    }
    public void Run()
    {
      Compose();
    }
    public IMessageSender GetMessageSender(string name, string version)
    {
      return Senders
        .Where(l => l.Metadata.Name.Equals(name) && l.Metadata.Version.Equals(version))
        .Select(l => l.Value)
        .FirstOrDefault();
    }
    private void Compose()
    {
        var catalog = new AggregateCatalog(); 
        catalog.Catalogs.Add(new DirectoryCatalog(@"./"));
        var container = new CompositionContainer(catalog);
        container.ComposeParts(this);
    }
}

在.NET中使用MEF只获取必要的插件

MEF支持导出自定义元数据以伴随导出的类型。您需要做的是,首先定义一个接口,MEF将使用该接口创建包含元数据的代理对象。在您的示例中,您可能需要每个导出都有一个唯一的名称,因此我们可以定义:

public interface INameMetadata
{
  string Name { get; }
}

然后,您需要做的是确保为每个需要的导出分配元数据:

[Export(typeof(IMessageSender)), ExportMetadata("Name", "EmailSender1")]
public class EmailSender : IMessageSender
{
  public void Send(string message)
  {
    Console.WriteLine(message);
  }
}

MEF将使用存储在ExportMetadata("Name", "EmailSender1") atrribute中的值生成一个项目,即接口INameMetadata的实现。

完成后,您可以进行一些过滤,因此将您的[Import]重新定义为:

[ImportMany]
public IEnumerable<Lazy<IMessageSender, INameMetadata>> Senders { get; set; }

MEF将创建一个可枚举的Lazy<T, TMetadata>实例,这些实例支持实例类型的延迟实例化。我们可以查询为:

public IMessageSender GetMessageSender(string name)
{
  return Senders
    .Where(l => l.Metadata.Name.Equals(name))
    .Select(l => l.Value)
    .FirstOrDefault();
}

使用name参数的参数"EmailSender1"运行此操作将导致返回我们的EmailSender实例。需要注意的重要一点是,我们是如何根据查询与类型相关联的元数据来选择要使用的特定实例的。

您可以更进一步,可以将ExportExportMetadata属性合并为一个属性,例如:

[AttributeUsage(AttributeTargets.Class, AllowMultiple=false), MetadataAttribute]
public class ExportMessageSenderAttribute : ExportAttribute, INameMetadata
{
  public ExportMessageSenderAttribute(string name)
    : base(typeof(IMessageSender))
  {
    Name = name;
  }
  public string Name { get; private set; }
}

这允许我们使用单个属性来导出类型,同时仍然提供额外的元数据:

[ExportMessageSender("EmailSender2")]
public class EmailSender : IMessageSender
{
  public void Send(string message)
  {
    Console.WriteLine(message);
  }
}

显然,以这种方式进行查询会为您提供一个设计决策。使用Lazy<T, TMetadata>实例意味着您可以推迟实例的实例化,但这意味着每个懒惰只能创建一个实例。MEF框架的Silverlight变体还支持ExportFactory<T, TMetadata>类型,它允许您每次启动T的新实例,同时仍然为您提供丰富的元数据机制。