C# MEF RegistrationBuilder导出特定接口实现

C# MEF RegistrationBuilder导出特定接口实现,c#,mef,composition,C#,Mef,Composition,我有一个接口的多个实现,我只想通过编程导出一个。我已经研究了RegistrationBuilder和它的AddMetaData()函数,但这是为导出定义元数据,而不是为特定值过滤。例如,我想做这样的事情: public enum MyClassType { TypeA, TypeB } public interface IClass {} public interface ClassMetaData { MyClassType Type { get; } } [ExportMetadata("

我有一个接口的多个实现,我只想通过编程导出一个。我已经研究了
RegistrationBuilder
和它的
AddMetaData()
函数,但这是为导出定义元数据,而不是为特定值过滤。例如,我想做这样的事情:

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.
public enum MyClassType{TypeA,TypeB}
公共接口类{}
公共接口类元数据{MyClassType类型{get;}}
[ExportMetadata(“Type”,MyClassType.TypeA)]
公共MyClassA:IClass
{
公共MyClassType类型{get{return MyClassType.TypeA;}}
}
[ExportMetadata(“类型”,MyClassType.TypeB)]
公共MyClassB:IClass
{
公共MyClassType类型{get{return MyClassType.TypeB;}}
}
//…然后在我的引导课程中,我设置了MEF容器。。。
var registrationBuilder=new registrationBuilder();
registrationBuilder.forTypesDrivesFrom()。。。。
//如何在^中指定一个过滤器,即仅导出带有元数据的实现。Type==MyClassA或instance.Type==MyClassA。

看起来不可能做到。如果您从我的测试代码中注意到,
RegistrationBuilder
甚至不会导出用
ExportMetadata
属性修饰的类(消息:System.ComponentModel.Composition警告:102:将应用于“test.MyClassA”类型的导出规范约定已被源文件中应用的属性或以前的约定覆盖)。MEF属性优先于通过
RegistrationBuilder进行的Fluent配置

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; }
}

+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; }
    }
}
解决问题的关键是
RegistrationBuilder
上的
ForTypesMatching()
方法。该参数是一个谓词,它接受该类型并返回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; }
    }
}
内部类程序
{
私有静态void Main(字符串[]args)
{
var registrationBuilder=new registrationBuilder();
注册生成器
.ForTypesMatching(t=>FilterOnMetadata(t,MyClassType.TypeOne))
.ExportInterfaces();
var assemblyCatalog=newassemblycatalog(typeof(MyClassType).Assembly,registrationBuilder);
var compositionContainer=新的compositionContainer(assemblyCatalog);
var ic=新的TestImportContainer();
复合容器。复合部件(ic);
var count=ic.ImportedParts.count();
}
公共静态bool过滤器元数据(类型t,MyClassType)
{
变量metadataAttribute=
(MyMetadataAttribute)t.GetCustomAttributes(true)
.SingleOrDefault(at=>at是MyMetadataAttribute);
if(metadataAttribute!=null)
返回metadataAttribute.Type==classType;
返回false;
}
私有密封类TestImportContainer
{
[进口数量(类型(IClass))]
公共IEnumerable导入部件{get;set;}
}
}

是否可以不使用
[ExportMetadata]
属性,即只使用一个返回枚举值的class属性(如我的示例,但不使用元数据属性)?否-必须使用标准的
[ImportMany]IEnumerable
并在构成对象时从那里进行过滤。更新了另一个示例,如果使用
导出
属性,则会对目录进行过滤。这对您有帮助:)但我不同意Lawrence的解决方案,因为它不太粗糙。没问题;更好的解决方案显然是不使用
ExportMetadata
,但问题表明您使用了该属性。请注意!非常感谢你。一个问题是,Export和ExportInterface之间的区别是什么?假设您有一个类
T
,它实现了
IIntefaceOne
IIntefacetwo
。使用
Export()
配置此类将匹配
T
类型的导入。使用
Export
将该类与类型为
IIntefaceOne
的导入相匹配。使用
ExportInterfaces()
将匹配类型为
IIntefaceOne
IIntefacetwo
的导入。这个评论可能无法很好地解释它。我发现有一篇很棒的文章非常有用。