Condividi tramite


Panoramica del modello di programmazione con attributi (MEF)

In Managed Extensibility Framework (MEF), un modello di programmazione è un metodo specifico per definire il set di oggetti concettuali su cui opera MEF. Questi oggetti concettuali includono parti, importazioni ed esportazioni. MEF usa questi oggetti, ma non specifica come devono essere rappresentati. Di conseguenza, sono possibili un'ampia gamma di modelli di programmazione, inclusi i modelli di programmazione personalizzati.

Il modello di programmazione predefinito usato in MEF è il modello di programmazione con attributi. Nelle parti del modello di programmazione attribuito, importazioni, esportazioni e altri oggetti vengono definiti mediante attributi che decorano le normali classi del .NET Framework. In questo argomento viene illustrato come usare gli attributi forniti dal modello di programmazione con attributi per creare un'applicazione MEF.

Nozioni di base su importazione ed esportazione

Un'esportazione è un valore fornito da una parte ad altre parti del contenitore e un'importazione è un requisito che una parte esprime al contenitore per essere compilata dalle esportazioni disponibili. Nel modello di programmazione con attributi le importazioni e le esportazioni vengono dichiarate decoratando classi o membri con gli Import attributi e Export . L'attributo Export può decorare una classe, un campo, una proprietà o un metodo, mentre l'attributo Import può decorare un campo, una proprietà o un parametro del costruttore.

Affinché un'importazione sia associata a un'esportazione, l'importazione e l'esportazione devono avere lo stesso contratto. Il contratto è costituito da una stringa, denominata nome del contratto e il tipo dell'oggetto esportato o importato, denominato tipo di contratto. Solo se il nome del contratto e il tipo di contratto corrispondono è un'esportazione considerata per soddisfare una particolare importazione.

Uno o entrambi i parametri del contratto possono essere impliciti o espliciti. Il codice seguente illustra una classe che dichiara un'importazione di base.

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

In questa importazione, l'attributo Import non ha né un tipo di contratto né un parametro del nome del contratto associato. Pertanto, entrambi verranno dedotti dalla proprietà decorata. In questo caso, il tipo di contratto sarà IMyAddine il nome del contratto sarà una stringa univoca creata dal tipo di contratto. In altre parole, il nome del contratto corrisponderà solo alle esportazioni i cui nomi vengono dedotti anche dal tipo IMyAddin.

Di seguito è illustrata un'esportazione corrispondente all'importazione precedente.

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

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

In questa esportazione, il tipo di contratto è IMyAddin perché viene specificato come parametro dell'attributo Export . Il tipo esportato deve essere uguale al tipo di contratto, derivare dal tipo di contratto o implementare il tipo di contratto se si tratta di un'interfaccia. In questa esportazione il tipo MyLogger effettivo implementa l'interfaccia IMyAddin. Il nome del contratto viene dedotto dal tipo di contratto, il che significa che l'esportazione corrisponderà all'importazione precedente.

Annotazioni

Le esportazioni e le importazioni devono in genere essere dichiarate in classi o membri pubblici. Sono supportate altre dichiarazioni, ma l'esportazione o l'importazione di un membro privato, protetto o riservato interrompe il modello di isolamento per la componente e pertanto non è consigliabile.

Il tipo di contratto deve corrispondere esattamente affinché l'esportazione e l'importazione possano essere considerate corrispondenti. Si consideri l'esportazione seguente.

<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 { }

In questa esportazione il tipo di contratto è MyLogger invece di IMyAddin. Anche se MyLogger implementa IMyAddine pertanto può essere eseguito il cast a un IMyAddin oggetto , questa esportazione non corrisponde all'importazione precedente perché i tipi di contratto non sono uguali.

In generale, non è necessario specificare il nome del contratto e la maggior parte dei contratti deve essere definita in termini di tipo di contratto e metadati. Tuttavia, in determinate circostanze, è importante specificare direttamente il nome del contratto. Il caso più comune è quando una classe esporta diversi valori che condividono un tipo comune, ad esempio primitive. Il nome del contratto può essere specificato come primo parametro dell'attributo Import o Export . Il codice seguente mostra un'importazione e un'esportazione con un nome di contratto specificato di 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 il tipo di contratto non viene specificato, verrà comunque dedotto dal tipo di importazione o esportazione. Tuttavia, anche se il nome del contratto viene specificato in modo esplicito, anche il tipo di contratto deve corrispondere esattamente affinché l'importazione e l'esportazione siano considerate una corrispondenza. Ad esempio, se il MajorRevision campo è una stringa, i tipi di contratto dedotti non corrispondono e l'esportazione non corrisponde all'importazione, nonostante abbia lo stesso nome di contratto.

