Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
No MEF (Managed Extensibility Framework), um modelo de programação é um método específico para definir o conjunto de objetos conceituais nos quais o MEF opera. Esses objetos conceituais incluem partes, importações e exportações. O MEF usa esses objetos, mas não especifica como eles devem ser representados. Portanto, uma ampla 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ídas, 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 parte fornece a outras partes no contêiner, e uma importação é um requisito que uma parte expressa ao contêiner, a ser preenchido com as exportações disponíveis. No modelo de programação atribuído, as importações e exportações são declaradas por meio da decoração de 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, uma propriedade ou um parâmetro de construtor.
Para que uma importação seja correspondida a 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 é uma exportação considerada para atender a uma importação específica.
Ambos ou ambos os parâmetros de 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; }
}
Nessa 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 da propriedade decorada. 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.)
O seguinte 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 { }
Nessa exportação, o tipo de contrato é IMyAddin porque ele é especificado como um parâmetro do Export atributo. O tipo exportado deve ser igual ao 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 do tipo de contrato, o que significa que essa exportação corresponderá à importação anterior.
Observação
Exportações e importações geralmente devem ser declaradas em classes ou membros públicos. Há suporte para outras declarações, mas exportar ou importar um membro privado, protegido ou interno interrompe o modelo de isolamento da parte e, portanto, não é recomendado.
O tipo de contrato deve corresponder exatamente para que a exportação e a importação sejam consideradas compatíveis. Considere a exportação a seguir.
<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. Embora MyLogger implemente IMyAddin e, portanto, possa ser convertido em um objeto IMyAddin, essa exportação não corresponderá à importação anterior porque os tipos de 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 de tipo de contrato e metadados. No entanto, em determinadas circunstâncias, é importante especificar o nome do contrato diretamente. 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 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 do 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 um 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);
}
Nessa classe, o DoSomething método usa um único int parâmetro e retorna um string. Para corresponder a essa exportação, a parte de importação 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 oferece suporte a vários tipos de importação, incluindo importação dinâmica, lenta, obrigatória e opcional.
Importações Dinâmicas
Em alguns casos, a classe de importação pode querer corresponder 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 for inferido da dynamic palavra-chave, ele corresponderá a qualquer tipo de contrato. Nesse caso, uma importação deve sempre especificar um nome de contrato para correspondência. (Se nenhum nome de contrato for especificado, a importação será considerada para corresponder a nenhuma exportação.) Ambas as exportações a seguir 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 lentas
Em alguns casos, a classe de importação pode exigir uma referência indireta ao objeto importado, de modo 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 lenta.
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 mecanismo de composição, um tipo de contrato Lazy<T> é considerado idêntico ao tipo de contrato T. Portanto, a importação anterior corresponderia à exportação a seguir.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
O nome do contrato e o Import tipo de contrato podem ser especificados no atributo para uma importação lenta, conforme descrito anteriormente na seção "Importações e Exportações Básicas".
Importações obrigatórias
As partes MEF exportadas normalmente são criadas pelo mecanismo de composição, em resposta a uma solicitação direta ou à necessidade de preencher uma importação correspondente. Por padrão, ao criar uma parte, 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 parte pode ter somente um construtor para uso por parte do mecanismo de composição. Nã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 declarados automaticamente como importações. Essa é uma maneira conveniente de declarar importações que são usadas durante a inicialização de parte. 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âmetro. É possível substituir isso decorando os parâmetros com Import atributos, que podem definir explicitamente o tipo de contrato e o nome do contrato. 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 tomar cuidado com parâmetros de coleção. Por exemplo, se você especificar ImportingConstructor em um construtor com um parâmetro de tipo IEnumerable<int>, a importação corresponderá a uma única exportação de 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.
Parâmetros declarados como importações pelo ImportingConstructor atributo também são marcados como importações de pré-requisito. O MEF normalmente permite exportações e importações para formar 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 comuns, 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 parte, esse objeto não pode participar de um ciclo. Se o objeto A exigir que o objeto B seja construído antes de ser construído sozinho 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 em parâmetros de construtor são, portanto, importações de pré-requisito, que devem ser preenchidas antes que qualquer uma das exportações do objeto que as exija possa ser usada.
Importações opcionais
O Import atributo especifica um requisito para que a parte funcione. Se uma importação não puder ser atendida, a composição dessa parte falhará e a parte não estará disponível.
Você pode especificar que uma importação é opcional usando a AllowDefault propriedade. Nesse caso, a composição terá êxito mesmo se a importação não corresponder a nenhuma exportação disponível, e a propriedade de importação será definida como o padrão para seu tipo de propriedade (null para propriedades de objeto, false para boolianos 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 esse atributo são sempre opcionais. Por exemplo, a composição não falhará se nenhuma exportação correspondente estiver presente. A classe a seguir 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 tornam 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, talvez você queira impedir que uma parte seja descoberta como parte de um catálogo. Por exemplo, a parte pode ser uma classe base da qual deve ser herdada, mas não usada. Há duas maneiras de fazer isso. Primeiramente, você pode usar a palavra-chave abstract na classe da parte. Classes abstratas nunca fornecem exportações, embora possam fornecer exportações herdadas para classes que derivam delas.
Se a classe não puder ser abstrata, você poderá decorá-la com o PartNotDiscoverable atributo. Uma parte decorada com esse 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, ele não será descoberto. Já que DataThree usou o atributo 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 Visões de Metadados
As exportações podem fornecer informações adicionais sobre si mesmas conhecidas como metadados. Os metadados podem ser usados para transmitir propriedades do objeto exportado para a parte de importação. A parte de importação pode usar esses dados para decidir quais exportações usar ou coletar informações sobre uma exportação sem precisar construí-la. Por esse motivo, uma importação deve ser lenta para usar metadados.
Para usar metadados, normalmente você declara uma interface conhecida como uma 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 é uma exibição de metadados de exemplo.
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 exibição de metadados, mas isso perde os benefícios da verificação de tipo e deve ser evitado.
Normalmente, todas as propriedades nomeadas na exibição de metadados são necessárias e todas as exportações que não as fornecem não serão consideradas uma correspondência. O DefaultValue atributo especifica que uma propriedade é opcional. Se a propriedade não estiver incluída, ela receberá o valor padrão especificado como um parâmetro de DefaultValue. Veja a seguir duas classes diferentes decoradas com metadados. Ambas as classes corresponderiam ao modo de 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 de nome/valor. A parte do 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 um ou filtrar uma coleção para corresponder a uma determinada condição. A classe a seguir instancia apenas objetos IPlugin que têm o valor Name "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;
}
}
Herança de importações e exportações
Se uma classe herda de uma parte, essa classe também pode se tornar parte. As importações são sempre herdadas por subclasses. Portanto, uma subclasse de uma parte sempre será uma parte, com as mesmas importações que sua classe pai.
As exportações declaradas usando o Export atributo não são herdadas por subclasses. No entanto, uma parte pode se exportar usando o InheritedExport atributo. As subclasses da parte herdarão e fornecerão a mesma exportação, incluindo o nome do contrato e o tipo de contrato. Ao contrário de um Export atributo, InheritedExport pode ser aplicado somente 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 da herança de importação e exportação.
NumTwo herda de NumOne, portanto NumTwo , importará IMyData. As exportações comuns não são herdadas, portanto NumTwo , não exportarão nada.
NumFour herda de NumThree. Como NumThree usou InheritedExport, NumFour tem uma exportação com o tipo de contrato NumThree. Exportações no nível do membro nunca são herdadas, portanto, IMyData não é exportado.
<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".) Metadados herdados não podem ser modificados pela subclasse. No entanto, ao declarar novamente 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 MegaLogger parte herda Logger e inclui o InheritedExport atributo. Como MegaLogger declara novamente novos metadados chamados Status, ele não herda os metadados de Nome e Versão de 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, verifique se os tipos de contrato são os mesmos. (No exemplo anterior, IPlugin é o tipo de contrato.) Se forem diferentes, em vez de substituir, o segundo atributo criará uma segunda exportação independente da parte. Em geral, isso significa que você precisará especificar explicitamente o tipo de contrato ao substituir um InheritedExport atributo, conforme 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 os 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 básicos Export 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 herdada de ExportAttribute (ou InheritedExportAttribute) deve ser decorada com o MetadataAttribute atributo. 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 o tipo IMyAddin de contrato e alguns metadados nomeados MyMetadata. Todas as propriedades em uma classe marcada com o MetadataAttribute atributo são consideradas metadadas definidas no atributo personalizado. As duas declarações a seguir 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 definidos explicitamente. Na segunda declaração, o tipo de contrato e os metadados estão implícitos no atributo personalizado. Especialmente 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), o uso de 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, ele receberá o valor padrão para seu tipo de propriedade (geralmente null, falseou 0.)
Políticas de criação
Quando uma parte especifica uma importação e uma composição é executada, o contêiner de composição tenta encontrar uma exportação correspondente. Se a importação for correspondida a uma exportação com êxito, o membro da importação é definido como uma instância do objeto exportado. O lugar de origem dessa instância é controlado pela política de criação da parte exportadora.
As duas possíveis políticas de criação são compartilhadas e não compartilhadas. Uma parte 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 precisar definir uma propriedade de importação, ele instanciará uma nova cópia do componente somente se ainda não existir; caso contrário, ele fornecerá a cópia existente. Isso significa que muitos objetos podem ter referências à mesma parte. Essas partes não devem depender do estado interno que pode ser alterado a partir de diversos locais. Essa política é apropriada para partes estáticas, partes que fornecem serviços e partes que consomem muita memória ou outros recursos.
Uma parte com a política de criação não compartilhada será criada toda vez que uma importação correspondente para uma das exportações for encontrada. Uma nova cópia será, portanto, instanciada para cada importação no contêiner que corresponder a um dos contratos exportados da parte. O estado interno dessas cópias não será compartilhado. Essa política é apropriada para partes em que cada importação requer seu próprio estado interno.
A importação e a exportação podem especificar a política de criação de uma parte, entre os valores Shared, NonSharedou Any. O padrão é Any para importações e exportações. Uma exportação que especifica Shared ou NonShared corresponde apenas a uma importação que especifica o mesmo ou que especifica Any. Da mesma forma, uma importação que especifica Shared ou NonShared corresponde apenas a uma exportação que especifica o mesmo ou que especifica Any. Importações e exportações com políticas de criação incompatíveis não são consideradas compatíveis, assim como importações e exportações cujos nome de contrato ou tipo de contrato não são compatíveis. Se a importação e a exportação especificarem Any, ou não especificarem uma política de criação e o padrão for definido como Any, a política de criação será definida como compartilhada por padrão.
O exemplo a seguir mostra importações e exportações que especificam 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 o padrão da importação e da exportação é Any, PartOne será compartilhada.
PartThree especifica uma Shared política de criação, portanto PartTwo , e PartThree compartilhará a mesma cópia de PartOne.
PartFour especifica uma NonShared política de criação, portanto PartFour não será compartilhada em PartFive.
PartSix especifica uma NonShared política de criação.
PartFive e PartSix cada um receberá cópias separadas de PartFour.
PartSeven especifica uma Shared política de criação. Como não há nenhuma exportação PartFour com uma política de criação de Shared, a importação PartSeven não corresponde a 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 descarte
Como as partes são hospedadas no contêiner de composição, seu ciclo de vida pode ser mais complexo do que objetos comuns. As partes podem implementar duas interfaces importantes relacionadas ao ciclo de vida: IDisposable e IPartImportsSatisfiedNotification.
As partes que exigem execução de trabalho durante o 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 parte deve chamar o Dispose método nele. O contêiner em si implementa IDisposable e, como parte de sua limpeza em Dispose, ele chamará Dispose em todas as partes que possui. Por esse motivo, você sempre deve descartar o contêiner de composição quando ele e todas as partes que ele possui não forem mais necessários.
Para contêineres de composição de longa vida, o consumo de memória pelas partes com uma política de criação não compartilhada pode se tornar um problema. Essas partes não compartilhadas podem ser criadas várias vezes e não serão descartadas até que o próprio contêiner 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 o descarta. Partes que são usadas somente pela exportação removida, e assim por diante na árvore, também são removidas e descartadas. Dessa forma, os recursos podem ser recuperados sem descartar o próprio contêiner de composição.
IPartImportsSatisfiedNotification contém um método chamado OnImportsSatisfied. Esse método é chamado pelo contêiner de composição em qualquer parte que implementar a interface quando a composição tiver sido concluída e as importações da parte estiverem prontas para uso. As partes são criadas pelo mecanismo de composição para preencher as importações de outras partes. Antes que as importações de uma parte tenham sido definidas, você não pode executar nenhuma inicialização que dependa ou manipule valores importados no construtor de peças, a menos que esses valores tenham sido especificados como pré-requisitos usando o ImportingConstructor atributo. Normalmente, esse é o método preferido, mas, em alguns casos, a injeção do construtor pode não estar disponível. Na maioria dos casos, a inicialização pode ser realizada em OnImportsSatisfied, e a parte deve implementar IPartImportsSatisfiedNotification.