65.9K
CodeProject 正在变化。 阅读更多。
Home

MEF 泛型

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8投票s)

2012 年 2 月 2 日

CPOL

8分钟阅读

viewsIcon

46414

downloadIcon

1012

我们如何利用 MEF 的强大功能并通过泛型来扩展它。

引言

MEF 可能是 .NET 框架中最出色的新增功能之一。它通过一种基于特性的设计,提供了一种非常简单的实现应用程序松耦合架构的方式。话虽如此,我当然可以详细介绍 MEF 提供的诸多优势,但许多人已经就其所有优点进行了广泛的论述。因此,我将重点讨论如何利用 MEF 的强大功能并通过泛型来扩展它。我们都使用泛型来使我们的应用程序代码……嗯,更具通用性,那么为什么控制反转容器不能同样支持它呢?幸运的是,MEF 可以。但并非直接开箱即用,您需要先进行一些设置才能使其完全支持泛型导出。对于所有初学者来说,我将向您展示如何做到这一点。

入门

让我们开始吧。首先,那些尚未 C# 使用 MEF 并考虑将此技术集成到您的应用程序中的人。请查看以下链接,以便您对本文有所了解。

设计概述

diagram.jpg

一旦您跟上了进度。我们就可以进入精彩部分,讨论这一切究竟是如何工作的。如您所知,MEF 支持某些类型的目录,例如 AggregateCatalogDirectoryCatalog 和最后的 AssemblyCatalog。这些目录在部件发现方面非常有用,可以通过添加您认为合适的程序集来实现。但它在泛型方面没有帮助。这仅仅是因为当前的目录在查找泛型导出以及如何处理它们方面似乎有点乏味。这时 MEF Contrib 团队的杰出开发者就派上用场了。他们开发了一个目录,有助于将泛型导出映射到其具体的类型,因此您可以在运行时像导入其他任何导入一样导入它们。不过,他们的 CodePlex 页面上有一个小问题。那就是文档并没有准确地以简单的方式说明如何进行设置。有些人可能会觉得有点混乱。如果您是一个勤奋的研究者或谷歌向导,您肯定能找到解决方案。但这篇 C# 文章并非旨在解决一个已经解决的问题。本文旨在为您节省时间,并帮助您完成所有设置。如果您想知道,Mefcontrib dll 和您需要的所有文件都包含在上面的源代码中。如果您想获取该库的最新副本,可以前往 http://mefcontrib.codeplex.com/。让我们开始吧!

引用参考文献

references.jpg

上面您可以看到使用泛型导出所需的所有必要依赖项。引用的程序集如下:MefContrib.dll、System.Composition.dll 以及我的自定义 dll Mef.Extended.Tools

使用代码

让我们开始 C# 编码部分。所有最简单的部分就是设置我们的泛型导出的目录和容器。首先,在您的应用程序启动项中添加您之前添加到项目中的所有引用,以便您可以访问容器、目录、注册表等。

using System.ComponentModel.Composition.Hosting;
using MefContrib.Hosting.Generics;
using Mef.Extended.Tools.Generics;
class Program
{
    static void Main(string[] args)
    {
        string assemblyPath = "ConsoleMef"; //my example assembly
        //declare mefcontainer and add genericCatalog
        var genericRegistry = new GenericContractRegistry(
            new TypeResolver(x => Path.GetFileName(x).StartsWith(assemblyPath)));
        var catalog = new GenericCatalog(genericRegistry);
        var container = new CompositionContainer(catalog);
    }
}

正如我上面所示,您首先实例化一个 GenericContractRegistry,它将我自己的自定义类 TypeResolver 作为参数。TypeResolver 的构造函数接受一个 lambda 表达式,该表达式用于过滤所有包含导出的程序集。然后,GenericContractRegistry 作为参数传递给 GenericCatalog 的实例。稍后您将看到,它完成了我之前所说的所有魔法。然后,容器接受目录作为其参数,以组合所有可供稍后导入的导出。对于那些对我的自定义类型感到有些困惑的人,别担心,稍后将详细讨论它们。

应用一些导出