Importazione ed esportazione di un metodo

L'attributo Export può anche decorare un metodo, allo stesso modo di una classe, di una proprietà o di una funzione. Le esportazioni di metodi devono specificare un tipo di contratto o un nome di contratto, perché il tipo non può essere dedotto. Il tipo specificato può essere un delegato personalizzato o un tipo generico, ad esempio Func. La classe seguente esporta un metodo denominato 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);
}

In questa classe il DoSomething metodo accetta un singolo int parametro e restituisce un oggetto string. Per corrispondere a questa esportazione, la parte di importazione deve dichiarare un membro appropriato. La classe seguente importa il DoSomething metodo .

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; }
}

Per altre informazioni su come usare l'oggetto Func<T, T> , vedere Func<T,TResult>.

Tipi di importazioni

MEF supporta diversi tipi di importazione, tra cui dinamici, differiti, prerequisiti e facoltativi.

Importazioni dinamiche

In alcuni casi, la classe di importazione potrebbe voler corrispondere alle esportazioni di qualsiasi tipo con un nome di contratto specifico. In questo scenario, la classe può dichiarare un'importazione dinamica. L'importazione seguente corrisponde a qualsiasi esportazione con il nome del contratto "TheString".

Public Class MyClass1

    <Import("TheString")>
    Public Property MyAddin

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

Quando il tipo di contratto viene dedotto dalla dynamic parola chiave , corrisponderà a qualsiasi tipo di contratto. In questo caso, un'importazione deve sempre specificare un nome di contratto da associare. Se non viene specificato alcun nome di contratto, l'importazione verrà considerata senza esportazioni. Entrambe le esportazioni seguenti corrispondono all'importazione precedente.

<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 { }

Ovviamente, la classe di importazione deve essere preparata per gestire un oggetto di tipo arbitrario.

Importazioni differita

In alcuni casi, la classe di importazione può richiedere un riferimento indiretto all'oggetto importato, in modo che l'oggetto non venga istanziato immediatamente. In questo scenario, la classe può dichiarare un'importazione differita utilizzando un tipo di contratto Lazy<T>. La proprietà di importazione seguente dichiara un'importazione differita.

Public Class MyClass1

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

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

Dal punto di vista del motore di composizione, un tipo di contratto di Lazy<T> è considerato identico al tipo di contratto di T. Pertanto, l'importazione precedente corrisponde all'esportazione seguente.

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

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

Il nome del contratto e il tipo di contratto possono essere specificati nell'attributo Import per un'importazione differita, come descritto in precedenza nella sezione "Importazioni ed esportazioni di base".

Importazioni prerequisiti

Le parti MEF esportate vengono in genere create dal motore di composizione, in risposta a una richiesta diretta o alla necessità di riempire un'importazione corrispondente. Per impostazione predefinita, quando si crea una parte, il motore di composizione usa il costruttore senza parametri. Per fare in modo che il motore usi un costruttore diverso, è possibile contrassegnarlo con l'attributo ImportingConstructor .

Ogni parte può avere un solo costruttore da utilizzare dal motore di composizione. Se non si specifica alcun costruttore senza parametri e nessun ImportingConstructor attributo o si specifica più di un ImportingConstructor attributo, verrà generato un errore.

Per riempire i parametri di un costruttore contrassegnato con l'attributo ImportingConstructor , tutti questi parametri vengono dichiarati automaticamente come importazioni. Si tratta di un modo pratico per dichiarare le importazioni utilizzate durante l'inizializzazione della parte. La classe seguente usa ImportingConstructor per dichiarare un'importazione.

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;
    }
}

Per impostazione predefinita, l'attributo ImportingConstructor usa i tipi di contratto dedotti e i nomi dei contratti per tutte le importazioni di parametri. È possibile eseguire l'override di questa operazione decorando i parametri con Import attributi, che possono quindi definire il tipo di contratto e il nome del contratto in modo esplicito. Il codice seguente illustra un costruttore che usa questa sintassi per importare una classe derivata anziché una classe padre.

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

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

