Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I Managed Extensibility Framework (MEF) är en programmeringsmodell en viss metod för att definiera den uppsättning konceptuella objekt som MEF fungerar på. Dessa konceptuella objekt omfattar delar, importer och exporter. MEF använder dessa objekt, men anger inte hur de ska representeras. Därför är en mängd olika programmeringsmodeller möjliga, inklusive anpassade programmeringsmodeller.
Standardprogrammeringsmodellen som används i MEF är den tillskrivna programmeringsmodellen. I de tilldelade programmeringsmodelldelarna definieras importer, exporter och andra objekt med attribut som dekorerar vanliga .NET klasser. Den här artikeln beskriver hur du använder attributen som tillhandahålls av den tilldelade programmeringsmodellen för att skapa ett MEF-program.
Grunderna för import och export
En export är ett värde som en del ger till andra delar i containern, och en import är ett krav som en del uttrycker till containern, som ska fyllas från de tillgängliga exporterna. I den tilldelade programmeringsmodellen deklareras importer och exporter genom att klasserna eller medlemmarna dekoreras med attributen Import och Export . Attributet Export kan dekorera en klass, ett fält, en egenskap eller en metod, medan Import attributet kan dekorera ett fält, en egenskap eller en konstruktorparameter.
För att en import ska matchas med en export måste importen och exporten ha samma kontrakt. Kontraktet består av en sträng som kallas kontraktnamn och typen av det exporterade eller importerade objektet, som kallas kontraktstyp. Endast om både kontraktsnamnet och kontraktstypen matchar anses en export uppfylla en viss import.
Antingen eller båda kontraktsparametrarna kan vara implicita eller explicita. Följande kod visar en klass som deklarerar en grundläggande import.
Public Class MyClass1
<Import()>
Public Property MyAddin As IMyAddin
End Class
public class MyClass
{
[Import]
public IMyAddin MyAddin { get; set; }
}
I den Import här importen har attributet varken en kontraktstyp eller en kontraktnamnsparameter kopplad. Därför kommer båda att härledas från den dekorerade egenskapen. I det här fallet blir IMyAddinkontraktstypen , och kontraktsnamnet är en unik sträng som skapas från kontraktstypen. (Med andra ord matchar kontraktsnamnet endast exporter vars namn också härleds från typen IMyAddin.)
Följande visar en export som matchar den tidigare importen.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
I den här exporten är kontrakttypen IMyAddin eftersom den anges som en parameter för attributet Export. Den exporterade typen måste antingen vara samma som kontraktstypen, härleda från kontraktstypen eller implementera kontraktstypen om det är ett gränssnitt. I den här exporten implementerar den faktiska typen MyLogger gränssnittet IMyAddin. Kontraktsnamnet härleds från kontraktstypen, vilket innebär att den här exporten matchar den tidigare importen.
Anmärkning
Exporter och importer bör vanligtvis deklareras på offentliga klasser eller medlemmar. Andra deklarationer stöds, men export eller import av en privat, skyddad eller intern medlem bryter isoleringsmodellen för delen och rekommenderas därför inte.
Kontraktstypen måste matcha exakt för att exporten och importen ska betraktas som en matchning. Överväg följande export.
<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 { }
I den här exporten är MyLogger kontraktstypen i stället för IMyAddin. Även om MyLogger implementerar IMyAddin, och därför kan omvandlas till ett IMyAddin objekt, matchar den här exporten inte den tidigare importen eftersom kontraktstyperna inte är desamma.
I allmänhet är det inte nödvändigt att ange kontraktsnamnet, och de flesta kontrakt bör definieras i termer av kontraktstyp och metadata. Under vissa omständigheter är det dock viktigt att ange kontraktnamnet direkt. Det vanligaste fallet är när en klass exporterar flera värden som delar en gemensam typ, till exempel primitiver. Kontraktsnamnet kan anges som den första parametern för Import attributet eller Export . Följande kod visar en import och en export med ett angivet kontraktnamn på 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;
}
Om kontraktstypen inte har angetts kommer den fortfarande att härledas från typen av import eller export. Men även om kontraktsnamnet uttryckligen anges måste kontrakttypen också matcha exakt för att importen och exporten ska betraktas som en matchning. Om fältet MajorRevision till exempel var en sträng matchar inte de härledda kontraktstyperna och exporten matchar inte importen, trots att den har samma kontraktnamn.
Importera och exportera en metod
Attributet Export kan också dekorera en metod på samma sätt som en klass, egenskap eller funktion. Metodexporter måste ange en kontraktstyp eller ett kontraktnamn, eftersom typen inte kan härledas. Den angivna typen kan vara antingen en anpassad delegering eller en generisk typ, till exempel Func. Följande klass exporterar en metod med namnet 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);
}
I den här klassen DoSomething tar metoden en enskild int parameter och returnerar en string. För att matcha den här exporten måste importdelen deklarera en lämplig medlem. Följande klass importerar DoSomething metoden.
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; }
}
Mer information om hur du använder objektet Func<T, T>finns i Func<T,TResult> .
Typer av importer
MEF stöder flera importtyper, inklusive dynamisk, lat, förutsättning och valfritt.
Dynamisk import
I vissa fall kanske importklassen vill matcha exporter av alla typer som har ett visst kontraktnamn. I det här scenariot kan klassen deklarera en dynamisk import. Följande import matchar alla exporter med kontraktsnamnet "TheString".
Public Class MyClass1
<Import("TheString")>
Public Property MyAddin
End Class
public class MyClass
{
[Import("TheString")]
public dynamic MyAddin { get; set; }
}
När kontraktstypen härleds från nyckelordet dynamic matchar den alla kontraktstyper. I det här fallet bör en import alltid ange ett kontraktnamn som ska matchas. (Om inget kontraktnamn har angetts anses importen inte matcha några exporter.) Båda följande exporter skulle matcha den tidigare importen.
<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 { }
Det är uppenbart att importklassen måste vara beredd att hantera ett objekt av godtycklig typ.
Lazy Importer
I vissa fall kan importklassen kräva en indirekt referens till det importerade objektet, så att objektet inte instansieras omedelbart. I det här scenariot kan klassen deklarera en fördröjd import med hjälp av en kontraktstyp av Lazy<T>. Följande importegenskap deklarerar en lat import.
Public Class MyClass1
<Import()>
Public Property MyAddin As Lazy(Of IMyAddin)
End Class
public class MyClass
{
[Import]
public Lazy<IMyAddin> MyAddin { get; set; }
}
Ur kompositionsmotorns synvinkel anses en kontraktstyp Lazy<T> vara identisk med kontraktstypen T. Därför skulle den tidigare importen matcha följande export.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
Kontraktsnamnet och kontraktstypen kan anges i Import attributet för en lat import, enligt beskrivningen tidigare i avsnittet "Grundläggande importer och exporter".
Förutsättningar för import
Exporterade MEF-delar skapas vanligtvis av kompositionsmotorn, som svar på en direktbegäran eller behovet av att fylla en matchad import. När du skapar en del använder kompositionsmotorn som standard en parameterlös konstruktor. Om du vill att motorn ska använda en annan konstruktor kan du markera den med attributet ImportingConstructor .
Varje del får endast ha en konstruktor för att användas av sammansättningsmotorn. Om du inte tillhandahåller någon parameterlös konstruktor och inget ImportingConstructor attribut, eller om du tillhandahåller fler än ett ImportingConstructor attribut, genereras ett fel.
För att fylla parametrarna för en konstruktor som markerats med ImportingConstructor attributet deklareras alla dessa parametrar automatiskt som importer. Det här är ett praktiskt sätt att deklarera importer som används under delvis initiering. Följande klass använder ImportingConstructor för att deklarera en import.
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;
}
}
Som standard ImportingConstructor använder attributet härledda kontraktstyper och kontraktsnamn för alla parameterimporter. Det går att åsidosätta detta genom att dekorera parametrarna med Import attribut, som sedan uttryckligen kan definiera kontraktstyp och kontraktnamn. Följande kod visar en konstruktor som använder den här syntaxen för att importera en härledd klass i stället för en överordnad klass.
<ImportingConstructor()>
Public Sub New(<Import(GetType(IMySubAddin))> ByVal MyAddin As IMyAddin)
End Sub
[ImportingConstructor]
public MyClass([Import(typeof(IMySubAddin))]IMyAddin MyAddin)
{
_theAddin = MyAddin;
}
I synnerhet bör du vara försiktig med insamlingsparametrar. Om du till exempel anger ImportingConstructor för en konstruktor med en parameter av typen IEnumerable<int>matchar importen en enskild export av typen IEnumerable<int>, i stället för en uppsättning exporter av typen int. Om du vill matcha en uppsättning exporter av typen intmåste du dekorera parametern med attributet ImportMany .
Parametrar som deklareras som importer av ImportingConstructor attributet markeras också som nödvändiga importer. MEF tillåter normalt export och import att bilda en cykel. Till exempel är en cykel där objektet A importerar objekt B, som i sin tur importerar objekt A. Under vanliga omständigheter är en cykel inte ett problem, och kompositionscontainern konstruerar båda objekten normalt.
När ett importerat värde krävs av konstruktorn för en del kan objektet inte delta i en cykel. Om objekt A kräver att objekt B byggs innan det kan byggas självt och objekt B importerar objekt A, kan cykeln inte lösas och ett kompositionsfel uppstår. Importer som deklareras på konstruktorparametrar är därför nödvändiga importer, som alla måste fyllas i innan någon export från objektet som kräver dem kan användas.
Valfria importer
Attributet Import anger ett krav för att delen ska fungera. Om en import inte kan genomföras, kommer sammansättningen för den delen att misslyckas och delen kommer inte att vara tillgänglig.
Du kan ange att en import är valfri med hjälp AllowDefault av egenskapen . I det här fallet lyckas kompositionen även om importen inte matchar några tillgängliga exporter och importegenskapen anges till standardvärdet för dess egenskapstyp (null för objektegenskaper, false booleska värden eller noll för numeriska egenskaper.) I följande klass används en valfri import.
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.
}
Importera flera objekt
Attributet Import skapas bara när det matchar en och endast en export. Andra fall kommer att orsaka ett sammanställningsfel. Om du vill importera mer än en export som matchar samma kontrakt använder du attributet ImportMany . Importer som har markerats med det här attributet är alltid valfria. Kompositionen misslyckas till exempel inte om det inte finns några matchande exporter. Följande klass importerar valfritt antal exporter av typen IMyAddin.
Public Class MyClass1
<ImportMany()>
Public Property MyAddin As IEnumerable(Of IMyAddin)
End Class
public class MyClass
{
[ImportMany]
public IEnumerable<IMyAddin> MyAddin { get; set; }
}
Du kan komma åt den importerade matrisen med vanlig IEnumerable<T> syntax och metoder. Det går också att använda en vanlig matris (IMyAddin[]) i stället.
Det här mönstret kan vara mycket viktigt när du använder det i kombination med syntaxen Lazy<T> . Genom att till exempel använda ImportMany, IEnumerable<T>och Lazy<T>kan du importera indirekta referenser till valfritt antal objekt och bara instansiera de som blir nödvändiga. Följande klass visar det här mönstret.
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; }
}
Undvika upptäckt
I vissa fall kanske du vill förhindra att en del identifieras som en del av en katalog. Delen kan till exempel vara en basklass som är avsedd att ärvas från, men inte användas. Det finns två sätt att åstadkomma detta. Först kan du använda nyckelordet abstract i delklassen. Abstrakta klasser tillhandahåller aldrig exporter, även om de kan tillhandahålla ärvda exporter till klasser som härleds från dem.
Om klassen inte kan göras abstrakt kan du dekorera den med attributet PartNotDiscoverable . En del som är dekorerad med det här attributet kommer inte att ingå i några kataloger. I följande exempel visas dessa mönster.
DataOne identifieras av katalogen. Eftersom DataTwo är abstrakt kommer det inte att identifieras. Sedan DataThree attributet PartNotDiscoverable användes identifieras det inte.
<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.
}
Metadata och metadatavyer
Exporter kan ge ytterligare information om sig själva som kallas metadata. Metadata kan användas för att förmedla egenskaperna för det exporterade objektet till importdelen. Importdelen kan använda dessa data för att bestämma vilka exporter som ska användas eller för att samla in information om en export utan att behöva konstruera den. Därför måste en import vara lat för att kunna använda metadata.
Om du vill använda metadata deklarerar du vanligtvis ett gränssnitt som kallas metadatavy, som deklarerar vilka metadata som ska vara tillgängliga. Gränssnittet för metadatavyn får bara ha egenskaper och dessa egenskaper måste ha get accessorer. Följande gränssnitt är en exempelmetadatavy.
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; }
}
Det är också möjligt att använda en allmän samling, , IDictionary<string, object>som en metadatavy, men detta förverkar fördelarna med typkontroll och bör undvikas.
Normalt krävs alla egenskaper som namnges i metadatavyn, och exporter som inte tillhandahåller dem betraktas inte som en matchning. Attributet DefaultValue anger att en egenskap är valfri. Om egenskapen inte ingår tilldelas den det standardvärde som anges som en parameter för DefaultValue. Följande är två olika klasser som är dekorerade med metadata. Båda dessa klasser skulle matcha den tidigare metadatavyn.
<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
{
}
Metadata uttrycks efter Export attributet med hjälp av attributet ExportMetadata . Varje bit metadata består av ett namn/värde-par. Namndelen av metadata måste matcha namnet på lämplig egenskap i metadatavyn och värdet tilldelas till den egenskapen.
Det är importören som anger vilken metadatavy, om någon, som ska användas. En import med metadata deklareras som en lat import, med metadatagränssnittet som den andra typparametern till Lazy<T,T>. Följande klass importerar föregående del med metadata.
Public Class Addin
<Import()>
Public Property plugin As Lazy(Of IPlugin, IPluginMetadata)
End Class
public class Addin
{
[Import]
public Lazy<IPlugin, IPluginMetadata> plugin;
}
I många fall vill du kombinera metadata med ImportMany attributet för att parsa genom tillgängliga importer och välja och instansiera endast en, eller filtrera en samling för att matcha ett visst villkor. Följande klass instansierar endast IPlugin objekt som har Name värdet "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;
}
}
Importera och exportera arv
Om en klass ärver från en del kan den klassen också bli en del. Importer ärvs alltid av underklasser. Därför kommer en underklass av en del alltid att vara en del, med samma importer som den överordnade klassen.
Exporter som deklareras med hjälp Export av attributet ärvs inte av underklasser. En del kan dock exportera sig själv med hjälp av attributet InheritedExport . Delklasser ärver och ger samma export, inklusive kontraktsnamn och kontraktstyp. Till skillnad från ett Export attribut InheritedExport kan endast tillämpas på klassnivå och inte på medlemsnivå. Därför kan exporter på medlemsnivå aldrig ärvas.
Följande fyra klasser visar principerna för arv av import och export.
NumTwo ärver från NumOne, så NumTwo importerar IMyData. Vanliga exporter ärvs inte, så NumTwo exporterar ingenting.
NumFour ärver från NumThree. Eftersom NumThree använde InheritedExport har NumFour en export med kontraktstyp NumThree. Exporter på medlemsnivå ärvs aldrig, så IMyData exporteras inte.
<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.
}
Om det finns metadata som är associerade med ett InheritedExport attribut ärvs även dessa metadata. (Mer information finns i avsnittet "Metadata och metadatavyer".) Ärvda metadata kan inte ändras av underklassen. Men genom att återdeklarera attributet InheritedExport med samma kontraktnamn och kontraktstyp, men med nya metadata, kan underklassen ersätta de ärvda metadata med nya metadata. Följande klass visar den här principen. Delen MegaLogger ärver från Logger och innehåller attributet InheritedExport . Eftersom MegaLogger deklarerar ny metadata med namnet Status igen, ärver den inte metadata för namn och version från 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.
}
När du återdeklarerar InheritedExport attributet för att åsidosätta metadata kontrollerar du att kontraktstyperna är desamma. (I föregående exempel IPlugin är kontrakttypen.) Om de skiljer sig åt, i stället för att åsidosättas, skapar det andra attributet en andra, oberoende export från delen. I allmänhet innebär det att du måste uttryckligen ange kontraktstypen när du åsidosätter ett InheritedExport attribut, som du ser i föregående exempel.
Eftersom gränssnitt inte kan instansieras direkt kan de vanligtvis inte dekoreras med Export eller Import attribut. Ett gränssnitt kan dock dekoreras med ett InheritedExport attribut på gränssnittsnivå, och den exporten tillsammans med eventuella associerade metadata ärvs av alla implementeringsklasser. Själva gränssnittet är dock inte tillgängligt som en del.
Anpassade exportattribut
De grundläggande exportattributen Export och InheritedExport, kan utökas så att de innehåller metadata som attributegenskaper. Den här tekniken är användbar för att tillämpa liknande metadata på många delar eller skapa ett arvsträd med metadataattribut.
Ett anpassat attribut kan ange kontraktstyp, kontraktnamn eller andra metadata. För att kunna definiera ett anpassat attribut måste en klass som ärver från ExportAttribute (eller InheritedExportAttribute) dekoreras med MetadataAttribute attributet . Följande klass definierar ett anpassat attribut.
<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; }
}
Den här klassen definierar ett anpassat attribut med namnet MyAttribute med kontraktstyp IMyAddin och vissa metadata med namnet MyMetadata. Alla egenskaper i en klass som har markerats med MetadataAttribute attributet anses vara metadatadefinierade i det anpassade attributet. Följande två deklarationer är likvärdiga.
<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; }
I den första deklarationen definieras kontrakttypen och metadata explicit. I den andra deklarationen är kontraktstypen och metadata implicita i det anpassade attributet. Särskilt i de fall där en stor mängd identiska metadata måste tillämpas på många delar (till exempel författare eller upphovsrättsinformation) kan användning av ett anpassat attribut spara mycket tid och duplicering. Dessutom kan arvsträd med anpassade attribut skapas för att tillåta variationer.
Om du vill skapa valfria metadata i ett anpassat attribut kan du använda attributet DefaultValue . När det här attributet tillämpas på en egenskap i en anpassad attributklass anger det att den dekorerade egenskapen är valfri och inte behöver anges av en exportör. Om ett värde för egenskapen inte anges tilldelas det standardvärdet för dess egenskapstyp (vanligtvis null, falseeller 0.)
Skapandepolicyer
När en del specificerar en import och sammansättningen utförs, försöker kompositionsbehållaren att hitta en matchande export. Om den matchar importen med en export framgångsrikt, ställs importmedlemmen in som en instans av det exporterade objektet. Var den här instansen kommer ifrån kontrolleras av exportdelens skapandepolicy.
De två möjliga skapandeprinciperna är delad och icke-delad. En del med en skapandeprincip för delning kommer att delas mellan varje import i containern för en del med det kontraktet. När kompositionsmotorn hittar en matchning och måste ange en importegenskap instansierar den bara en ny kopia av delen om en inte redan finns. annars kommer den att tillhandahålla den befintliga kopian. Det innebär att många objekt kan ha referenser till samma del. Sådana delar bör inte förlita sig på internt tillstånd som kan ändras från många platser. Den här principen är lämplig för statiska delar, delar som tillhandahåller tjänster och delar som förbrukar mycket minne eller andra resurser.
En del med en icke-delad skapandeprincip skapas varje gång en matchande import för en av dess exporter hittas. En ny kopia instansieras därför för varje import i containern som matchar något av delens exporterade avtal. Det interna tillståndet hos dessa kopior kommer inte att delas. Den här principen är lämplig för delar där varje import kräver sitt eget interna tillstånd.
Både importen och exporten kan ange skapandeprincipen för en del, bland värdena Shared, NonShared eller Any. Standardvärdet är Any för både import och export. En export som anger Shared eller NonShared endast matchar en import som anger samma eller som anger Any. På samma sätt en import som anger Shared eller NonShared endast matchar en export som anger samma eller som anger Any. Importer och exporter med inkompatibla skapandeprinciper betraktas inte som en matchning, på samma sätt som en import och export vars kontraktnamn eller kontraktstyp inte är en matchning. Om både import och export anger Any, eller inte anger en skapandeprincip och standardvärdet är Any, kommer skapandeprincipen att som standard vara delad.
I följande exempel visas både import och export med angivna principer för skapande.
PartOne anger ingen skapandeprincip, så standardvärdet är Any.
PartTwo anger ingen skapandeprincip, så standardvärdet är Any. Eftersom både import och export standardinställs till Any kommer PartOne att delas.
PartThree anger en Shared skapandeprincip, så PartTwo och PartThree delar samma kopia av PartOne.
PartFour anger en NonShared skapandeprincip, så PartFour delas inte i PartFive.
PartSix anger en NonShared skapandeprincip.
PartFive och PartSix kommer var och en att få separata kopior av PartFour.
PartSeven anger en Shared skapandeprincip. Eftersom det inte finns någon exporterad PartFour med en skapandepolicy av Shared, matchar inte PartSeven-importen något och kommer inte att fyllas.
<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.
}
Livscykel och bortskaffande
Eftersom delar finns i kompositionscontainern kan livscykeln vara mer komplex än vanliga objekt. Delar kan implementera två viktiga livscykelrelaterade gränssnitt: IDisposable och IPartImportsSatisfiedNotification.
Delar som kräver arbete som ska utföras vid avstängning eller som behöver frigöra resurser bör implementera IDisposable, som vanligt för .NET objekt. Dock, eftersom containern skapar och underhåller referenser till delar, bör endast den container som äger en del anropa metoden Dispose på den. Själva containern implementerar IDisposable, och som en del av dess rensning i Dispose kommer den att anropa Dispose på alla delar som den äger. Därför bör du alltid ta bort kompositionscontainern när den och eventuella delar den äger inte längre behövs.
För långlivade kompositionscontainrar kan minnesförbrukningen av delar med en skapandepolicy som inte delas bli ett problem. Dessa icke-delade delar kan skapas flera gånger och kommer inte att försvinna förrän själva containern försvinner. För att hantera detta tillhandahåller ReleaseExport containern metoden . Om du anropar den här metoden på en ej delad export tas exporten bort från kompositionscontainern och avyttras. Delar som endast används av den borttagna exporten, och så vidare nedåt i trädet, avlägsnas också och kasseras. På så sätt kan resurser frigöras utan att själva kompositionscontainern tas bort.
IPartImportsSatisfiedNotification innehåller en metod med namnet OnImportsSatisfied. Den här metoden anropas av kompositionscontainern på alla delar som implementerar gränssnittet när sammansättningen har slutförts och delens import är klar för användning. Delar skapas av kompositionsmotorn för att fylla importen av andra delar. Innan importen av en del har angetts kan du inte utföra någon initiering som förlitar sig på eller ändrar importerade värden i delkonstruktorn om inte dessa värden har angetts som förutsättningar med hjälp ImportingConstructor av attributet. Detta är normalt den föredragna metoden, men i vissa fall kanske konstruktorinmatningen inte är tillgänglig. I dessa fall kan initiering utföras i OnImportsSatisfied, och delen bör implementera IPartImportsSatisfiedNotification.