Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
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
, NonShared
ou 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
.