In particolare, è consigliabile prestare attenzione ai parametri di raccolta. Ad esempio, se si specifica ImportingConstructor in un costruttore con un parametro di tipo IEnumerable<int>, l'importazione corrisponderà a una singola esportazione di tipo IEnumerable<int>, anziché a un set di esportazioni di tipo int. Per trovare una corrispondenza con un set di esportazioni di tipo int, è necessario decorare il parametro con l'attributo ImportMany .

Anche i parametri dichiarati come importazioni dall'attributo ImportingConstructor vengono contrassegnati come importazioni prerequisiti. MEF consente in genere che esportazioni e importazioni formino un ciclo. Ad esempio, un ciclo è dove l'oggetto A importa l'oggetto B, che a sua volta importa l'oggetto A. In circostanze normali, un ciclo non è un problema e il contenitore di composizione costruisce normalmente entrambi gli oggetti.

Quando un valore importato è richiesto dal costruttore di una parte, tale oggetto non può partecipare a un ciclo. Se l'oggetto A richiede che l'oggetto B venga costruito prima di poterlo costruire e l'oggetto B importa l'oggetto A, il ciclo non sarà in grado di risolvere e si verificherà un errore di composizione. Le importazioni dichiarate nei parametri del costruttore sono pertanto importazioni prerequisiti, che devono essere compilate tutte prima che sia possibile usare una delle esportazioni dall'oggetto che le richiede.

Importazioni facoltative

L'attributo Import specifica un requisito per la funzione della parte. Se non è possibile soddisfare un'importazione, la composizione di tale parte avrà esito negativo e la parte non sarà disponibile.

È possibile specificare che un'importazione è facoltativa tramite la AllowDefault proprietà . In questo caso, la composizione avrà esito positivo anche se l'importazione non corrisponde ad alcuna esportazione disponibile e la proprietà di importazione verrà impostata sul valore predefinito per il tipo di proprietà (null per le proprietà dell'oggetto, false per i valori booleani o zero per le proprietà numeriche). La classe seguente usa un'importazione facoltativa.

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.
}

Importazione di più oggetti

L'attributo Import verrà composto correttamente solo quando corrisponde a uno e solo un'esportazione. Altri casi genereranno un errore di composizione. Per importare più di un'esportazione corrispondente allo stesso contratto, usare l'attributo ImportMany . Le importazioni contrassegnate con questo attributo sono sempre facoltative. Ad esempio, la composizione non avrà esito negativo se non sono presenti esportazioni corrispondenti. La classe seguente importa un numero qualsiasi di esportazioni di 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; }
}

È possibile accedere alla matrice importata usando la sintassi e i metodi ordinari IEnumerable<T> . È anche possibile usare una matrice normale (IMyAddin[]).

Questo modello può essere molto importante quando lo si usa in combinazione con la Lazy<T> sintassi. Ad esempio, usando ImportMany, IEnumerable<T>e Lazy<T>, è possibile importare riferimenti indiretti a un numero qualsiasi di oggetti e creare solo un'istanza di quelle necessarie. La classe seguente illustra questo modello.

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; }
}

Evitare l'individuazione

In alcuni casi, è possibile impedire che una parte venga individuata come parte di un catalogo. Ad esempio, la parte può essere una classe di base che deve essere ereditata, ma non usata. Esistono due modi per eseguire questa operazione. In primo luogo, è possibile usare la abstract parola chiave nella classe part. Le classi astratte non forniscono mai esportazioni, anche se possono fornire esportazioni ereditate alle classi che ne derivano.

Se la classe non può essere resa astratta, è possibile decorarla con l'attributo PartNotDiscoverable . Una parte decorata con questo attributo non verrà inclusa in alcun catalogo. Nell'esempio seguente vengono illustrati questi modelli. DataOne verrà individuato dal catalogo. Poiché DataTwo è astratta, non verrà individuata. Poiché DataThree è stato usato l'attributo PartNotDiscoverable , non verrà individuato.

<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.
}

Metadati e visualizzazioni dei metadati

