Partilhar via


Visão geral do modelo de programação atribuído (MEF)

No Managed Extensibility Framework (MEF), um modelo de programação é um método particular de definição do conjunto de objetos conceituais nos quais o MEF opera. Esses objetos conceituais incluem peças, importações e exportações. O MEF usa esses objetos, mas não especifica como eles devem ser representados. Portanto, uma grande variedade de modelos de programação são possíveis, incluindo modelos de programação personalizados.

O modelo de programação padrão usado no MEF é o modelo de programação atribuído. Nas partes do modelo de programação atribuído, importações, exportações e outros objetos são definidos com atributos que decoram classes comuns do .NET Framework. Este tópico explica como usar os atributos fornecidos pelo modelo de programação atribuído para criar um aplicativo MEF.

Noções básicas de importação e exportação

Uma exportação é um valor que uma peça fornece a outras partes no contêiner, e uma importação é um requisito que uma peça expressa para o contêiner, a ser preenchida a partir das exportações disponíveis. No modelo de programação atribuído, as importações e exportações são declaradas decorando classes ou membros com os atributos Import e Export. O Export atributo pode decorar uma classe, campo, propriedade ou método, enquanto o Import atributo pode decorar um campo, propriedade ou parâmetro do construtor.

Para que uma importação possa ser acompanhada de uma exportação, a importação e a exportação devem ter o mesmo contrato. O contrato consiste em uma cadeia de caracteres, chamada de nome do contrato, e o tipo do objeto exportado ou importado, chamado de tipo de contrato. Somente se o nome do contrato e o tipo de contrato corresponderem a uma exportação considerada para atender a uma importação específica.

Um ou ambos os parâmetros do contrato podem ser implícitos ou explícitos. O código a seguir mostra uma classe que declara uma importação básica.

Public Class MyClass1
    <Import()>
    Public Property MyAddin As IMyAddin
End Class
public class MyClass
{
    [Import]
    public IMyAddin MyAddin { get; set; }
}

Nesta importação, o Import atributo não tem um tipo de contrato nem um parâmetro de nome de contrato anexado. Portanto, ambos serão inferidos a partir do imóvel decorado. Nesse caso, o tipo de contrato será IMyAddin, e o nome do contrato será uma cadeia de caracteres exclusiva criada a partir do tipo de contrato. (Em outras palavras, o nome do contrato corresponderá apenas às exportações cujos nomes também são inferidos do tipo IMyAddin.)

A seguir mostra uma exportação que corresponde à importação anterior.

<Export(GetType(IMyAddin))>
Public Class MyLogger
    Implements IMyAddin

End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }

Nesta exportação, o tipo de contrato é IMyAddin porque é especificado como um parâmetro do Export atributo. O tipo exportado deve ser o mesmo que o tipo de contrato, derivar do tipo de contrato ou implementar o tipo de contrato se for uma interface. Nesta exportação, o tipo MyLogger real implementa a interface IMyAddin. O nome do contrato é inferido a partir do tipo de contrato, o que significa que esta exportação corresponderá à importação anterior.

Observação

As exportações e importações devem geralmente ser especificadas em classes ou membros públicos. Há suporte para outras declarações, mas exportar ou importar um membro privado, protegido ou interno quebra o modelo de isolamento da peça e, portanto, não é recomendado.

O tipo de contrato deve corresponder exatamente para que a exportação e a importação sejam consideradas correspondentes. Considere a seguinte exportação.

<Export()> 'WILL NOT match the previous import!
Public Class MyLogger
    Implements IMyAddin

End Class
[Export] //WILL NOT match the previous import!
public class MyLogger : IMyAddin { }

Nesta exportação, o tipo de contrato é MyLogger em vez de IMyAddin. Mesmo que MyLogger implemente IMyAddin e, portanto, possa ser convertido em um objeto IMyAddin, esta exportação não corresponderá à importação anterior porque os tipos de IMyAddin contrato não são os mesmos.

Em geral, não é necessário especificar o nome do contrato, e a maioria dos contratos deve ser definida em termos do tipo de contrato e metadados. No entanto, em determinadas circunstâncias, é importante especificar diretamente o nome do contrato. O caso mais comum é quando uma classe exporta vários valores que compartilham um tipo comum, como primitivos. O nome do contrato pode ser especificado como o primeiro parâmetro do atributo Import ou Export. O código a seguir mostra uma importação e uma exportação com um nome de contrato especificado de MajorRevision.

Public Class MyExportClass

    'This one will match
    <Export("MajorRevision")>
    Public ReadOnly Property MajorRevision As Integer
        Get
            Return 4
        End Get
    End Property

    <Export("MinorRevision")>
    Public ReadOnly Property MinorRevision As Integer
        Get
            Return 16
        End Get
    End Property
