3

我有一个接口的多个实现,我只想以编程方式导出一个。我看过RegistrationBuilder它的AddMetaData()功能,但这是为导出定义 MetaData,而不是过滤特定值。例如,我想做这样的事情:

public enum MyClassType { TypeA, TypeB }
public interface IClass {}
public interface ClassMetaData { MyClassType Type { get; } }

[ExportMetadata("Type", MyClassType.TypeA)]
public MyClassA : IClass
{
    public MyClassType Type { get { return MyClassType.TypeA; } }
}

[ExportMetadata("Type", MyClassType.TypeB)]
public MyClassB : IClass
{
    public MyClassType Type { get { return MyClassType.TypeB; } }
}

//...Then in my bootstrapping class where I set up the MEF container...

var registrationBuilder = new RegistrationBuilder();
registrationBuilder.ForTypesDerivesFrom<IClass>()....
// How do I specify a filter in ^ to say only export the implementation with MetaData.Type == MyClassA or instance.Type == MyClassA.
4

2 回答 2

5

+1 问题 - 自 4.5 发布以来,我没有机会查看 MEF,所以它迫使我跟上新添加的RegistrationBuilder课程!

我猜您的示例不起作用的原因是因为据我了解,它RegistrationBuilder旨在替换 MEF 严重依赖的属性的角色,直到 .NET 4.0。这ExportMetadataAttribute是旧的做事方式的一部分,我只是猜测新旧不能很好地结合在一起。

多亏了添加,RegistrationBuilder您可以完全实现您想要的,而导出的类不知道它们是使用 MEF 构建的。在我看来,这是 MEF 与 4.0 相比的巨大改进。

首先让我们从要导出的类开始。首先,我定义了一个MyMetadataAttribute类,它封装了与我们要过滤的类型相关联的元数据:

public enum MyClassType
{
    TypeOne,
    TypeTwo
}

[AttributeUsage(AttributeTargets.Class)]
public class MyMetadataAttribute: Attribute
{
    public MyMetadataAttribute(MyClassType type)
    {
        Type = type;
    }

    public MyClassType Type { get; private set; }
}

现在来了我可能想要导出的类:

public interface IClass
{
}

[MyMetadata(MyClassType.TypeOne)]
public class MyClassA : IClass
{
    public MyClassType Type
    {
        get { return MyClassType.TypeOne; }
    }
}

[MyMetadata(MyClassType.TypeTwo)]
public class MyClassB : IClass
{
    public MyClassType Type
    {
        get { return MyClassType.TypeTwo; }
    }
}

解决你的问题的关键是ForTypesMatching()方法论RegistrationBuilder。该参数是一个谓词,它采用类型并根据您是否要在导出的结果中包含该类型返回 true 或 false。下面的代码演示了一个例子:

internal class Program
{
    private static void Main(string[] args)
    {
        var registrationBuilder = new RegistrationBuilder();
        registrationBuilder
            .ForTypesMatching<IClass>(t => FilterOnMetadata(t, MyClassType.TypeOne))
            .ExportInterfaces();
        var assemblyCatalog = new AssemblyCatalog(typeof (MyClassType).Assembly, registrationBuilder);
        var compositionContainer = new CompositionContainer(assemblyCatalog);
        var ic = new TestImportContainer();
        compositionContainer.ComposeParts(ic);
        var count = ic.ImportedParts.Count();
    }

    public static bool FilterOnMetadata(Type t, MyClassType classType)
    {
        var metadataAttribute = 
            (MyMetadataAttribute) t.GetCustomAttributes(true)
                .SingleOrDefault(at => at is MyMetadataAttribute);
        if (metadataAttribute != null)
            return metadataAttribute.Type == classType;
        return false;
    }

    private sealed class TestImportContainer
    {
        [ImportMany(typeof(IClass))]
        public IEnumerable<IClass> ImportedParts { get; set; }
    }
}
于 2013-11-03T19:25:11.917 回答
1

好像做不到。如果您从我的测试代码中注意到,RegistrationBuilder甚至不会导出用ExportMetadata属性装饰的类(消息:System.ComponentModel.Composition 警告:102:适用于类型“test.MyClassA”的导出规范约定具有已被源文件中应用的属性或先前的约定覆盖)。MEF 属性优先于 Fluent 配置RegistrationBuilder

class Program
{
    static void Main()
    {
        // importing class instance
        var mcc = new MyClassConsumer();

        // type to export
        var typeToExport = typeof(MyClassA);

        Console.WriteLine("Type to export: {0}", typeToExport);

        var rb = new RegistrationBuilder();

        rb.ForType(typeToExport)
            .ExportInterfaces();

        var catalog = new AssemblyCatalog(typeof(Program).Assembly, rb);

        var container = new CompositionContainer(catalog);

        container.ComposeParts(mcc); // bombs if MyClassA's MetadataAttribute is not commented out

        Console.WriteLine("Imported property's type: {0}", mcc.MyClass.GetType());

        Console.ReadLine();
    }
}

public enum MyClassType { TypeA, TypeB }
public interface IClass {}
public interface IClassMetaData { MyClassType Type { get; } }

[ExportMetadata("Type", MyClassType.TypeA)] // works if you comment this line
public class MyClassA : IClass
{
}

[ExportMetadata("Type", MyClassType.TypeB)]
public class MyClassB : IClass
{
}

public class MyClassConsumer
{
    [Import]
    public IClass MyClass { get; set; }
}

更新:如果您可以添加[Export(IClass)]到要导出的类,您可以过滤目录,如下所示:

class Program
{
    static void Main()
    {
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);

        var filteredCatalog = catalog.Filter(p =>
            {
                var type = ReflectionModelServices.GetPartType(p).Value;

                return typeof(IClass).IsAssignableFrom( type ) && // implements interface you're looking for
                    Attribute.IsDefined(type, typeof(ExportMetadataAttribute)) && // has ExportMetadata attribute
                    // check for Type == MyClassType.TypeA
                    type.GetCustomAttributes(typeof(ExportMetadataAttribute), true).Any(ca =>
                        {
                            var ema = (ExportMetadataAttribute)ca;

                            return ema.Name == "Type" && (MyClassType)ema.Value == MyClassType.TypeA;
                        });
            });

        var container = new CompositionContainer(filteredCatalog);

        MyClassConsumer mcc = new MyClassConsumer();

        container.ComposeParts(mcc);

        Console.WriteLine("Imported property's type: {0}", mcc.MyClass.GetType());

        Console.ReadLine();
    }
}

public enum MyClassType { TypeA, TypeB }
public interface IClass {}
public interface IClassMetaData { MyClassType Type { get; } }

[Export(typeof(IClass))]
[ExportMetadata("Type", MyClassType.TypeA)]
public class MyClassA : IClass
{
}

[Export(typeof(IClass))]
[ExportMetadata("Type", MyClassType.TypeB)]
public class MyClassB : IClass
{
}

public class MyClassConsumer
{
    [Import]
    public IClass MyClass { get; set; }
}
于 2013-11-03T00:33:57.833 回答