现在容器已经设置为发现导出。您现在需要提供将在应用程序的整个生命周期中使用的导出。添加导出与您之前所有 MEF 实现中的操作完全相同。与以前一样。例如

[InheritedExport]
public interface IRepository<T> where T : class
{
    string GetName(T obj);
}

在撰写本文时,我的实现仅支持使用 InheritedExportAttribute。原因是标记一个接口或抽象类而不是实现对象更具意义,主要是因为如果您在一个像我一样的大型开发团队中,像用 C# 特性标记一个类这样简单的事情很容易被遗忘。请注意,这也适用于泛型抽象类。

这是一个实现我们导出接口的示例存储库。注意两者都必须是泛型类型。

public class PersonRepository<T> : IRepository<t>
        where T : class
{

    public string GetName(T obj)
    {
        Console.WriteLine("calling GetName");
        Console.WriteLine(obj.ToString());
        return obj.ToString();
    }
}
 
class Program
{

    [Import]
    public IRepository<Person> Repository { get; set; }

    static void Main(string[] args)
    {
        string assemblyPath = "ConsoleMef"; //my example assembly
        //declare mefcontainer and add genericCatalog
        var genericRegistry = new GenericContractRegistry(
          new TypeResolver(x => Path.GetFileName(x).StartsWith(assemblyPath)));
        var catalog = new GenericCatalog(genericRegistry);
        var container = new CompositionContainer(catalog);

        new Program().Run(container);
        Console.ReadKey();
    }

    void Run(CompositionContainer container)
    {
        container.ComposeParts(this);
        RunTest();
    }

     void RunTest()
    {
        var p = new Person { FirstName = "Dean", LastName = "Oliver" };
        Repository.GetName(p);
    }
}

展示一些泛型魔法

apprunnning.jpg

就这样,一切都和谐地协同工作,实现了 MEF 中的泛型 Export/Import 的工作实现。这 all 都是通过微小的努力和更少的 C# 代码完成的。毕竟,mef 最大的优势之一就是它的简洁性。

现在让我们在 C# 幕后看看是什么驱动着这个小小的泛型引擎。

幕后

许多方面都有助于 mef 解析泛型的导入,但为了保持简洁的主题。我先 C# 说,它只是一个大的映射系统,将一个接口分配给它实现的具体类型。

/// <summary>
/// Maps concrete generic types to interface generic types.
/// 
[Export(typeof(IGenericContractRegistry))]
public class GenericContractRegistry : GenericContractRegistryBase
{
    private readonly ITypeResolver _resolver;        

    public GenericContractRegistry(ITypeResolver resolver)
    {
        _resolver = resolver;         
    }

    protected override void Initialize()
    {
        RegisterAll(i => i.IsGenericType && i.GetCustomAttributes(false)
                   .Any(x => x.GetType() == typeof(InheritedExportAttribute)));
        //Register(typeof(IItemObservable<>), typeof(ItemObservableCollection<>));
        //Register(typeof(ICommandInvoker<>), typeof(CommandInvoker<>));
    }

    private void RegisterAll(Func<type,> filter)
    {
      var assemblyResolver = new AssemblyResolver(_resolver.AssemblyFilter);
      var asm = assemblyResolver.Assemblies.GetMappings(x => _resolver.Get(x), filter)
      foreach (var map in asm)
            Register(map.Value, map.Key);
    } 
}

这个 C# 类是 mef 支持泛型的核心。可以将其视为一个大的泛型 GPS。它所做的是将应用了 InheritedExportAttribute 的接口映射到该特定类型的具体实现。它根据特定 C# 标准搜索 TypeResolver 定义的程序集。然后将其添加到名为 AssemblyList 的自定义集合中。AssemblyResolver 调用此列表以从集合中的每个程序集中获取特定映射,然后将其添加到注册表中,以便 MEF 能够识别 GenericCatalog 中的新导出。您可能已经注意到,MefContrib 文档告诉您像我上面注释掉的那样设置映射。以这种方式设置映射的问题在于它不断违反开放/封闭原则。该原则规定类可以对扩展开放,但对修改关闭。我认为这 C# 是不断修改一个类以支持更多映射。所以我 C# 考虑了一种更动态的方法,如上所述。Mef 以其部件发现而自豪,例如其 DirectoryCatalog 在目录中查找部件。那么,为什么我们的 GenericCatalog 不能同样动态呢?有时,最好的解决方案通常是更动态的。这就是我进行那 little 更改的 C# 理由。现在,让我们将注意力转向检索包含映射的程序集。