End Class
public class MyClass
{
    [Import("MajorRevision")]
    public int MajorRevision { get; set; }
}

public class MyExportClass
{
    [Export("MajorRevision")] //This one will match.
    public int MajorRevision = 4;

    [Export("MinorRevision")]
    public int MinorRevision = 16;
}

Se o tipo de contrato não for especificado, ele ainda será inferido a partir do tipo de importação ou exportação. No entanto, mesmo que o nome do contrato seja especificado explicitamente, o tipo de contrato também deve corresponder exatamente para que a importação e a exportação sejam consideradas uma correspondência. Por exemplo, se o MajorRevision campo fosse uma cadeia de caracteres, os tipos de contrato inferidos não corresponderiam e a exportação não corresponderia à importação, apesar de ter o mesmo nome de contrato.

Importando e exportando um método

O Export atributo também pode decorar um método, da mesma forma que uma classe, propriedade ou função. As exportações de método devem especificar um tipo de contrato ou nome de contrato, pois o tipo não pode ser inferido. O tipo especificado pode ser um delegado personalizado ou um tipo genérico, como Func. A classe a seguir exporta um método chamado DoSomething.

Public Class MyAddin

    'Explicitly specifying a generic type
    <Export(GetType(Func(Of Integer, String)))>
    Public Function DoSomething(ByVal TheParam As Integer) As String
        Return Nothing 'Function body goes here
    End Function

End Class
public class MyAddin
{
    //Explicitly specifying a generic type.
    [Export(typeof(Func<int, string>))]
    public string DoSomething(int TheParam);
}

Nesta aula, o método DoSomething recebe um único parâmetro int e retorna um string. Para corresponder a essa exportação, a parte importadora deve declarar um membro apropriado. A classe a seguir importa o DoSomething método.

Public Class MyClass1

    'Contract name must match!
    <Import()>
    Public Property MajorRevision As Func(Of Integer, String)
End Class
public class MyClass
{
    [Import] //Contract name must match!
    public Func<int, string> DoSomething { get; set; }
}

Para obter mais informações sobre como usar o Func<T, T> objeto, consulte Func<T,TResult>.

Tipos de Importações

O MEF suporta vários tipos de importação, incluindo dinâmico, preguiçoso, pré-requisito e opcional.

Importações dinâmicas

Em alguns casos, a classe de importação pode querer corresponder a exportações de qualquer tipo que tenham um nome de contrato específico. Nesse cenário, a classe pode declarar uma importação dinâmica. A importação a seguir corresponde a qualquer exportação com o nome do contrato "TheString".

Public Class MyClass1

    <Import("TheString")>
    Public Property MyAddin

End Class
public class MyClass
{
    [Import("TheString")]
    public dynamic MyAddin { get; set; }
}

Quando o tipo de contrato é inferido a partir da palavra-chave dynamic, corresponderá a qualquer tipo de contrato. Nesse caso, uma importação deve sempre especificar um nome de contrato correspondente. (Se nenhum nome de contrato for especificado, a importação será considerada como não correspondendo a nenhuma exportação.) Ambas as exportações seguintes corresponderiam à importação anterior.

<Export("TheString", GetType(IMyAddin))>
Public Class MyLogger
    Implements IMyAddin

End Class

<Export("TheString")>
Public Class MyToolbar

End Class
[Export("TheString", typeof(IMyAddin))]
public class MyLogger : IMyAddin { }

[Export("TheString")]
public class MyToolbar { }

Obviamente, a classe importadora deve estar preparada para lidar com um objeto de tipo arbitrário.

Importações preguiçosas

Em alguns casos, a classe de importação pode exigir uma referência indireta ao objeto importado, para que o objeto não seja instanciado imediatamente. Nesse cenário, a classe pode declarar uma importação lenta usando um tipo de contrato de Lazy<T>. A propriedade de importação a seguir declara uma importação preguiçosa.

Public Class MyClass1

    <Import()>
    Public Property MyAddin As Lazy(Of IMyAddin)

End Class
public class MyClass
{
    [Import]
    public Lazy<IMyAddin> MyAddin { get; set; }
}

Do ponto de vista do motor de composição, um tipo de contrato de Lazy<T> é considerado idêntico ao tipo de contrato de T. Portanto, a importação anterior corresponderia à exportação seguinte.

<Export(GetType(IMyAddin))>
Public Class MyLogger
    Implements IMyAddin

End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }

O nome do contrato e o tipo de contrato podem ser especificados no atributo para uma importação lenta Import , conforme descrito anteriormente na seção "Importações e exportações básicas".

Importação de pré-requisitos