Le esportazioni possono fornire informazioni aggiuntive su se stesse note come metadati. I metadati possono essere usati per trasmettere le proprietà dell'oggetto esportato alla parte di importazione. La parte di importazione può usare questi dati per decidere quali esportazioni usare o per raccogliere informazioni su un'esportazione senza doverla costruire. Per questo motivo, un'importazione deve essere pigra per usare i metadati.

Per usare i metadati, in genere si dichiara un'interfaccia nota come visualizzazione metadati, che dichiara quali metadati saranno disponibili. L'interfaccia della visualizzazione metadati deve avere solo proprietà e tali proprietà devono avere get funzioni di accesso. L'interfaccia seguente è una visualizzazione di metadati di esempio.

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; }
}

È anche possibile usare una raccolta generica, IDictionary<string, object>, come visualizzazione dei metadati, ma questo impedisce i vantaggi del controllo dei tipi e deve essere evitato.

In genere, tutte le proprietà denominate nella visualizzazione metadati sono necessarie e tutte le esportazioni che non le forniscono non verranno considerate una corrispondenza. L'attributo DefaultValue specifica che una proprietà è facoltativa. Se la proprietà non è inclusa, verrà assegnato il valore predefinito specificato come parametro di DefaultValue. Di seguito sono riportate due classi diverse decorate con metadati. Entrambe queste classi corrispondono alla visualizzazione dei metadati precedente.

<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
{
}

I metadati vengono espressi dopo l'attributo Export usando l'attributo ExportMetadata . Ogni parte di metadati è costituita da una coppia nome/valore. La parte del nome dei metadati deve corrispondere al nome della proprietà appropriata nella visualizzazione metadati e il valore verrà assegnato a tale proprietà.

È l'importatore che specifica quale visualizzazione dei metadati, se presente, sarà utilizzata. Un'importazione con metadati viene dichiarata come importazione pigra, con l'interfaccia dei metadati come secondo parametro di tipo in Lazy<T,T>. La classe seguente importa la parte precedente con i metadati.

Public Class Addin

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

In molti casi, è necessario combinare i metadati con l'attributo ImportMany , per analizzare le importazioni disponibili e scegliere e creare un'istanza di una sola raccolta o filtrare una raccolta in modo che corrisponda a una determinata condizione. La classe seguente istanzia solo IPlugin oggetti che hanno il valore 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;
    }
}

Importare ed esportare l'ereditarietà

Se una classe eredita da una parte, tale classe può anche diventare una parte. Le importazioni vengono sempre ereditate dalle sottoclassi. Pertanto, una sottoclasse di una parte sarà sempre una parte, con gli stessi import della relativa classe padre.

Le esportazioni dichiarate tramite l'attributo Export non vengono ereditate dalle sottoclassi. Tuttavia, una parte può esportare se stessa usando l'attributo InheritedExport . Le sottoclassi della parte erediteranno e forniranno la stessa esportazione, inclusi il nome del contratto e il tipo di contratto. A differenza di un Export attributo, InheritedExport può essere applicato solo a livello di classe e non a livello di membro. Pertanto, le esportazioni a livello di membro non possono mai essere ereditate.

Le quattro classi seguenti illustrano i principi dell'ereditarietà di importazione ed esportazione. NumTwo eredita da NumOne, quindi NumTwo importerà IMyData. Le esportazioni ordinarie non vengono ereditate, quindi NumTwo non esporterà nulla. NumFour eredita da NumThree. Poiché NumThree ha usato InheritedExport, NumFour ha un'esportazione con tipo di contratto NumThree. Le esportazioni a livello di membro non vengono mai ereditate, quindi IMyData non vengono esportate.

<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 sono presenti metadati associati a un InheritedExport attributo, anche i metadati verranno ereditati. Per altre informazioni, vedere la sezione precedente "Metadati e visualizzazioni metadati". I metadati ereditati non possono essere modificati dalla sottoclasse. Tuttavia, dichiarando nuovamente l'attributo InheritedExport con lo stesso nome di contratto e tipo di contratto, ma con nuovi metadati, la sottoclasse può sostituire i metadati ereditati con nuovi metadati. La classe seguente illustra questo principio. La MegaLogger parte eredita da Logger e include l'attributo InheritedExport . Poiché MegaLogger dichiara nuovamente i nuovi metadati denominati Status, non eredita i metadati Name e Version da 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.
}

