6

我发现这个问题很难表达(尤其是标题形式),所以请多多包涵。

我有一个应用程序,我不断修改它来做不同的事情。似乎 MEF 可能是管理不同功能的好方法。从广义上讲,应用程序的三个部分构成了某种管道:

  1. 获得
  2. 转型
  3. 表达

以最简单的形式,我可以将这些阶段中的每一个都表示为接口(IAcquisition等)。当我想使用提供比标准更丰富的数据的采集组件时,问题就开始了。我想设计使用这些更丰富数据的模块,但我不能依赖它的存在。

当然,我可以将所有数据添加到接口规范中。我可以通过抛出异常或返回空值来处理较差的数据源。这似乎与理想相去甚远。

我更喜欢分三个阶段进行 MEF 绑定,这样只有在模块与之前选择的模块兼容时才将模块提供给用户。

所以我的问题是:我可以指定限制可用导入集的元数据吗?

一个例子:

Acquision1 仅提供 BasicData

Acquision2 提供 BasicData 和 AdvancedData

Transformation1 需要 BasicData

Transformation2 需要 BasicData 和 AdvancedData

首先选择采集模块。

如果选择 Acquisition1,则不提供 Transformation 2,否则两者都提供。

这可能吗?如果是这样,怎么做?

4

1 回答 1

4

您的问题提出了这样的结构:

public class BasicData
{
    public string Basic { get; set; } // example data
}

public class AdvancedData : BasicData
{
    public string Advanced { get; set; } // example data
}

现在您有了采集、转换和表达组件。您希望能够处理不同类型的数据,因此它们是通用的:

public interface IAcquisition<out TDataKind>
{
    TDataKind Acquire();
}

public interface ITransformation<TDataKind>
{
    TDataKind Transform(TDataKind data);
}

public interface IExpression<in TDataKind>
{
    void Express(TDataKind data);
}

现在你想用它们构建一个管道,如下所示:

IExpression.Express(ITransformation.Transform(IAcquisition.Acquire));

所以让我们开始构建一个管道构建器:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Linq.Expressions;

// namespace ...

public static class PipelineBuidler
{
    private static readonly string AcquisitionIdentity =
        AttributedModelServices.GetTypeIdentity(typeof(IAcquisition<>));
    private static readonly string TransformationIdentity =
        AttributedModelServices.GetTypeIdentity(typeof(ITransformation<>));
    private static readonly string ExpressionIdentity =
        AttributedModelServices.GetTypeIdentity(typeof(IExpression<>));

    public static Action BuildPipeline(ComposablePartCatalog catalog,
        Func<IEnumerable<string>, int> acquisitionSelector,
        Func<IEnumerable<string>, int> transformationSelector,
        Func<IEnumerable<string>, int> expressionSelector)
    {
        var container = new CompositionContainer(catalog);

该类为您的三个合同接口保存 MEF 类型标识。我们稍后将需要这些来识别正确的出口。我们的BuildPipeline方法返回一个Action. 那将是管道,所以我们可以这样做pipeline()。它需要 aComposablePartCatalog和 3 Funcs(选择导出)。这样一来,我们就可以将所有的脏活留在这个类中。然后我们从创建一个CompositionContainer.

现在我们要构建ImportDefinitions,首先是获取组件:

        var aImportDef = new ImportDefinition(def => (def.ContractName == AcquisitionIdentity), null, ImportCardinality.ZeroOrMore, true, false);

ImportDefinition只是过滤掉IAcquisition<>接口的所有导出。现在我们可以把它交给容器了:

        var aExports = container.GetExports(aImportDef).ToArray();

aExports现在保存IAcquisition<>目录中的所有导出。因此,让我们在此运行选择器:

        var selectedAExport = aExports[acquisitionSelector(aExports.Select(export => export.Metadata["Name"] as string))];

我们有我们的采集组件:

        var acquisition = selectedAExport.Value;
        var acquisitionDataKind = (Type)selectedAExport.Metadata["DataKind"];

现在我们将对转换和表达式组件做同样的事情,但有一点不同:ImportDefinition将确保每个组件都可以处理前一个组件的输出。

        var tImportDef = new ImportDefinition(def => (def.ContractName == TransformationIdentity) && ((Type)def.Metadata["DataKind"]).IsAssignableFrom(acquisitionDataKind),
            null, ImportCardinality.ZeroOrMore, true, false);
        var tExports = container.GetExports(tImportDef).ToArray();
        var selectedTExport = tExports[transformationSelector(tExports.Select(export => export.Metadata["Name"] as string))];

        var transformation = selectedTExport.Value;
        var transformationDataKind = (Type)selectedTExport.Metadata["DataKind"];

        var eImportDef = new ImportDefinition(def => (def.ContractName == ExpressionIdentity) && ((Type)def.Metadata["DataKind"]).IsAssignableFrom(transformationDataKind),
            null, ImportCardinality.ZeroOrMore, true, false);
        var eExports = container.GetExports(eImportDef).ToArray();
        var selectedEExport = eExports[expressionSelector(eExports.Select(export => export.Metadata["Name"] as string))];

        var expression = selectedEExport.Value;
        var expressionDataKind = (Type)selectedEExport.Metadata["DataKind"];

现在我们可以将它们全部连接到一个表达式树中:

        var acquired = Expression.Call(Expression.Constant(acquisition), typeof(IAcquisition<>).MakeGenericType(acquisitionDataKind).GetMethod("Acquire"));
        var transformed = Expression.Call(Expression.Constant(transformation), typeof(ITransformation<>).MakeGenericType(transformationDataKind).GetMethod("Transform"), acquired);
        var expressed = Expression.Call(Expression.Constant(expression), typeof(IExpression<>).MakeGenericType(expressionDataKind).GetMethod("Express"), transformed);
        return Expression.Lambda<Action>(expressed).Compile();
    }
}

