Как использовать MEF Inherited Export & MetaData?

У меня есть интерфейс:

[InheritedExport(typeof(IMetric))]
public interface IMetric { ... }

У меня есть интерфейс атрибута Meta:

 public interface IMetricAttribute { ... }

и атрибут, который его реализует:

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class MetricAttribute : ExportAttribute, IMetricAttribute {
    public string MetricName { get; set; }
    public string MetricDescription { get; set; }

    public MetricAttribute(string name, string description)
        : base(typeof(MetricAttribute)) {
        this.MetricName = name;
        this.MetricDescription = description;
    }
}

Тогда у меня есть два класса:

[Metric("MetricA","MetricA")]
public class MetricA: IMetric { ... }

[Export(typeof(IMetric))] <<<< THIS IS IMPORTANT
[Metric("MetricB", "MetricB")]
public class MetricB: IMetric { ... }

Затем я пытаюсь импортировать метрики (я вижу оба в каталоге)

Возвращаются следующие значения: MetricA AND MetricB

var metrics = compositionContainer.GetExports<IMetric>();

Однако следующее возвращает ТОЛЬКО Метрический B и НЕ Метрический

var metrics = compositionContainer.GetExports<IMetric, IMetricAttribute>();

любая идея, почему?

(обратите внимание на дубликат экспорта в MetricB (он уже имеет его от реализации IMetric))

спасибо

Дэвид

Ответ 1

Впервые я видел это поведение, но из того, что я могу понять, метаданные генерируются для экспорта на уровне типа. Итак, учитывая:

[Metric("MetricA", "MetricA")]
public class MetricA : IMetric
{

}

У вас есть два экспорта для этого типа. У вас есть экспорт MetricA, который явно предоставляется вашим MetricAttribute, и у вас есть унаследованный экспорт для IMetric, предоставляемый атрибутом InheritedExport(typeof(IMetric)) на вашем интерфейсе.

Если вы посмотрите на контейнер, вы увидите два экспорта, определенные для MetricA. Вот первый, с его метаданными:

enter image description here

И вот второе:

enter image description here

Вы заметите, что метаданные выполняются при экспорте MetricA, а не в унаследованном экспорте. Если бы я добавил дополнительный экспорт, скажем [Export("test")] до MetricA, вы получите другое определение экспорта с теми же элементами метаданных для MetricName и MetricDescription для контракта с именем "test". Это показывает вам, что по мере анализа типа атрибут экспорта идентифицируется, а созданное определение экспорта включает метаданные, указанные на том же уровне в дереве абстракции.

Самый простой способ сделать то, что вы хотите, - это отказаться от InheritedExport и изменить свое определение вашего MetricAttribute на:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false), MetadataAttribute]
public class MetricAttribute : ExportAttribute, IMetricAttribute
{
    public MetricAttribute(string name, string description)
        : base(typeof(IMetric))
    {
        this.MetricName = name;
        this.MetricDescription = description;
    }

    public string MetricName { get; private set; }
    public string MetricDescription { get; private set; }

}

Если вы затем передаете typeof(IMetric) в базовый конструктор ExportAttribute. Затем вы правильно получите два экспорта для GetExports<IMetric>() и GetExports<IMetric, IMetricAttribute>().

Ответ 2

Я столкнулся с той же проблемой и нашел решение, отличное от меня: Я просто добавил метаданные в интерфейс!

[InheritedExport(typeof(IMetric))]
[Metric("name","description")]
public interface IMetric { ... }

Вы можете оставить поля пустыми или использовать null по умолчанию, но здесь важно указать метаданные. Затем вы указываете свои классы без атрибута экспорта:

[Metric("MetricA")]
public class MetricA: IMetric { ... }

Помните, что вы можете указать только одну метаданные, но в этом случае вторая не будет description, она будет null! Таким образом, метаданные в интерфейсе НЕ являются значениями по умолчанию. В целом это сработало для меня, и я могу использовать InheritedExport с моими метаданными: -)

Ответ 3

Чтобы прояснить ответ Мэтью:

Когда вы определяете собственный класс атрибутов метаданных MetricAttribute и наследуете от ExportAttribute, вы по существу добавляете атрибут [Export] ко всем классам, которые вы украшаете своим атрибутом [Metric]. Это означает, что вам больше не нужен атрибут [InheritedExport] на интерфейсе, поскольку он просто создает отдельное определение экспорта без каких-либо метаданных.

Если вы хотите создать более многоразовый атрибут метаданных, вы можете указать ExportAttribute параметры конструктора в MetricAttribute следующим образом:

public MetricAttribute(Type contractType, string name, string description)
    : base(contractType)
{
    this.MetricName = name;
    this.MetricDescription = description;
}

Введя переменную contractType, вы можете теперь добавить свое определение

[Export(typeof(IMetric))]
[Metric("MetricB", "MetricB")]
public class MetricB: IMetric { ... }

с:

[Metric(typeof(IMetric), "MetricB", "MetricB")]
public class MetricB: IMetric { ... }