As peças MEF exportadas são normalmente criadas pelo motor de composição, em resposta a um pedido direto ou à necessidade de preencher uma importação correspondente. Por padrão, ao criar uma peça, o mecanismo de composição usa o construtor sem parâmetros. Para fazer com que o mecanismo use um construtor diferente, você pode marcá-lo com o ImportingConstructor atributo.

Cada peça pode ter apenas um construtor para utilização pelo motor de composição. Fornecer nenhum construtor sem parâmetros e nenhum ImportingConstructor atributo, ou fornecer mais de um ImportingConstructor atributo, produzirá um erro.

Para preencher os parâmetros de um construtor marcado com o ImportingConstructor atributo, todos esses parâmetros são automaticamente declarados como importações. Esta é uma maneira conveniente de declarar importações que são usadas durante a inicialização da peça. A classe a seguir usa ImportingConstructor para declarar uma importação.

Public Class MyClass1

    Private _theAddin As IMyAddin

    'Parameterless constructor will NOT be used
    'because the ImportingConstructor
    'attribute is present.
    Public Sub New()

    End Sub

    'This constructor will be used.
    'An import with contract type IMyAddin
    'is declared automatically.
    <ImportingConstructor()>
    Public Sub New(ByVal MyAddin As IMyAddin)
        _theAddin = MyAddin
    End Sub

End Class
public class MyClass
{
    private IMyAddin _theAddin;

    //Parameterless constructor will NOT be
    //used because the ImportingConstructor
    //attribute is present.
    public MyClass() { }

    //This constructor will be used.
    //An import with contract type IMyAddin is
    //declared automatically.
    [ImportingConstructor]
    public MyClass(IMyAddin MyAddin)
    {
        _theAddin = MyAddin;
    }
}

Por padrão, o ImportingConstructor atributo usa tipos de contrato inferidos e nomes de contrato para todas as importações de parâmetros. É possível substituir isso decorando os parâmetros com Import atributos, que podem definir o tipo de contrato e o nome do contrato explicitamente. O código a seguir demonstra um construtor que usa essa sintaxe para importar uma classe derivada em vez de uma classe pai.

<ImportingConstructor()>
Public Sub New(<Import(GetType(IMySubAddin))> ByVal MyAddin As IMyAddin)

End Sub
[ImportingConstructor]
public MyClass([Import(typeof(IMySubAddin))]IMyAddin MyAddin)
{
    _theAddin = MyAddin;
}

Em particular, você deve ter cuidado com os parâmetros de coleta. Por exemplo, se você especificar ImportingConstructor em um construtor com um parâmetro do tipo IEnumerable<int>, a importação corresponderá a uma única exportação do tipo IEnumerable<int>, em vez de um conjunto de exportações do tipo int. Para corresponder a um conjunto de exportações do tipo int, você precisa decorar o parâmetro com o ImportMany atributo.

Os parâmetros declarados como importações pelo atributo também são marcados ImportingConstructor como importações de pré-requisito. Normalmente, o MEF permite que as exportações e importações formem um ciclo. Por exemplo, um ciclo é onde o objeto A importa o objeto B, que por sua vez importa o objeto A. Em circunstâncias normais, um ciclo não é um problema, e o contêiner de composição constrói ambos os objetos normalmente.

Quando um valor importado é exigido pelo construtor de uma peça, esse objeto não pode participar de um ciclo. Se o objeto A exigir que o objeto B seja construído antes de poder ser construído por si mesmo, e o objeto B importar o objeto A, o ciclo não poderá ser resolvido e ocorrerá um erro de composição. As importações declaradas nos parâmetros do construtor são, portanto, importações de pré-requisito, que devem ser todas preenchidas antes que qualquer uma das exportações do objeto que as requer possa ser usada.

Importações opcionais

O Import atributo especifica um requisito para que a peça funcione. Se uma importação não puder ser cumprida, a composição dessa parte falhará e a peça não estará disponível.

Você pode especificar que uma importação é opcional usando a AllowDefault propriedade. Nesse caso, a composição será bem-sucedida mesmo se a importação não corresponder a nenhuma exportação disponível, e a propriedade de importação será definida como padrão para seu tipo de propriedade (null para propriedades de objeto, false para booleanos ou zero para propriedades numéricas). A classe a seguir usa uma importação opcional.

Public Class MyClass1

    <Import(AllowDefault:=True)>
    Public Property thePlugin As Plugin

    'If no matching export is available,
    'thePlugin will be set to null.
End Class
public class MyClass
{
    [Import(AllowDefault = true)]
    public Plugin thePlugin { get; set; }

    //If no matching export is available,
    //thePlugin will be set to null.
}

Importando vários objetos

