Compartir a través de


Introducción al modelo de programación con atributos (MEF)

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á IMyAddiny 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 IMyAddiny, 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, falseo 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, NonSharedo 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 Anyo 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 OnImportsSatisfiedy el elemento debe implementar IPartImportsSatisfiedNotification.