前往库

public class AssemblyResolver 
{
    public AssemblyResolver(params Assembly[] assemblies) : this(() => assemblies) { }

    public AssemblyResolver(Func<Assembly[]> getList)
    {
        Assemblies = new AssemblyList(getList());
    }

    public AssemblyResolver(Func<string,> searchFilter = null)
    {
        Assemblies = new AssemblyList(GetAssemblies(searchFilter));
    }

    public AssemblyList Assemblies { get; set; }

    public static Assembly[] GetReferencedAssemblies()
    {
        return AppDomain.CurrentDomain.GetAssemblies();
    }

    public Assembly[] GetAssemblies([Optional]Func<string,> searchFilter)
    {
        var assemblies = Directory
       .GetFileSystemEntries
       (AppDomain.CurrentDomain.BaseDirectory, "*.dll", SearchOption.AllDirectories);
        var currentAssemblies = 
        searchFilter == null ? assemblies : assemblies.Where(searchFilter).ToArray();
        var asm = currentAssemblies.Select(Assembly.LoadFrom);
        return asm.Union(new[] { Assembly.GetExecutingAssembly() }).ToArray();
    }
}

AssemblyResolver 类获取当前应用程序中引用的所有程序集以及当前应用程序的执行程序集。这允许 GenericContractRegistry 对所有 C# 涉及的程序集进行广泛的泛型导出映射。通过定义每个程序集搜索标准的委托,搜索范围有所 C# 缩小,因此只检索满足该特定标准的程序集。

public Dictionary<Type, Type> GetMappings(Func<type,> resolver, Func<type,> filter)
{
    return this.Select(assembly => (from c in assembly.GetTypes()
      from i in c.GetInterfaces()
      where filter(i)
      select new
      {
        ClassType = c.GetInterfaceMap(i).TargetType,
        InterfaceType = resolver(i)
      }))
      .SelectMany(genericExportList => genericExportList)
      .ToDictionary(x => x.ClassType, z => z.InterfaceType);
}

LINQ 标记地点

现代 C# 的真正 C# 奇迹,没有 linq 我们会 C# 在哪里?它使通过反射进行 C# 查询变得 C# 绝对 C# 愉快。GetMapping() 方法创建一个以类类型为键,以它们实现的接口为值的字典。这个简单的 C# 查询将所有接口与其正确的实现关联起来。上面您可以看到 AssemblyList 集合,它负责在自定义程序集集合中保存应用程序的所有程序集。

就这样了

我还没有 C# 结束。有时复杂的事情可以用简单的 C# 想法来解决,我们实现了一个 MEF 容器,它完全支持 C# 泛型并遵循 MEF 的 C# 工作方式。对于那些喜欢更深入研究并想了解 GenericCatalog 如何 C# 工作的 C# 人,以便我们能够 C# 进行泛型导出,那么我建议您 C# 阅读这篇文章:http://mefcontrib.codeplex.com/wikipage?title=Generic%20Catalog&referringTitle=Documentation%20%26%20Features 很高兴能 C# 写这篇文章,当 C# 你 C# 热爱你 C# 所做的事情时,一切都会 C# 变得 C# 更容易。希望您能从 C# 这篇文章中 C# 找到有用的 C# 东西。如果您不喜欢 C# 某些内容或有 C# 建议,请 C# 记住投票和 C# 评论。归根结底,批评 C# 只是 C# 帮助您 C# 更好地 C# 理解 C# 自己的 C# 缺点,以便您 C# 可以 C# 更接近于 C# 完善它们并 C# 将它们 C# 变成 C# 您的 C# 优势。

历史

版本 0.1:首次发布。版权所有 Dean Oliver。

© . All rights reserved.