O Import atributo só será composto com êxito quando corresponder a uma e apenas uma exportação. Outros casos produzirão um erro de composição. Para importar mais de uma exportação que corresponda ao mesmo contrato, use o ImportMany atributo. As importações marcadas com este atributo são sempre opcionais. Por exemplo, a composição não falhará se não existirem exportações correspondentes. A classe seguinte importa qualquer número de exportações do tipo IMyAddin.

Public Class MyClass1

    <ImportMany()>
    Public Property MyAddin As IEnumerable(Of IMyAddin)

End Class
public class MyClass
{
    [ImportMany]
    public IEnumerable<IMyAddin> MyAddin { get; set; }
}

A matriz importada pode ser acessada usando sintaxe e métodos comuns IEnumerable<T> . Também é possível usar uma matriz comum (IMyAddin[]) em vez disso.

Esse padrão pode ser muito importante quando você o usa em combinação com a Lazy<T> sintaxe. Por exemplo, usando ImportMany, IEnumerable<T>e Lazy<T>, você pode importar referências indiretas para qualquer número de objetos e apenas instanciar os que se tornarem necessários. A classe a seguir mostra esse padrão.

Public Class MyClass1

    <ImportMany()>
    Public Property MyAddin As IEnumerable(Of Lazy(Of IMyAddin))

End Class
public class MyClass
{
    [ImportMany]
    public IEnumerable<Lazy<IMyAddin>> MyAddin { get; set; }
}

Evitando a descoberta

Em alguns casos, convém impedir que uma peça seja descoberta como parte de um catálogo. Por exemplo, a peça pode ser uma classe base destinada a ser herdada, mas não usada. Há duas maneiras de conseguir isso. Primeiro, pode usar a palavra-chave abstract na classe parte. As classes abstratas nunca fornecem exportações, embora possam fornecer exportações herdadas para classes que derivam delas.

Se a classe não puder ser tornada abstrata, você pode decorá-la com o PartNotDiscoverable atributo. Uma parte decorada com este atributo não será incluída em nenhum catálogo. O exemplo a seguir demonstra esses padrões. DataOne será descoberto pelo catálogo. Como DataTwo é abstrato, não será descoberto. Como o atributo DataThree foi usado por PartNotDiscoverable, ele não será descoberto.

<Export()>
Public Class DataOne
    'This part will be discovered
    'as normal by the catalog.
End Class

<Export()>
Public MustInherit Class DataTwo
    'This part will not be discovered
    'by the catalog.
End Class

<PartNotDiscoverable()>
<Export()>
Public Class DataThree
    'This part will also not be discovered
    'by the catalog.
End Class
[Export]
public class DataOne
{
    //This part will be discovered
    //as normal by the catalog.
}

[Export]
public abstract class DataTwo
{
    //This part will not be discovered
    //by the catalog.
}

[PartNotDiscoverable]
[Export]
public class DataThree
{
    //This part will also not be discovered
    //by the catalog.
}

Metadados e Visualizações de Metadados

As exportações podem fornecer informações adicionais sobre si mesmas, conhecidas como metadados. Os metadados podem ser usados para transmitir as propriedades do objeto exportado para a parte importadora. A parte importadora pode usar esses dados para decidir quais exportações usar ou para coletar informações sobre uma exportação sem precisar construí-la. Por esse motivo, uma importação deve ter preguiça de usar metadados.

Para usar metadados, você normalmente declara uma interface conhecida como exibição de metadados, que declara quais metadados estarão disponíveis. A interface de exibição de metadados deve ter apenas propriedades, e essas propriedades devem ter get acessadores. A interface a seguir é um exemplo de exibição de metadados.

Public Interface IPluginMetadata

    ReadOnly Property Name As String

    <DefaultValue(1)>
    ReadOnly Property Version As Integer

End Interface
public interface IPluginMetadata
{
    string Name { get; }

    [DefaultValue(1)]
    int Version { get; }
}

Também é possível usar uma coleção genérica, IDictionary<string, object>como uma visão de metadados, mas isso perde os benefícios da verificação de tipo e deve ser evitado.

Normalmente, todas as propriedades nomeadas na visualização de metadados são necessárias, e quaisquer exportações que não as forneçam não serão consideradas uma correspondência. O DefaultValue atributo especifica que uma propriedade é opcional. Se a propriedade não estiver incluída, será atribuído o valor padrão especificado como um parâmetro de DefaultValue. A seguir estão duas classes diferentes decoradas com metadados. Ambas as classes corresponderiam à exibição de metadados anterior.

<Export(GetType(IPlugin))>
<ExportMetadata("Name", "Logger")>
<ExportMetadata("Version", 4)>
Public Class MyLogger
    Implements IPlugin

End Class

'Version is not required because of the DefaultValue
<Export(GetType(IPlugin))>
<ExportMetadata("Name", "Disk Writer")>
Public Class DWriter
    Implements IPlugin

