Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En Managed Extensibility Framework (MEF), un modelo de programación es un método determinado para definir el conjunto de objetos conceptuales en los que funciona MEF. Estos objetos conceptuales incluyen partes, importaciones y exportaciones. MEF usa estos objetos, pero no especifica cómo se deben representar. Por lo tanto, es posible una amplia variedad de modelos de programación, incluidos los modelos de programación personalizados.
El modelo de programación predeterminado usado en MEF es el modelo de programación con atributos. En las partes del modelo de programación con atributos, las importaciones, las exportaciones y otros objetos se definen con atributos que decoran clases normales de .NET Framework. En este tema se explica cómo usar los atributos proporcionados por el modelo de programación con atributos para crear una aplicación MEF.
Conceptos básicos de importación y exportación
Una exportación es un valor que una parte proporciona a otras partes del contenedor y una importación es un requisito que un elemento expresa al contenedor para rellenarse a partir de las exportaciones disponibles. En el modelo de programación con atributos, las importaciones y exportaciones se declaran mediante la decoración de clases o miembros con los atributos Import
y Export
. El Export
atributo puede decorar una clase, un campo, una propiedad o un método, mientras que el Import
atributo puede decorar un campo, una propiedad o un parámetro de constructor.
Para que una importación coincida con una exportación, la importación y exportación debe tener el mismo contrato. El contrato consta de una cadena, denominada nombre del contrato, y el tipo del objeto exportado o importado, denominado tipo de contrato. Solo si el nombre del contrato y el tipo de contrato coinciden es una exportación que se considera que cumple una importación determinada.
Los parámetros del contrato o ambos pueden ser implícitos o explícitos. El código siguiente muestra una clase que declara una importación básica.
Public Class MyClass1
<Import()>
Public Property MyAddin As IMyAddin
End Class
public class MyClass
{
[Import]
public IMyAddin MyAddin { get; set; }
}
En esta importación, el Import
atributo no tiene ningún tipo de contrato ni un parámetro de nombre de contrato adjunto. Por consiguiente, ambos se deducirán de la propiedad decorada. En este caso, el tipo de contrato será IMyAddin
y el nombre del contrato será una cadena única creada a partir del tipo de contrato. (En otras palabras, el nombre del contrato coincidirá solo con las exportaciones cuyos nombres también se deducen del tipo IMyAddin
).
A continuación se muestra una exportación que coincide con la importación anterior.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
En esta exportación, el tipo de contrato es IMyAddin
porque se especifica como parámetro del atributo Export
. El tipo exportado debe ser el mismo que el tipo de contrato, derivar del tipo de contrato o implementar el tipo de contrato si es una interfaz. En esta exportación, el tipo MyLogger
real implementa la interfaz IMyAddin
. El nombre del contrato se deduce del tipo de contrato, lo que significa que esta exportación coincidirá con la importación anterior.
Nota:
Las exportaciones y las importaciones se deben declarar en clases o miembros públicos. Se admiten otras declaraciones, pero la exportación o importación de un miembro privado, protegido o interno interrumpe el modelo de aislamiento del elemento y, por tanto, no se recomienda.
El tipo de contrato debe coincidir exactamente para que la exportación e importación se considere una coincidencia. Observe la exportación siguiente.
<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 { }
En esta exportación, el tipo de contrato es MyLogger
en lugar de IMyAddin
. Aunque MyLogger
implemente IMyAddin
y, por tanto, podría convertirse en un IMyAddin
objeto , esta exportación no coincidirá con la importación anterior porque los tipos de contrato no son los mismos.
En general, no es necesario especificar el nombre del contrato y la mayoría de los contratos deben definirse en términos del tipo de contrato y los metadatos. Sin embargo, en determinadas circunstancias, es importante especificar directamente el nombre del contrato. El caso más común es cuando una clase exporta varios valores que comparten un tipo común, como primitivos. El nombre del contrato se puede especificar como el primer parámetro del Import
atributo o Export
. El código siguiente muestra una importación y una exportación con un nombre 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;
}
Si no se especifica el tipo de contrato, todavía se deducirá del tipo de importación o exportación. Sin embargo, aunque el nombre del contrato se especifique explícitamente, el tipo de contrato también debe coincidir exactamente para que la importación y exportación se consideren una coincidencia. Por ejemplo, si el MajorRevision
campo era una cadena, los tipos de contrato inferidos no coincidirían y la exportación no coincidiría con la importación, a pesar de tener el mismo nombre de contrato.
Importación y exportación de un método
El Export
atributo también puede decorar un método, de la misma manera que una clase, propiedad o función. Las exportaciones de métodos deben especificar un tipo de contrato o un nombre de contrato, ya que el tipo no se puede deducir. El tipo especificado puede ser un delegado personalizado o un tipo genérico, como Func
. La siguiente clase exporta un método denominado 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);
}
En esta clase, el DoSomething
método toma un único int
parámetro y devuelve un string
. Para coincidir con esta exportación, la parte de la importación debe declarar un miembro adecuado. La siguiente clase importa el 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 obtener más información sobre cómo usar el Func<T, T>
objeto , vea Func<T,TResult>.
Tipos de importaciones
MEF admite varios tipos de importación: dinámica, diferida, de requisito previo y opcional.
Importaciones dinámicas
En algunos casos, la clase de importación puede querer coincidir con las exportaciones de cualquier tipo que tengan un nombre de contrato determinado. En este escenario, la clase puede declarar una importación dinámica. La importación siguiente coincide con cualquier exportación cuyo nombre de contrato sea "TheString".
Public Class MyClass1
<Import("TheString")>
Public Property MyAddin
End Class
public class MyClass
{
[Import("TheString")]
public dynamic MyAddin { get; set; }
}
Cuando el tipo de contrato se deduce de la dynamic
palabra clave , coincidirá con cualquier tipo de contrato. En este caso, una importación siempre debe especificar un nombre de contrato para que coincida. (Si no se especifica ningún nombre de contrato, la importación se considerará que no coincide con ninguna exportación). Ambas exportaciones siguientes coincidirían con la importación 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, la clase importadora debe estar preparada para gestionar un objeto de tipo arbitrario.
Importaciones diferidas
En algunos casos, la clase importadora puede requerir una referencia indirecta al objeto importado, para que el objeto no se instancie inmediatamente. En este escenario, la clase puede declarar una importación diferida utilizando un tipo de contrato Lazy<T>
. La siguiente propiedad de importación declara una importación diferida.
Public Class MyClass1
<Import()>
Public Property MyAddin As Lazy(Of IMyAddin)
End Class
public class MyClass
{
[Import]
public Lazy<IMyAddin> MyAddin { get; set; }
}
Desde el punto de vista del motor de composición, un tipo de contrato de Lazy<T>
se considera idéntico al tipo de contrato de T
. Por lo tanto, la importación anterior coincidiría con la siguiente exportación.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
El nombre del contrato y el tipo de contrato se pueden especificar en el Import
atributo para una importación diferida, como se describió anteriormente en la sección "Importaciones y exportaciones básicas".
Importaciones necesarias
Normalmente, el motor de composición crea piezas MEF exportadas, en respuesta a una solicitud directa o a la necesidad de rellenar una importación coincidente. De forma predeterminada, al crear una parte, el motor de composición usa el constructor sin parámetros. Para que el motor use un constructor diferente, puede marcarlo con el ImportingConstructor
atributo .
Cada pieza puede tener solo un constructor para su uso por parte del motor de composición. Si no se proporciona ningún constructor sin parámetros ni ningún ImportingConstructor
atributo, o se proporciona más de un ImportingConstructor
atributo, se producirá un error.
Para rellenar los parámetros de un constructor marcado con el ImportingConstructor
atributo , todos esos parámetros se declaran automáticamente como importaciones. Esta es una manera cómoda de declarar importaciones que se usan durante la inicialización de partes. La siguiente clase usa ImportingConstructor
para declarar una importación.
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;
}
}
De forma predeterminada, el ImportingConstructor
atributo usa tipos de contrato inferidos y nombres de contrato para todas las importaciones de parámetros. Es posible invalidar esto mediante la decoración de los parámetros con Import
atributos, que luego pueden definir el tipo de contrato y el nombre del contrato explícitamente. En el código siguiente se muestra un constructor que usa esta sintaxis para importar una clase derivada en lugar de una clase primaria.
<ImportingConstructor()>
Public Sub New(<Import(GetType(IMySubAddin))> ByVal MyAddin As IMyAddin)
End Sub
[ImportingConstructor]
public MyClass([Import(typeof(IMySubAddin))]IMyAddin MyAddin)
{
_theAddin = MyAddin;
}
En concreto, debería tener precaución con los parámetros de colección. Por ejemplo, si especifica ImportingConstructor
en un constructor con un parámetro de tipo IEnumerable<int>
, la importación coincidirá con una única exportación de tipo IEnumerable<int>
, en lugar de un conjunto de exportaciones de tipo int
. Para hacer coincidir un conjunto de exportaciones de tipo int
, debe decorar el parámetro con el atributo ImportMany
.
Los parámetros declarados como importaciones por el ImportingConstructor
atributo también se marcan como importaciones de requisitos previos. MEF normalmente permite que las exportaciones e importaciones forme un ciclo. Por ejemplo, un ciclo es donde el objeto A importa el objeto B, que a su vez importa el objeto A. En circunstancias normales, un ciclo no es un problema y el contenedor de composición construye ambos objetos normalmente.
Cuando el constructor de un elemento requiere un valor importado, ese objeto no puede participar en un ciclo. Si el objeto A requiere que se construya el objeto B antes de que se pueda construir a sí mismo y el objeto B importa el objeto A, el ciclo no podrá resolverse y se producirá un error de composición. Las importaciones declaradas en parámetros de constructor son, por lo tanto, importaciones de requisitos previos, que deben rellenarse antes de que se pueda usar cualquiera de las exportaciones del objeto que las requiera.
Importaciones opcionales
El Import
atributo especifica un requisito para que la parte funcione. Si no se puede completar una importación, se producirá un error en la composición de esa parte y la parte no estará disponible.
Puede especificar que una importación es opcional mediante la AllowDefault
propiedad . En este caso, la composición se realizará correctamente incluso si la importación no coincide con ninguna exportación disponible y la propiedad de importación se establecerá en el valor predeterminado para su tipo de propiedad (null
para las propiedades de objeto, false
para booleanos o cero para las propiedades numéricas). La siguiente clase usa una importación 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.
}
Importación de varios objetos
El Import
atributo solo se compondrá correctamente cuando coincida con una y una sola exportación. Otros casos producirán un error de composición. Para importar más de una exportación que coincida con el mismo contrato, use el ImportMany
atributo . Las importaciones marcadas con este atributo siempre son opcionales. Por ejemplo, no se producirá un error en la composición si no hay ninguna exportación coincidente presente. La siguiente clase importa cualquier número de exportaciones de 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; }
}
Se puede acceder a la matriz importada mediante la sintaxis y los métodos normales IEnumerable<T>
. También es posible usar una matriz normal (IMyAddin[]
) en su lugar.
Este patrón puede ser muy importante cuando se usa en combinación con la Lazy<T>
sintaxis . Por ejemplo, utilizando ImportMany
, IEnumerable<T>
y Lazy<T>
, puede importar las referencias indirectas a cualquier número de objetos y crear instancias de los que resultan necesarios únicamente. La siguiente clase muestra este patrón.
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; }
}
Evitar la detección
En algunos casos, puede desear evitar que una parte sea detectada como parte de un catálogo. Por ejemplo, la parte puede ser una clase base de la que se va a heredar, pero no utilizada. Hay dos maneras de hacerlo. Primero, puede utilizar la palabra clave abstract
en la clase de la parte. Las clases abstractas nunca proporcionan exportaciones, aunque pueden proporcionar exportaciones heredadas a clases que derivan de ellas.
Si la clase no se puede hacer abstracta, puede decorarla con el PartNotDiscoverable
atributo . Una parte decorada con este atributo no se incluirá en ningún catálogo. En el ejemplo siguiente se muestran estos patrones. El catálogo detectará DataOne
. Como DataTwo
es abstracto, no se detectará. Dado que DataThree
utilizó el atributo PartNotDiscoverable
, no se descubrirá.
<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.
}
Metadatos y vistas de metadatos
Las exportaciones pueden proporcionar información adicional sobre ellos mismos conocidos como metadatos. Los metadatos se pueden usar para transmitir propiedades del objeto exportado al elemento de importación. La parte de importación puede usar estos datos para decidir qué exportaciones usar, o para recopilar información sobre una exportación sin tener que construirlos. Por esta razón, una importación debe ser diferida para utilizar los metadatos.
Para usar metadatos, normalmente se declara una interfaz conocida como vista de metadatos, que declara qué metadatos estarán disponibles. La interfaz de vista de metadatos solo debe tener propiedades y esas propiedades deben tener get
descriptores de acceso. La siguiente interfaz es una vista de metadatos de ejemplo.
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; }
}
También es posible usar una colección genérica, IDictionary<string, object>
, como vista de metadatos, pero esto pierde las ventajas de la comprobación de tipos y debe evitarse.
Normalmente, todas las propiedades denominadas en la vista de metadatos son necesarias y las exportaciones que no las proporcionan no se considerarán coincidencias. El DefaultValue
atributo especifica que una propiedad es opcional. Si la propiedad no está incluida, se le asignará el valor predeterminado especificado como parámetro de DefaultValue
. A continuación se muestran dos clases diferentes decoradas con metadatos. Ambas clases coincidirían con la vista de metadatos 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
{
}
Los metadatos se expresan después del Export
atributo mediante el ExportMetadata
atributo . Cada fragmento de metadatos se compone de un par nombre-valor. La parte de nombre de los metadatos debe coincidir con el nombre de la propiedad adecuada en la vista de metadatos y el valor se asignará a esa propiedad.
Es el importador que especifica qué vista de metadatos, si existe, estará en uso. Una importación con metadatos se declara como una importación perezosa, con la interfaz de metadatos como el segundo tipo de parámetro para Lazy<T,T>
. La siguiente clase importa la parte anterior con metadatos.
Public Class Addin
<Import()>
Public Property plugin As Lazy(Of IPlugin, IPluginMetadata)
End Class
public class Addin
{
[Import]
public Lazy<IPlugin, IPluginMetadata> plugin;
}
En muchos casos, querrá combinar metadatos con el atributo ImportMany
, con el fin de analizar las importaciones disponibles y elegir e instanciar una sola, o filtrar una colección para coincidir con una condición determinada. La siguiente clase instancia solo objetos IPlugin
que tienen el 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;
}
}
Importación y exportación de herencia
Si una clase hereda de una parte, esa clase también puede convertirse en parte. Las importaciones siempre se heredan por subclases. Por lo tanto, una subclase de un elemento siempre será una parte, con las mismas importaciones que su clase primaria.
Las exportaciones declaradas mediante el Export
atributo no se heredan por subclases. Sin embargo, una parte puede exportarse mediante el InheritedExport
atributo . Las subclases del elemento heredarán y proporcionarán la misma exportación, incluido el nombre del contrato y el tipo de contrato. A diferencia de un Export
atributo, InheritedExport
solo se puede aplicar en el nivel de clase y no en el nivel de miembro. Por lo tanto, las exportaciones de nivel de miembro nunca se pueden heredar.
Las cuatro clases siguientes muestran los principios de herencia de importación y exportación.
NumTwo
hereda de NumOne
, por lo que NumTwo
importará IMyData
. Las exportaciones ordinarias no se heredan, por lo que NumTwo
no exportará nada.
NumFour
hereda de NumThree
. Dado que NumThree
utilizó InheritedExport
, NumFour
tiene una exportación con tipo de contrato NumThree
. Las exportaciones de nivel de miembro nunca se heredan, por lo que IMyData
no se exporta.
<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.
}
Si hay metadatos asociados a un InheritedExport
atributo, también se heredarán los metadatos. (Para obtener más información, vea la sección anterior "Metadatos y vistas de metadatos". La subclase no puede modificar los metadatos heredados. Sin embargo, al volver a declarar el InheritedExport
atributo con el mismo nombre de contrato y tipo de contrato, pero con nuevos metadatos, la subclase puede reemplazar los metadatos heredados por nuevos metadatos. En la siguiente clase se muestra este principio. El MegaLogger
elemento hereda de Logger
e incluye el InheritedExport
atributo . Como MegaLogger
declara nuevos metadatos denominados Status, no hereda los metadatos Name y Version 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.
}
Al volver a declarar el InheritedExport
atributo para invalidar los metadatos, asegúrese de que los tipos de contrato son los mismos. (En el ejemplo anterior, IPlugin
es el tipo de contrato). Si difieren, en lugar de invalidar, el segundo atributo creará una segunda exportación independiente de la parte. Por lo general, esto significa que tendrá que especificar explícitamente el tipo de contrato al invalidar un InheritedExport
atributo, como se muestra en el ejemplo anterior.
Dado que las interfaces no se pueden instanciar directamente, por lo general no se pueden decorar con Export
o Import
atributos. Sin embargo, una interfaz se puede decorar con un InheritedExport
atributo en el nivel de interfaz y esa exportación junto con los metadatos asociados se heredará mediante cualquier clase de implementación. Sin embargo, la propia interfaz no estará disponible como parte.
Atributos de exportación personalizados
Los atributos Export
de exportación básicos y InheritedExport
, se pueden extender para incluir metadatos como propiedades de atributo. Esta técnica es útil para aplicar metadatos similares a muchas partes o crear un árbol de herencia de atributos de metadatos.
Un atributo personalizado puede especificar el tipo de contrato, el nombre del contrato o cualquier otro metadato. Para definir un atributo personalizado, se debe decorar una clase que herede de ExportAttribute
(o InheritedExportAttribute
) con el MetadataAttribute
atributo . La siguiente clase define un 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; }
}
Esta clase define un atributo personalizado denominado MyAttribute
con el tipo IMyAddin
de contrato y algunos metadatos denominados MyMetadata
. Todas las propiedades de una clase marcadas con el MetadataAttribute
atributo se consideran metadatos definidos en el atributo personalizado. Las dos declaraciones siguientes son 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; }
En la primera declaración, el tipo de contrato y los metadatos se definen explícitamente. En la segunda declaración, el tipo de contrato y los metadatos están implícitos en el atributo personalizado. Especialmente en los casos en los que se debe aplicar una gran cantidad de metadatos idénticos a muchas partes (por ejemplo, información de autor o copyright), el uso de un atributo personalizado puede ahorrar mucho tiempo y duplicación. Además, se pueden crear árboles de herencia de atributos personalizados para permitir variaciones.
Para crear metadatos opcionales en un atributo personalizado, puede usar el DefaultValue
atributo . Cuando este atributo se aplica a una propiedad de una clase de atributo personalizado, especifica que la propiedad decorada es opcional y no tiene que ser suministrada por un exportador. Si no se proporciona un valor para la propiedad, se le asignará el valor predeterminado para su tipo de propiedad (normalmente null
, false
o 0).
Directivas de creación
Cuando una parte especifica una importación y se lleva a cabo la composición, el contenedor de composición intenta encontrar una exportación coincidente. Si la importación coincide correctamente con una exportación, el miembro de importación se convierte en una instancia del objeto exportado. La directiva de creación del elemento de exportación controla el origen de esta instancia.
Las dos posibles directivas de creación son compartidas y no compartidas. Una parte con una directiva de creación compartida se compartirá entre todas las importaciones del contenedor de una parte con ese contrato. Cuando el motor de composición encuentra una coincidencia y tiene que establecer una propiedad de importación, solo creará una instancia de una nueva copia de la parte si aún no existe; si no, proporcionará la copia existente. Esto significa que muchos objetos pueden tener referencias a la misma parte. Estas partes no deberían confiar en un estado interno que se puede cambiar desde muchos lugares. Esta directiva es adecuada para elementos estáticos, partes que proporcionan servicios y elementos que consumen mucha memoria u otros recursos.
Se creará una parte con una directiva de creación de parte no compartida cada vez que se encuentre una importación correspondiente para una de sus exportaciones. Se crearán instancias de una nueva copia para cada importación del contenedor que coincida con uno de los contratos exportados de la parte. El estado interno de estas copias no se compartirá. Esta directiva es adecuada para las partes en las que cada importación requiere su propio estado interno.
Tanto la importación como la exportación pueden especificar la directiva de creación de un elemento, de entre los valores Shared
, NonShared
o Any
. El valor predeterminado es Any
para las importaciones y las exportaciones. Exportación que especifica Shared
o NonShared
solo coincidirá con una importación que especifique lo mismo o que especifique Any
. De forma similar, una importación que especifica Shared
o NonShared
solo coincidirá con una exportación que especifique lo mismo o que especifique Any
. Las importaciones y exportaciones con directivas de creación incompatibles no se consideran un emparejamiento, al igual que las importaciones y exportaciones cuyo nombre de contrato o tipo de contrato no coinciden. Si la importación y la exportación especifican Any
o no especifican una directiva de creación y tienen como valor predeterminado Any
, la directiva de creación se establecerá de manera predeterminado en compartida.
En el siguiente ejemplo se muestran tanto las importaciones como las exportaciones especificando políticas de creación.
PartOne
no especifica una directiva de creación, por lo que el valor predeterminado es Any
.
PartTwo
no especifica una directiva de creación, por lo que el valor predeterminado es Any
. Dado que tanto la importación como la exportación se establecen por defecto en Any
, PartOne
se compartirá.
PartThree
especifica una Shared
directiva de creación, por lo que PartTwo
y PartThree
compartirán la misma copia de PartOne
.
PartFour
especifica una NonShared
directiva de creación, por lo que PartFour
no se compartirá en PartFive
.
PartSix
especifica una directiva de creación NonShared
.
PartFive
y PartSix
cada uno recibirá copias independientes de PartFour
.
PartSeven
especifica una directiva de creación Shared
. Dado que no hay ningún PartFour
exportado con una directiva de creación de Shared
, la importación PartSeven
no coincide con nada y no se completará.
<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 y eliminación
Dado que las partes se hospedan en el contenedor de composición, su ciclo de vida puede ser más complejo que los objetos normales. Las partes pueden implementar dos interfaces importantes relacionadas con el ciclo de vida: IDisposable
y IPartImportsSatisfiedNotification
.
Los elementos que requieren que se realicen trabajos al apagarse o que necesiten liberar recursos deben implementar IDisposable
, como de costumbre para los objetos de .NET Framework. Sin embargo, dado que el contenedor crea y mantiene referencias a elementos, solo el contenedor que posee una parte debe llamar al Dispose
método en él. El contenedor implementa IDisposable
, y como parte de la limpieza en Dispose
llamará a Dispose
en todas las partes que posee. Por esta razón, debería eliminar, cuando ya no se necesite, el contenedor de composición y sus partes.
Para los contenedores de composición duraderos, el consumo de memoria de las partes con una directiva de creación de parte no compartida puede convertirse en un problema. Estos elementos no compartidos se pueden crear varias veces y no se eliminarán hasta que se elimine el propio contenedor. Para tratar con esto, el contenedor proporciona el ReleaseExport
método . Llamando a este método en una exportación no compartida, se quita esa exportación del contenedor de composición y se elimina. Las partes que solo utilizaba la exportación eliminada y demás hacia abajo en el árbol, también se quitan y se eliminan. De este modo, los recursos se pueden recuperar sin eliminar el propio contenedor de composición.
IPartImportsSatisfiedNotification
contiene un método denominado OnImportsSatisfied
. El contenedor de composición llama a este método en cualquier parte que implemente la interfaz cuando se ha completado la composición y las importaciones de la parte están listas para el uso. El motor de composición crea componentes para cubrir las necesidades de importación de otros componentes. Antes de establecer las importaciones de un elemento, no puede realizar ninguna inicialización que se base o manipule valores importados en el constructor de elementos a menos que esos valores se hayan especificado como requisitos previos mediante el ImportingConstructor
atributo . Éste es normalmente el método preferido, pero en algunos casos, la inyección de constructor tal vez no esté disponible. En esos casos, la inicialización se puede realizar en OnImportsSatisfied
y el elemento debe implementar IPartImportsSatisfiedNotification
.