Quando si dichiara nuovamente l'attributo per eseguire l'override InheritedExport dei metadati, assicurarsi che i tipi di contratto siano gli stessi. Nell'esempio precedente, il tipo di contratto è IPlugin. Se sono diversi, invece di sovrascrivere, il secondo attributo creerà una seconda esportazione indipendente dal componente. In genere, ciò significa che è necessario specificare in modo esplicito il tipo di contratto quando si esegue l'override di un InheritedExport attributo, come illustrato nell'esempio precedente.

Poiché le interfacce non possono essere create direttamente, in genere non possono essere decorate con attributi Export o Import. Tuttavia, un'interfaccia può essere decorata con un InheritedExport attributo a livello di interfaccia e tale esportazione insieme a tutti i metadati associati verrà ereditata da qualsiasi classe di implementazione. L'interfaccia stessa non sarà tuttavia disponibile come parte.

Attributi di esportazione personalizzati

Gli attributi Export di esportazione di base e InheritedExport, possono essere estesi per includere i metadati come proprietà degli attributi. Questa tecnica è utile per applicare metadati simili a molte parti o creare un albero di ereditarietà degli attributi dei metadati.

Un attributo personalizzato può specificare il tipo di contratto, il nome del contratto o qualsiasi altro metadati. Per definire un attributo personalizzato, una classe che eredita da ExportAttribute (o InheritedExportAttribute) deve essere decorata con l'attributo MetadataAttribute . La classe seguente definisce un attributo personalizzato.

<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; }
}

Questa classe definisce un attributo personalizzato denominato MyAttribute con tipo di IMyAddin contratto e alcuni metadati denominati MyMetadata. Tutte le proprietà in una classe contrassegnata con l'attributo MetadataAttribute vengono considerate metadati definiti nell'attributo personalizzato. Le due dichiarazioni seguenti sono equivalenti.

<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; }

Nella prima dichiarazione il tipo di contratto e i metadati vengono definiti in modo esplicito. Nella seconda dichiarazione il tipo di contratto e i metadati sono impliciti nell'attributo personalizzato. In particolare nei casi in cui una grande quantità di metadati identici deve essere applicata a molte parti (ad esempio, informazioni sull'autore o sul copyright), l'uso di un attributo personalizzato può risparmiare molto tempo e duplicazione. È inoltre possibile creare alberi di ereditarietà degli attributi personalizzati per consentire variazioni.

Per creare metadati facoltativi in un attributo personalizzato, è possibile usare l'attributo DefaultValue . Quando questo attributo viene applicato a una proprietà in una classe di attributi personalizzata, specifica che la proprietà decorata è facoltativa e non deve essere fornita da un'esportatore. Se non viene specificato un valore per la proprietà, verrà assegnato il valore predefinito per il tipo di proprietà ,in genere null, falseo 0.

Politiche di creazione

Quando una parte specifica un'importazione e una composizione viene eseguita, il contenitore di composizione tenta di trovare un'esportazione corrispondente. Se l'importazione corrisponde correttamente a un'esportazione, il membro importatore viene impostato su un'istanza dell'oggetto esportato. La posizione da cui proviene questa istanza è controllata dai criteri di creazione della parte di esportazione.

I due possibili criteri di creazione sono condivisi e non condivisi. Una parte con un criterio di creazione condivisa verrà condivisa tra ogni importazione all'interno del contenitore per parti con quel contratto. Quando il motore di composizione trova una corrispondenza e deve impostare una proprietà di importazione, crea un'istanza di una nuova copia della parte solo se non ne esiste già una; in caso contrario, fornirà la copia esistente. Ciò significa che molti oggetti possono avere riferimenti alla stessa parte. Tali parti non devono basarsi sullo stato interno che potrebbe essere modificato da molti punti. Questo criterio è appropriato per parti statiche, parti che forniscono servizi e parti che utilizzano molta memoria o altre risorse.

Verrà creata una parte con criteri di creazione non condivisi ogni volta che viene trovata un'importazione corrispondente per una delle sue esportazioni. Verrà quindi istanziata una nuova copia per ogni importazione nel contenitore che corrisponde a uno dei contratti esportati del componente. Lo stato interno di queste copie non verrà condiviso. Questo criterio è appropriato per le parti in cui ogni importazione richiede il proprio stato interno.