End Class
[Export(typeof(IPlugin)),
    ExportMetadata("Name", "Logger"),
    ExportMetadata("Version", 4)]
public class Logger : IPlugin
{
}

[Export(typeof(IPlugin)),
    ExportMetadata("Name", "Disk Writer")]
    //Version is not required because of the DefaultValue
public class DWriter : IPlugin
{
}

Os metadados são expressos após o Export atributo usando o ExportMetadata atributo. Cada parte dos metadados é composta por um par nome/valor. A parte de nome dos metadados deve corresponder ao nome da propriedade apropriada na exibição de metadados e o valor será atribuído a essa propriedade.

É o importador que especifica qual exibição de metadados, se houver, estará em uso. Uma importação com metadados é declarada como uma importação lenta, com a interface de metadados como o segundo parâmetro de tipo para Lazy<T,T>. A classe a seguir importa a parte anterior com metadados.

Public Class Addin

    <Import()>
    Public Property plugin As Lazy(Of IPlugin, IPluginMetadata)
End Class
public class Addin
{
    [Import]
    public Lazy<IPlugin, IPluginMetadata> plugin;
}

Em muitos casos, você desejará combinar metadados com o ImportMany atributo, a fim de analisar as importações disponíveis e escolher e instanciar apenas uma, ou filtrar uma coleção para corresponder a uma determinada condição. A classe a seguir instancia somente IPlugin objetos que têm o Name valor "Logger".

Public Class User

    <ImportMany()>
    Public Property plugins As IEnumerable(Of Lazy(Of IPlugin, IPluginMetadata))

    Public Function InstantiateLogger() As IPlugin

        Dim logger As IPlugin
        logger = Nothing

        For Each Plugin As Lazy(Of IPlugin, IPluginMetadata) In plugins
            If Plugin.Metadata.Name = "Logger" Then
                logger = Plugin.Value
            End If
        Next
        Return logger
    End Function

End Class
public class User
{
    [ImportMany]
    public IEnumerable<Lazy<IPlugin, IPluginMetadata>> plugins;

    public IPlugin InstantiateLogger()
    {
        IPlugin logger = null;

        foreach (Lazy<IPlugin, IPluginMetadata> plugin in plugins)
        {
            if (plugin.Metadata.Name == "Logger")
                logger = plugin.Value;
        }
        return logger;
    }
}

Importação e exportação de heranças

Se uma classe herda de uma parte, essa classe também pode se tornar uma parte. As importações são sempre herdadas por subclasses. Portanto, uma subclasse de uma peça será sempre uma parte, com as mesmas importações que sua classe mãe.

As exportações declaradas usando o Export atributo não são herdadas por subclasses. No entanto, uma parte pode exportar a si mesma usando o InheritedExport atributo. As subclasses do elemento herdarão e executarão a mesma exportação, incluindo o nome e o tipo de contrato. Ao contrário de um Export atributo, InheritedExport pode ser aplicado apenas no nível da classe, e não no nível do membro. Portanto, as exportações de nível de membro nunca podem ser herdadas.

As quatro classes a seguir demonstram os princípios de herança de importação e exportação. NumTwo herda de NumOne, assim NumTwo importará IMyData. As exportações ordinárias não são herdadas, por isso NumTwo não exportam nada. NumFour herda de NumThree. Porque NumThree foi usado por InheritedExport, NumFour tem uma exportação com tipo de contrato NumThree. As exportações ao nível dos membros nunca são herdadas, pelo que IMyData não são exportadas.

<Export()>
Public Class NumOne
    <Import()>
    Public Property MyData As IMyData
End Class

Public Class NumTwo
    Inherits NumOne

    'Imports are always inherited, so NumTwo will
    'Import IMyData

    'Ordinary exports are not inherited, so
    'NumTwo will NOT export anything.  As a result it
    'will not be discovered by the catalog!

End Class

<InheritedExport()>
Public Class NumThree

    <Export()>
    Public Property MyData As IMyData

    'This part provides two exports, one of
    'contract type NumThree, and one of
    'contract type IMyData.

End Class

Public Class NumFour
    Inherits NumThree

    'Because NumThree used InheritedExport,
    'this part has one export with contract
    'type NumThree.

    'Member-level exports are never inherited,
    'so IMyData is not exported.

End Class
[Export]
public class NumOne
{
    [Import]
    public IMyData MyData { get; set; }
}

public class NumTwo : NumOne
{
    //Imports are always inherited, so NumTwo will
    //import IMyData.

    //Ordinary exports are not inherited, so
    //NumTwo will NOT export anything. As a result it
    //will not be discovered by the catalog!
}