就是这样!一个简单的示例应用程序如下所示:

[Export(typeof(IAcquisition<>))]
[ExportMetadata("DataKind", typeof(BasicData))]
[ExportMetadata("Name", "Basic acquisition")]
public class Acquisition1 : IAcquisition<BasicData>
{
    public BasicData Acquire()
    {
        return new BasicData { Basic = "Acquisition1" };
    }
}

[Export(typeof(IAcquisition<>))]
[ExportMetadata("DataKind", typeof(AdvancedData))]
[ExportMetadata("Name", "Advanced acquisition")]
public class Acquisition2 : IAcquisition<AdvancedData>
{
    public AdvancedData Acquire()
    {
        return new AdvancedData { Advanced = "Acquisition2A", Basic = "Acquisition2B" };
    }
}

[Export(typeof(ITransformation<>))]
[ExportMetadata("DataKind", typeof(BasicData))]
[ExportMetadata("Name", "Basic transformation")]
public class Transformation1 : ITransformation<BasicData>
{
    public BasicData Transform(BasicData data)
    {
        data.Basic += " - Transformed1";
        return data;
    }
}

[Export(typeof(ITransformation<>))]
[ExportMetadata("DataKind", typeof(AdvancedData))]
[ExportMetadata("Name", "Advanced transformation")]
public class Transformation2 : ITransformation<AdvancedData>
{
    public AdvancedData Transform(AdvancedData data)
    {
        data.Basic += " - Transformed2";
        data.Advanced += " - Transformed2";
        return data;
    }
}

[Export(typeof(IExpression<>))]
[ExportMetadata("DataKind", typeof(BasicData))]
[ExportMetadata("Name", "Basic expression")]
public class Expression1 : IExpression<BasicData>
{
    public void Express(BasicData data)
    {
        Console.WriteLine("Expression1: {0}", data.Basic);
    }
}

[Export(typeof(IExpression<>))]
[ExportMetadata("DataKind", typeof(AdvancedData))]
[ExportMetadata("Name", "Advanced expression")]
public class Expression2 : IExpression<AdvancedData>
{
    public void Express(AdvancedData data)
    {
        Console.WriteLine("Expression2: ({0}) - ({1})", data.Basic, data.Advanced);
    }
}


class Program
{
    static void Main(string[] args)
    {
        var pipeline = PipelineBuidler.BuildPipeline(new AssemblyCatalog(typeof(Program).Assembly), StringSelector, StringSelector, StringSelector);
        pipeline();
    }

    static int StringSelector(IEnumerable<string> strings)
    {
        int i = 0;
        foreach (var item in strings)
            Console.WriteLine("[{0}] {1}", i++, item);
        return int.Parse(Console.ReadLine());
    }
}
于 2013-08-22T15:54:47.700 回答