Sia l'importazione che l'esportazione possono specificare i criteri di creazione di una parte, tra i valori Shared, NonSharedo Any. Il valore predefinito è Any sia per le importazioni che per le esportazioni. Esportazione che specifica Shared o NonShared corrisponderà solo a un'importazione che specifica lo stesso oggetto o che specifica Any. Analogamente, un'importazione che specifica Shared o NonShared corrisponderà solo a un'esportazione che specifica lo stesso oggetto o che specifica Any. Le importazioni e le esportazioni con criteri di creazione incompatibili non sono considerate una corrispondenza, allo stesso modo di un'importazione ed esportazione il cui nome del contratto o il tipo di contratto non corrispondono. Se sia l'importazione che l'esportazione specificano Any o non specificano un criterio di creazione, e il criterio predefinito è Any, il criterio di creazione predefinito sarà condiviso.

Nell'esempio seguente vengono illustrate sia le importazioni che le esportazioni, specificando i criteri di creazione. PartOne non specifica un criterio di creazione, quindi il valore predefinito è Any. PartTwo non specifica un criterio di creazione, quindi il valore predefinito è Any. Poiché sia l'importazione che l'esportazione sono predefinite in Any, PartOne verranno condivise. PartThree specifica un Shared criterio di creazione, quindi PartTwo e PartThree condividerà la stessa copia di PartOne. PartFour specifica un NonShared criterio di creazione, quindi PartFour non verrà condiviso in PartFive. PartSix specifica un NonShared criterio di creazione. PartFive e PartSix riceveranno copie separate di PartFour. PartSeven specifica un Shared criterio di creazione. Poiché non esiste alcuna esportazione PartFour con un criterio di creazione di Shared, l'importazione PartSeven non corrisponde a nulla e non verrà riempita.

<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 di vita e eliminazione

Poiché le parti sono ospitate nel contenitore di composizione, il ciclo di vita può essere più complesso rispetto agli oggetti ordinari. Le parti possono implementare due interfacce importanti correlate al ciclo di vita: IDisposable e IPartImportsSatisfiedNotification.

Le parti che richiedono operazioni da eseguire all'arresto o che devono rilasciare le risorse devono implementare IDisposable, come di consueto per gli oggetti .NET Framework. Tuttavia, poiché il contenitore crea e gestisce riferimenti a parti, solo il contenitore proprietario di una parte deve chiamare il Dispose metodo su di esso. Il contenitore stesso implementa IDisposable, e come parte della sua pulizia in Dispose, chiamerà Dispose su tutte le parti di cui è proprietario. Per questo motivo, è consigliabile eliminare sempre il contenitore di composizione quando e le parti di cui è proprietario non sono più necessarie.

Per i contenitori di composizione di lunga durata, il consumo di memoria per parti con criteri di creazione non condivisi può diventare un problema. Queste parti non condivise possono essere create più volte e non verranno eliminate finché il contenitore stesso non viene eliminato. Per risolvere questo problema, il contenitore fornisce il ReleaseExport metodo . La chiamata a questo metodo in un'esportazione non condivisa rimuove l'esportazione dal contenitore di composizione e la elimina. Le parti utilizzate esclusivamente dall'esportazione rimossa, e quindi nel resto dell'albero, vengono rimosse ed eliminate. In questo modo, le risorse possono essere recuperate senza eliminare il contenitore di composizione stesso.

IPartImportsSatisfiedNotification contiene un metodo denominato OnImportsSatisfied. Questo metodo viene chiamato dal contenitore di composizione su tutte le parti che implementano l'interfaccia quando la composizione è stata completata e le importazioni della parte sono pronte per l'uso. I componenti vengono creati dal motore di composizione per completare le importazioni di altri componenti. Prima che le importazioni di una parte siano state impostate, non è possibile eseguire alcuna inizializzazione basata su o modificare i valori importati nel costruttore della parte, a meno che tali valori non siano stati specificati come prerequisiti utilizzando l'attributo ImportingConstructor . Questo è in genere il metodo preferito, ma in alcuni casi l'iniezione tramite costruttore potrebbe non essere disponibile. In questi casi, l'inizializzazione può essere eseguita in OnImportsSatisfiede la parte deve implementare IPartImportsSatisfiedNotification.