[InheritedExport]
public class NumThree
{
    [Export]
    Public IMyData MyData { get; set; }

    //This part provides two exports, one of
    //contract type NumThree, and one of
    //contract type IMyData.
}

public class NumFour : NumThree
{
    //Because NumThree used InheritedExport,
    //this part has one export with contract
    //type NumThree.

    //Member-level exports are never inherited,
    //so IMyData is not exported.
}

Se houver metadados associados a um InheritedExport atributo, esses metadados também serão herdados. (Para obter mais informações, consulte a seção anterior "Metadados e Exibições de metadados".) Os metadados herdados não podem ser modificados pela subclasse. No entanto, ao redeclarar o InheritedExport atributo com o mesmo nome de contrato e tipo de contrato, mas com novos metadados, a subclasse pode substituir os metadados herdados por novos metadados. A classe a seguir demonstra esse princípio. A parte MegaLogger herda de Logger e inclui o atributo InheritedExport. Como MegaLogger redeclara novos metadados chamados Status, ele não herda os metadados Nome e Versão do Logger.

<InheritedExport(GetType(IPlugin))>
<ExportMetadata("Name", "Logger")>
<ExportMetadata("Version", 4)>
Public Class Logger
    Implements IPlugin

    'Exports with contract type IPlugin
    'and metadata "Name" and "Version".
End Class

Public Class SuperLogger
    Inherits Logger

    'Exports with contract type IPlugin and
    'metadata "Name" and "Version", exactly the same
    'as the Logger class.

End Class

<InheritedExport(GetType(IPlugin))>
<ExportMetadata("Status", "Green")>
Public Class MegaLogger
    Inherits Logger

    'Exports with contract type IPlugin and
    'metadata "Status" only. Re-declaring
    'the attribute replaces all metadata.

End Class
[InheritedExport(typeof(IPlugin)),
    ExportMetadata("Name", "Logger"),
    ExportMetadata("Version", 4)]
public class Logger : IPlugin
{
    //Exports with contract type IPlugin and
    //metadata "Name" and "Version".
}

public class SuperLogger : Logger
{
    //Exports with contract type IPlugin and
    //metadata "Name" and "Version", exactly the same
    //as the Logger class.
}

[InheritedExport(typeof(IPlugin)),
    ExportMetadata("Status", "Green")]
public class MegaLogger : Logger        {
    //Exports with contract type IPlugin and
    //metadata "Status" only. Re-declaring
    //the attribute replaces all metadata.
}

Ao declarar novamente o InheritedExport atributo para substituir metadados, certifique-se de que os tipos de contrato sejam os mesmos. (No exemplo anterior, IPlugin é o tipo de contrato.) Se os atributos diferirem, em vez de o segundo atributo substituir o primeiro, ele criará uma segunda exportação independente da parte. Geralmente, isso significa que você terá que especificar explicitamente o tipo de contrato quando substituir um InheritedExport atributo, como mostrado no exemplo anterior.

Como as interfaces não podem ser instanciadas diretamente, elas geralmente não podem ser decoradas com Export ou Import atributos. No entanto, uma interface pode ser decorada com um InheritedExport atributo no nível da interface, e essa exportação junto com quaisquer metadados associados será herdada por qualquer classe de implementação. No entanto, a interface em si não estará disponível como parte.

Atributos de exportação personalizados

Os atributos Export básicos de exportação e InheritedExport, podem ser estendidos para incluir metadados como propriedades de atributo. Essa técnica é útil para aplicar metadados semelhantes a muitas partes ou criar uma árvore de herança de atributos de metadados.

Um atributo personalizado pode especificar o tipo de contrato, o nome do contrato ou quaisquer outros metadados. Para definir um atributo personalizado, uma classe que herde de ExportAttribute ou InheritedExportAttribute deve ser decorada com o atributo MetadataAttribute. A classe a seguir define um atributo personalizado.

<MetadataAttribute()>
<AttributeUsage(AttributeTargets.Class, AllowMultiple:=false)>
Public Class MyAttribute
    Inherits ExportAttribute

    Public Property MyMetadata As String

    Public Sub New(ByVal myMetadata As String)
        MyBase.New(GetType(IMyAddin))

        myMetadata = myMetadata
    End Sub

End Class
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class MyAttribute : ExportAttribute
{
    public MyAttribute(string myMetadata)
        : base(typeof(IMyAddin))
    {
        MyMetadata = myMetadata;
    }

    public string MyMetadata { get; private set; }
}

Essa classe define um atributo personalizado nomeado MyAttribute com tipo IMyAddin de contrato e alguns metadados chamados MyMetadata. Todas as propriedades em uma classe marcada com o MetadataAttribute atributo são consideradas metadados definidos no atributo personalizado. As duas declarações seguintes são equivalentes.

<Export(GetType(IMyAddin))>
<ExportMetadata("MyMetadata", "theData")>
Public Property myAddin As MyAddin
<MyAttribute("theData")>
Public Property myAddin As MyAddin
[Export(typeof(IMyAddin)),
    ExportMetadata("MyMetadata", "theData")]
public MyAddin myAddin { get; set; }
[MyAttribute("theData")]
public MyAddin myAddin { get; set; }

Na primeira declaração, o tipo de contrato e os metadados são explicitamente definidos. Na segunda declaração, o tipo de contrato e os metadados estão implícitos no atributo personalizado. Particularmente nos casos em que uma grande quantidade de metadados idênticos deve ser aplicada a muitas partes (por exemplo, informações de autor ou direitos autorais), usar um atributo personalizado pode economizar muito tempo e duplicação. Além disso, árvores de herança de atributos personalizados podem ser criadas para permitir variações.

Para criar metadados opcionais em um atributo personalizado, você pode usar o DefaultValue atributo. Quando esse atributo é aplicado a uma propriedade em uma classe de atributo personalizada, ele especifica que a propriedade decorada é opcional e não precisa ser fornecida por um exportador. Se um valor para a propriedade não for fornecido, será atribuído o valor padrão para seu tipo de propriedade (geralmente null, false, ou 0.)

Políticas de Criação

Quando uma peça especifica uma importação e a composição é executada, o contêiner de composição tenta encontrar uma exportação correspondente. Se uma importação corresponder a uma exportação com sucesso, o membro importador é configurado como uma instância do objeto exportado. A origem desta instância é controlada pela política de criação da parte exportadora.

As duas políticas de criação possíveis são compartilhadas e não compartilhadas. Uma peça com uma política de criação compartilhada será compartilhada entre cada importação no contêiner para uma parte com esse contrato. Quando o mecanismo de composição encontrar uma correspondência e tiver que definir uma propriedade de importação, ele instanciará uma nova cópia da peça somente se ainda não existir uma; caso contrário, fornecerá a cópia existente. Isso significa que muitos objetos podem ter referências à mesma parte. Essas partes não devem depender de um estado interno que possa ser alterado em muitos locais. Esta política é apropriada para partes estáticas, peças que fornecem serviços e peças que consomem muita memória ou outros recursos.

Uma parte com a política de criação de não-compartilhada será criada toda vez que uma importação correspondente para uma de suas exportações for encontrada. Uma nova cópia será, portanto, instanciada para cada importação no contêiner que corresponda a um dos contratos exportados da peça. O estado interno destas cópias não será partilhado. Esta política é apropriada para peças em que cada importação requer seu próprio estado interno.

Tanto a importação quanto a exportação podem especificar a política de criação de uma peça, entre os valores Shared, NonSharedou Any. O padrão é Any para importações e exportações. Uma exportação que especifica Shared ou NonShared corresponderá apenas a uma importação que especifica o mesmo ou que especifica Any. Da mesma forma, uma importação que especifica Shared ou NonShared corresponderá apenas a uma exportação que especifica o mesmo, ou que especifica Any. As importações e exportações com políticas de criação incompatíveis não são consideradas uma correspondência, da mesma forma que uma importação e exportação cujo nome de contrato ou tipo de contrato não são correspondentes. Se importar e exportar especificar Any, ou não especificar uma política de criação e usar o padrão para Any, a política de criação será compartilhada por padrão.

O exemplo a seguir mostra importações e exportações especificando políticas de criação. PartOne não especifica uma política de criação, portanto, o padrão é Any. PartTwo não especifica uma política de criação, portanto, o padrão é Any. Como tanto a importação quanto a exportação têm como padrão Any, PartOne será partilhado. PartThree especifica uma Shared política de criação, portanto PartTwo , e PartThree compartilhará a mesma cópia do PartOne. PartFour especifica uma política de NonShared criação, portanto PartFour não será compartilhada no PartFive. PartSix Especifica uma política de NonShared criação. PartFive e PartSix cada um receberá cópias separadas do PartFour. PartSeven Especifica uma política de Shared criação. Como não existe PartFour exportado com uma política de criação de Shared, a importação de PartSeven não coincide com nada e não será preenchida.

<Export()>
Public Class PartOne
    'The default creation policy for an export is Any.
End Class

Public Class PartTwo

    <Import()>
    Public Property partOne As PartOne

    'The default creation policy for an import is Any.
    'If both policies are Any, the part will be shared.

End Class

Public Class PartThree

    <Import(RequiredCreationPolicy:=CreationPolicy.Shared)>
    Public Property partOne As PartOne

    'The Shared creation policy is explicitly specified.
    'PartTwo and PartThree will receive references to the
    'SAME copy of PartOne.

End Class

<Export()>
<PartCreationPolicy(CreationPolicy.NonShared)>
Public Class PartFour
    'The NonShared creation policy is explicitly specified.
End Class

Public Class PartFive

    <Import()>
    Public Property partFour As PartFour

    'The default creation policy for an import is Any.
    'Since the export's creation policy was explicitly
    'defined, the creation policy for this property will
    'be non-shared.

End Class

Public Class PartSix

    <Import(RequiredCreationPolicy:=CreationPolicy.NonShared)>
    Public Property partFour As PartFour

    'Both import and export specify matching creation
    'policies.  PartFive and PartSix will each receive
    'SEPARATE copies of PartFour, each with its own
    'internal state.

End Class

Public Class PartSeven

    <Import(RequiredCreationPolicy:=CreationPolicy.Shared)>
    Public Property partFour As PartFour

    'A creation policy mismatch.  Because there is no
    'exported PartFour with a creation policy of Shared,
    'this import does not match anything and will not be
    'filled.

End Class
[Export]
public class PartOne
{
    //The default creation policy for an export is Any.
}

public class PartTwo
{
    [Import]
    public PartOne partOne { get; set; }

    //The default creation policy for an import is Any.
    //If both policies are Any, the part will be shared.
}

public class PartThree
{
    [Import(RequiredCreationPolicy = CreationPolicy.Shared)]
    public PartOne partOne { get; set; }

    //The Shared creation policy is explicitly specified.
    //PartTwo and PartThree will receive references to the
    //SAME copy of PartOne.
}

[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class PartFour
{
    //The NonShared creation policy is explicitly specified.
}

public class PartFive
{
    [Import]
    public PartFour partFour { get; set; }

    //The default creation policy for an import is Any.
    //Since the export's creation policy was explicitly
    //defined, the creation policy for this property will
    //be non-shared.
}

public class PartSix
{
    [Import(RequiredCreationPolicy = CreationPolicy.NonShared)]
    public PartFour partFour { get; set; }

    //Both import and export specify matching creation
    //policies.  PartFive and PartSix will each receive
    //SEPARATE copies of PartFour, each with its own
    //internal state.
}

public class PartSeven
{
    [Import(RequiredCreationPolicy = CreationPolicy.Shared)]
    public PartFour partFour { get; set; }

    //A creation policy mismatch.  Because there is no
    //exported PartFour with a creation policy of Shared,
    //this import does not match anything and will not be
    //filled.
}

Ciclo de vida e eliminação

Como as peças são hospedadas no contêiner de composição, seu ciclo de vida pode ser mais complexo do que objetos comuns. As peças podem implementar duas interfaces importantes relacionadas ao ciclo de vida: IDisposable e IPartImportsSatisfiedNotification.

As partes que exigem que o trabalho seja executado no desligamento ou que precisam liberar recursos devem implementar IDisposable, como de costume para objetos do .NET Framework. No entanto, como o contêiner cria e mantém referências a partes, somente o contêiner que possui uma peça deve chamar o Dispose método nele. O próprio contêiner implementa IDisposable e, como parte de sua limpeza em Dispose, ele chamará Dispose em todas as partes que possui. Por esta razão, deve sempre eliminar o recipiente de composição quando este e quaisquer peças que possua já não forem necessários.

Para recipientes de composição de longa duração, o consumo de memória por partes com uma política de criação não partilhada pode tornar-se um problema. Essas peças não compartilhadas podem ser criadas várias vezes e não serão descartadas até que o próprio recipiente seja descartado. Para lidar com isso, o contêiner fornece o ReleaseExport método. Chamar esse método em uma exportação não compartilhada remove essa exportação do contêiner de composição e a descarta. As peças que são usadas apenas pela exportação removida, e assim por diante na árvore, também são removidas e descartadas. Desta forma, os recursos podem ser recuperados sem descartar o próprio recipiente de composição.

IPartImportsSatisfiedNotification Contém um método chamado OnImportsSatisfied. Este método é chamado pelo container de composição em todos os componentes que implementam a interface quando a composição estiver concluída e as importações do componente estejam prontas para uso. As peças são criadas pelo motor de composição para preencher as importações de outras peças. Antes que as importações de uma peça tenham sido definidas, você não pode executar qualquer inicialização que dependa ou manipule valores importados no construtor da peça, a menos que esses valores tenham sido especificados como pré-requisitos usando o ImportingConstructor atributo. Este é normalmente o método preferido, mas em alguns casos, a injeção do construtor pode não estar disponível. Nesses casos, a inicialização pode ser executada em OnImportsSatisfied, e a peça deve implementar IPartImportsSatisfiedNotification.