Bagikan melalui


Gambaran umum model pemrograman atribut (MEF)

Dalam Managed Extensibility Framework (MEF), model pemrograman adalah metode tertentu untuk menentukan set objek konseptual tempat MEF beroperasi. Objek konseptual ini mencakup bagian, impor, dan ekspor. MEF menggunakan objek ini, tetapi tidak menentukan bagaimana objek tersebut harus diwakili. Oleh karena itu, berbagai model pemrograman dimungkinkan, termasuk model pemrograman yang disesuaikan.

Model pemrograman default yang digunakan dalam MEF adalah model pemrograman atribut. Dalam bagian model pemrograman atribut, impor, ekspor, dan objek lainnya didefinisikan dengan atribut yang menghias kelas .NET Framework biasa. Topik ini menjelaskan cara menggunakan atribut yang disediakan oleh model pemrograman atribut untuk membuat aplikasi MEF.

Dasar-Dasar Impor dan Ekspor

Ekspor adalah nilai yang disediakan bagian lain dalam kontainer, dan impor adalah persyaratan yang diekspresikan bagian ke kontainer, untuk diisi dari ekspor yang tersedia. Dalam model pemrograman yang diatribusikan, impor dan ekspor dideklarasikan dengan mendekorasi kelas atau anggota dengan atribut Import dan Export. Atribut Export dapat menghias kelas, bidang, properti, atau metode, sementara atribut Import dapat menghias parameter bidang, properti, atau konstruktor.

Agar impor dicocokkan dengan ekspor, impor dan ekspor harus memiliki kontrak yang sama. Kontrak terdiri dari string, yang disebut nama kontrak, dan jenis objek yang diekspor atau diimpor, yang disebut jenis kontrak. Hanya jika nama kontrak dan jenis kontrak cocok adalah ekspor yang dianggap memenuhi impor tertentu.

Salah satu atau kedua parameter kontrak dapat implisit atau eksplisit. Kode berikut menunjukkan kelas yang mendeklarasikan impor dasar.

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

Dalam impor ini, atribut Import tidak memiliki jenis kontrak atau parameter nama kontrak yang terlampir. Oleh karena itu, keduanya akan disimpulkan dari properti yang didekorasi. Dalam hal ini, jenis kontrak akan menjadi IMyAddin, dan nama kontrak akan menjadi string unik yang dibuat dari jenis kontrak. (Dengan kata lain, nama kontrak hanya akan cocok dengan ekspor yang namanya juga disimpulkan dari jenis IMyAddin.)

Berikut ini memperlihatkan ekspor yang cocok dengan impor sebelumnya.

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

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

Dalam ekspor ini, jenis kontrak adalah IMyAddin karena ditentukan sebagai parameter atribut Export. Jenis yang diekspor harus sama dengan jenis kontrak, berasal dari jenis kontrak, atau mengimplementasikan jenis kontrak jika merupakan antarmuka. Dalam ekspor ini, jenis aktual MyLogger mengimplementasikan antarmuka IMyAddin. Nama kontrak disimpulkan dari jenis kontrak, yang berarti bahwa ekspor ini akan cocok dengan impor sebelumnya.

Catatan

Ekspor dan impor biasanya harus dideklarasikan pada kelas atau anggota publik. Deklarasi lain didukung, tetapi mengekspor atau mengimpor anggota privat, terlindungi, atau internal merusak model isolasi untuk bagian tersebut dan karenanya tidak disarankan.

Jenis kontrak harus sama persis agar ekspor dan impor dianggap cocok. Pertimbangkan ekspor berikut.

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

Dalam ekspor ini, jenis kontrak adalah MyLogger bukan IMyAddin. Meskipun MyLogger mengimplementasikan IMyAddin, dan oleh karena itu dapat dilemparkan ke objek IMyAddin, ekspor ini tidak akan cocok dengan impor sebelumnya karena jenis kontrak tidak sama.

Secara umum, tidak perlu menentukan nama kontrak, dan sebagian besar kontrak harus didefinisikan dalam hal jenis kontrak dan metadata. Namun, dalam keadaan tertentu, penting untuk menentukan nama kontrak secara langsung. Kasus yang paling umum adalah ketika kelas mengekspor beberapa nilai yang berbagi jenis umum, seperti primitif. Nama kontrak dapat ditentukan sebagai parameter pertama dari atribut Import atau Export. Kode berikut menunjukkan impor dan ekspor dengan nama kontrak tertentu dari 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;
}

Jika jenis kontrak tidak ditentukan, jenis kontrak masih akan disimpulkan dari jenis impor atau ekspor. Namun, bahkan jika nama kontrak ditentukan secara eksplisit, jenis kontrak juga harus cocok persis agar impor dan ekspor dianggap cocok. Misalnya, jika bidang MajorRevision adalah string, jenis kontrak yang disimpulkan tidak akan cocok dan ekspor tidak akan cocok dengan impor, meskipun memiliki nama kontrak yang sama.

Mengimpor dan Mengekspor Metode

Atribut Export juga dapat menghias metode, dengan cara yang sama seperti kelas, properti, atau fungsi. Ekspor metode harus menentukan jenis kontrak atau nama kontrak, karena jenis tidak dapat disimpulkan. Jenis yang ditentukan dapat berupa delegasi kustom atau jenis generik, seperti Func. Kelas berikut mengekspor metode bernama 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);
}

Di kelas ini, metode DoSomething mengambil satu parameter int dan mengembalikan string. Agar sesuai dengan ekspor ini, bagian impor harus menyatakan anggota yang sesuai. Kelas berikut mengimpor metode DoSomething.

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

Untuk informasi selengkapnya tentang cara menggunakan objek Func<T, T>, lihat Func<T,TResult>.

Jenis Impor

MEF mendukung beberapa jenis impor, termasuk dinamis, malas, prasyarat, dan opsional.

Impor Dinamis

Dalam beberapa kasus, kelas impor mungkin ingin mencocokkan ekspor dari jenis apa pun yang memiliki nama kontrak tertentu. Dalam skenario ini, kelas dapat mendeklarasikan impor dinamis. Impor berikut cocok dengan ekspor apa pun dengan nama kontrak "TheString".

Public Class MyClass1

    <Import("TheString")>
    Public Property MyAddin

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

Ketika jenis kontrak disimpulkan dari kata kunci dynamic, itu akan cocok dengan jenis kontrak apa pun. Dalam hal ini, impor harus selalu menentukan nama kontrak yang akan dicocokkan. (Jika tidak ada nama kontrak yang ditentukan, impor akan dianggap tidak cocok dengan ekspor.) Kedua ekspor berikut akan cocok dengan impor sebelumnya.

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

Jelas, kelas impor harus siap untuk menangani objek jenis arbitrer.

Impor Lambat

Dalam beberapa kasus, kelas impor mungkin memerlukan referensi tidak langsung ke objek yang diimpor, sehingga objek tidak segera dibuat. Dalam skenario ini, kelas dapat menyatakan impor lambat dengan menggunakan jenis kontrak Lazy<T>. Properti pengimporan berikut menyatakan impor lambat.

Public Class MyClass1

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

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

Dari sudut pandang mesin komposisi, jenis kontrak Lazy<T> dianggap identik dengan jenis kontrak T. Oleh karena itu, impor sebelumnya akan cocok dengan ekspor berikut.

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

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

Nama kontrak dan jenis kontrak dapat ditentukan dalam atribut Import untuk impor lambat, seperti yang dijelaskan sebelumnya di bagian "Impor dan Ekspor Dasar".

Impor Prasyarat

Bagian MEF yang diekspor biasanya dibuat oleh mesin komposisi, sebagai respons terhadap permintaan langsung atau kebutuhan untuk mengisi impor yang cocok. Secara default, saat membuat bagian, mesin komposisi menggunakan konstruktor tanpa parameter. Untuk membuat mesin menggunakan konstruktor yang berbeda, Anda dapat menandainya dengan atribut ImportingConstructor.

Setiap bagian mungkin hanya memiliki satu konstruktor untuk digunakan oleh mesin komposisi. Tidak menyediakan konstruktor tanpa parameter dan tidak ada atribut ImportingConstructor, atau menyediakan lebih dari satu atribut ImportingConstructor, akan menghasilkan kesalahan.

Untuk mengisi parameter konstruktor yang ditandai dengan atribut ImportingConstructor, semua parameter tersebut secara otomatis dinyatakan sebagai impor. Ini adalah cara mudah untuk mendeklarasikan impor yang digunakan selama inisialisasi bagian. Kelas berikut menggunakan ImportingConstructor untuk mendeklarasikan impor.

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

Secara default, atribut ImportingConstructor menggunakan jenis kontrak yang disimpulkan dan nama kontrak untuk semua impor parameter. Dimungkinkan untuk mengambil alih ini dengan mendekorasi parameter dengan atribut Import, yang kemudian dapat menentukan jenis kontrak dan nama kontrak secara eksplisit. Kode berikut menunjukkan konstruktor yang menggunakan sintaks ini untuk mengimpor kelas turunan, bukan kelas induk.

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

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

Secara khusus, Anda harus berhati-hati dengan parameter pengumpulan. Misalnya, jika Anda menentukan ImportingConstructor pada konstruktor dengan parameter jenis IEnumerable<int>, impor akan cocok dengan satu ekspor jenis IEnumerable<int>, bukan sekumpulan ekspor jenis int. Untuk mencocokkan sekumpulan ekspor jenis int, Anda harus menghias parameter dengan atribut ImportMany.

Parameter yang dinyatakan sebagai impor oleh atribut ImportingConstructor juga ditandai sebagai impor prasyarat. MEF biasanya memungkinkan ekspor dan impor untuk membentuk siklus. Misalnya, siklus adalah tempat objek A mengimpor objek B, yang pada gilirannya mengimpor objek A. Dalam keadaan biasa, siklus tidak menjadi masalah, dan kontainer komposisi membangun kedua objek secara normal.

Ketika nilai yang diimpor diperlukan oleh konstruktor bagian, objek tersebut tidak dapat berpartisipasi dalam siklus. Jika objek A mengharuskan objek B dibangun sebelum dapat dibangun sendiri, dan objek B mengimpor objek A, maka siklus tidak akan dapat diselesaikan dan kesalahan komposisi akan terjadi. Oleh karena itu, impor yang dideklarasikan pada parameter konstruktor adalah impor prasyarat, yang semuanya harus diisi sebelum salah satu ekspor dari objek yang mengharuskannya dapat digunakan.

Impor Opsional

Atribut Import menentukan persyaratan agar bagian berfungsi. Jika impor tidak dapat dipenuhi, komposisi bagian tersebut akan gagal dan bagian tersebut tidak akan tersedia.

Anda dapat menentukan bahwa impor bersifat opsional dengan menggunakan properti AllowDefault. Dalam hal ini, komposisi akan berhasil bahkan jika impor tidak cocok dengan ekspor yang tersedia, dan properti impor akan diatur ke default untuk jenis propertinya (null untuk properti objek, false untuk Boolean, atau nol untuk properti numerik.) Kelas berikut menggunakan impor opsional.

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

Mengimpor Beberapa Objek

Atribut Import hanya akan berhasil disusun ketika cocok dengan satu dan hanya satu ekspor. Kasus lain akan menghasilkan kesalahan komposisi. Untuk mengimpor lebih dari satu ekspor yang cocok dengan kontrak yang sama, gunakan atribut ImportMany. Impor yang ditandai dengan atribut ini selalu opsional. Misalnya, komposisi tidak akan gagal jika tidak ada ekspor yang cocok. Kelas berikut mengimpor sejumlah ekspor jenis IMyAddin.

Public Class MyClass1

    <ImportMany()>
    Public Property MyAddin As IEnumerable(Of IMyAddin)

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

Array yang diimpor dapat diakses dengan menggunakan sintaks dan metode biasa IEnumerable<T>. Dimungkinkan juga untuk menggunakan array biasa (IMyAddin[]) sebagai gantinya.

Pola ini bisa sangat penting ketika Anda menggunakannya dalam kombinasi dengan sintaks Lazy<T>. Misalnya, dengan menggunakan ImportMany, IEnumerable<T>, dan Lazy<T>, Anda dapat mengimpor referensi tidak langsung ke sejumlah objek dan hanya membuat instans objek yang menjadi diperlukan. Contoh berikut menunjukkan pola ini.

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

Menghindari Penemuan

Dalam beberapa kasus, Anda mungkin ingin mencegah bagian ditemukan sebagai bagian dari katalog. Misalnya, bagian tersebut mungkin merupakan kelas dasar yang dimaksudkan untuk diwarisi, tetapi tidak digunakan. Ada dua cara Anda dapat mencapainya. Pertama, Anda dapat menggunakan kata kunci abstract pada kelas bagian. Kelas abstrak tidak pernah menyediakan ekspor, meskipun mereka dapat memberikan ekspor yang diwariskan ke kelas yang berasal dari mereka.

Jika kelas tidak dapat dibuat abstrak, Anda dapat menghiasnya dengan atribut PartNotDiscoverable. Bagian yang dihiasi dengan atribut ini tidak akan disertakan dalam katalog apa pun. Contoh berikut menunjukkan persyaratan ini. DataOne akan ditemukan oleh katalog. Karena DataTwo bersifat abstrak, itu tidak akan ditemukan. Karena DataThree menggunakan atribut PartNotDiscoverable, atribut tidak akan ditemukan.

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

Tampilan Metadata dan Metadata

Ekspor dapat memberikan informasi tambahan tentang diri mereka sendiri yang dikenal sebagai metadata. Metadata dapat digunakan untuk menyampaikan properti objek yang diekspor ke bagian impor. Bagian impor dapat menggunakan data ini untuk memutuskan ekspor mana yang akan digunakan, atau untuk mengumpulkan informasi tentang ekspor tanpa harus membangunnya. Untuk alasan ini, impor harus malas untuk menggunakan metadata.

Untuk menggunakan metadata, Anda biasanya mendeklarasikan antarmuka yang dikenal sebagai tampilan metadata, yang menyatakan metadata apa yang akan tersedia. Antarmuka tampilan metadata hanya boleh memiliki properti, dan properti tersebut harus memiliki pengakses get. Antarmuka berikut adalah contoh tampilan metadata.

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

Dimungkinkan juga untuk menggunakan koleksi generik, IDictionary<string, object>, sebagai tampilan metadata, tetapi ini kehilangan manfaat pemeriksaan jenis dan harus dihindari.

Biasanya, semua properti yang dinamai dalam tampilan metadata diperlukan, dan ekspor apa pun yang tidak menyediakannya tidak akan dianggap cocok. Atribut DefaultValue menentukan bahwa properti bersifat opsional. Jika properti tidak disertakan, properti akan diberi nilai default yang ditentukan sebagai parameter DefaultValue. Berikut ini adalah dua kelas berbeda yang dihiasi dengan metadata. Kedua kelas ini akan cocok dengan tampilan metadata sebelumnya.

<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 dinyatakan setelah atribut Export dengan menggunakan atribut ExportMetadata. Setiap bagian metadata terdiri dari pasangan nama/nilai. Bagian nama metadata harus cocok dengan nama properti yang sesuai dalam tampilan metadata, dan nilai akan ditetapkan ke properti tersebut.

Ini adalah pengimpor yang menentukan tampilan metadata apa, jika ada, yang akan digunakan. Impor dengan metadata dinyatakan sebagai impor malas, dengan antarmuka metadata sebagai parameter jenis kedua ke Lazy<T,T>. Kelas berikut mengimpor bagian sebelumnya dengan metadata.

Public Class Addin

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

Dalam banyak kasus, Anda ingin menggabungkan metadata dengan atribut ImportMany, untuk mengurai impor yang tersedia dan memilih dan membuat instans hanya satu, atau memfilter koleksi agar sesuai dengan kondisi tertentu. Kelas berikut hanya membuat instans objek IPlugin yang memiliki nilai Name "Pencatat".

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

Pewarisan Impor dan Ekspor

Jika kelas mewarisi dari suatu bagian, kelas tersebut juga dapat menjadi bagian. Impor selalu diwariskan oleh subkelas. Oleh karena itu, subkelas bagian akan selalu menjadi bagian, dengan impor yang sama dengan kelas induknya.

Ekspor yang dinyatakan dengan menggunakan atribut Export tidak diwarisi oleh subkelas. Namun, bagian dapat mengekspor dirinya sendiri dengan menggunakan atribut InheritedExport. Subkelas bagian akan mewarisi dan memberikan ekspor yang sama, termasuk nama kontrak dan jenis kontrak. Tidak seperti atribut Export, InheritedExport hanya dapat diterapkan di tingkat kelas, dan bukan di tingkat anggota. Oleh karena itu, ekspor tingkat anggota tidak pernah dapat diwariskan.

Empat kelas berikut menunjukkan prinsip-prinsip pewarisan impor dan ekspor. NumTwo mewarisi dari NumOne, sehingga NumTwo akan mengimpor IMyData. Ekspor biasa tidak diwariskan, sehingga NumTwo tidak akan mengekspor apa pun. NumFour mewarisi dari NumThree. Karena NumThree digunakan InheritedExport, NumFour memiliki satu ekspor dengan jenis kontrak NumThree. Ekspor tingkat anggota tidak pernah diwariskan, jadi IMyData tidak diekspor.

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

Jika ada metadata yang terkait dengan atribut InheritedExport, metadata tersebut juga akan diwariskan. (Untuk informasi selengkapnya, lihat bagian "Tampilan Metadata dan Metadata" sebelumnya.) Metadata yang diwariskan tidak dapat dimodifikasi oleh subkelas. Namun, dengan mendeklarasikan ulang atribut InheritedExport dengan nama kontrak dan jenis kontrak yang sama, tetapi dengan metadata baru, subkelas dapat mengganti metadata yang diwariskan dengan metadata baru. Kelas berikut menunjukkan prinsip ini. Bagian MegaLogger mewarisi dari Logger dan menyertakan atribut InheritedExport. Karena MegaLogger mendeklarasikan ulang metadata baru bernama Status, metadata Nama dan Versi tidak diwarisi dari 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.
}

Saat mendeklarasikan ulang atribut InheritedExport untuk mengambil alih metadata, pastikan bahwa jenis kontrak sama. (Dalam contoh sebelumnya, IPlugin adalah jenis kontrak.) Jika berbeda, alih-alih mengambil alih, atribut kedua akan membuat ekspor independen kedua dari bagian tersebut. Umumnya, ini berarti Bahwa Anda harus secara eksplisit menentukan jenis kontrak ketika Anda mengambil alih atribut InheritedExport, seperti yang ditunjukkan pada contoh sebelumnya.

Karena antarmuka tidak dapat diinstansiasi secara langsung, antarmuka umumnya tidak dapat didekorasi dengan atribut Export atau Import. Namun, antarmuka dapat dihiasi dengan atribut InheritedExport di tingkat antarmuka, dan ekspor tersebut bersama dengan metadata terkait akan diwarisi oleh kelas penerapan apa pun. Antarmuka itu sendiri tidak akan tersedia sebagai bagian, namun.

Atribut Ekspor Kustom

Atribut ekspor dasar, Export dan InheritedExport, dapat diperluas untuk menyertakan metadata sebagai properti atribut. Teknik ini berguna untuk menerapkan metadata serupa ke banyak bagian, atau membuat pohon warisan atribut metadata.

Atribut kustom dapat menentukan jenis kontrak, nama kontrak, atau metadata lainnya. Untuk menentukan atribut kustom, kelas yang mewarisi dari ExportAttribute (atau InheritedExportAttribute) harus dihiasi dengan atribut MetadataAttribute. Kelas berikut mendefinisikan atribut kustom.

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

Kelas ini mendefinisikan atribut kustom bernama MyAttribute dengan jenis kontrak IMyAddin dan beberapa metadata bernama MyMetadata. Semua properti di kelas yang ditandai dengan atribut MetadataAttribute dianggap sebagai metadata yang ditentukan dalam atribut kustom. Dua pernyataan berikut setara.

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

Dalam deklarasi pertama, jenis kontrak dan metadata ditentukan secara eksplisit. Dalam deklarasi kedua, jenis kontrak dan metadata implisit dalam atribut yang disesuaikan. Terutama dalam kasus di mana sejumlah besar metadata identik harus diterapkan ke banyak bagian (misalnya, penulis atau informasi hak cipta), menggunakan atribut kustom dapat menghemat banyak waktu dan duplikasi. Selanjutnya, pohon pewarisan atribut kustom dapat dibuat untuk memungkinkan variasi.

Untuk membuat metadata opsional dalam atribut kustom, Anda dapat menggunakan atribut DefaultValue. Ketika atribut ini diterapkan ke properti di kelas atribut kustom, atribut ini menentukan bahwa properti yang didekorasi bersifat opsional dan tidak harus disediakan oleh pengekspor. Jika nilai untuk properti tidak disediakan, nilai default akan ditetapkan untuk jenis propertinya (biasanya null, false, atau 0.)

Kebijakan Pembuatan

Ketika bagian menentukan impor dan komposisi dilakukan, kontainer komposisi mencoba menemukan ekspor yang cocok. Jika berhasil mencocokkan impor dengan ekspor, anggota impor diatur ke instans objek yang diekspor. Dari mana instans ini berasal dikendalikan oleh kebijakan pembuatan bagian pengekspor.

Dua kebijakan pembuatan yang mungkin dibagikan dan tidak dibagikan. Bagian dengan kebijakan pembuatan bersama akan dibagikan antara setiap impor dalam kontainer untuk bagian dengan kontrak tersebut. Ketika mesin komposisi menemukan kecocokan dan harus mengatur properti impor, itu akan membuat instans salinan baru bagian hanya jika satu belum ada; jika tidak, itu akan menyediakan salinan yang ada. Ini berarti bahwa banyak objek mungkin memiliki referensi ke bagian yang sama. Bagian-bagian seperti itu tidak boleh bergantung pada keadaan internal yang mungkin diubah dari banyak tempat. Kebijakan ini sesuai untuk bagian statis, bagian yang menyediakan layanan, dan bagian yang mengonsumsi banyak memori atau sumber daya lainnya.

Bagian dengan kebijakan pembuatan non-berbagi akan dibuat setiap kali impor yang cocok untuk salah satu ekspornya ditemukan. Oleh karena itu, salinan baru akan dibuat untuk setiap impor dalam kontainer yang cocok dengan salah satu kontrak yang diekspor bagian. Status internal salinan ini tidak akan dibagikan. Kebijakan ini sesuai untuk bagian-bagian di mana setiap impor memerlukan status internalnya sendiri.

Impor dan ekspor dapat menentukan kebijakan pembuatan bagian, dari antara nilai Shared, NonShared, atau Any. Defaultnya adalah Any untuk impor dan ekspor. Ekspor yang menentukan Shared atau NonShared hanya akan cocok dengan impor yang menentukan hal yang sama, atau yang menentukan Any. Demikian pula, impor yang menentukan Shared atau NonShared hanya akan cocok dengan ekspor yang menentukan yang sama, atau yang menentukan Any. Impor dan ekspor dengan kebijakan pembuatan yang tidak kompatibel tidak dianggap cocok, dengan cara yang sama seperti impor dan ekspor yang nama kontrak atau jenis kontraknya tidak cocok. Jika impor dan ekspor menentukan Any, atau tidak menentukan kebijakan pembuatan dan default ke Any, kebijakan pembuatan akan default untuk dibagikan.

Contoh berikut menunjukkan impor dan ekspor yang menentukan kebijakan pembuatan. PartOne tidak menentukan kebijakan pembuatan, jadi defaultnya adalah Any. PartTwo tidak menentukan kebijakan pembuatan, jadi defaultnya adalah Any. Karena impor dan ekspor default ke Any, PartOne akan dibagikan. PartThree menentukan Shared kebijakan pembuatan, jadi PartTwo dan PartThree akan berbagi salinan yang sama dari PartOne. PartFour menentukan NonShared kebijakan pembuatan, jadi PartFour tidak akan dibagikan di PartFive. PartSix menentukan NonShared kebijakan pembuatan. PartFive dan PartSix masing-masing akan menerima salinan terpisah dari PartFour. PartSeven menentukan Shared kebijakan pembuatan. Karena tidak ada PartFour yang diekspor dengan pembuatan kebijakan Shared, impor PartSeven tidak cocok dengan apa pun dan tidak akan diisi.

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

Siklus Hidup dan Pembuangan

Karena bagian dihosting dalam wadah komposisi, siklus hidupnya bisa lebih kompleks daripada objek biasa. Bagian dapat mengimplementasikan dua antarmuka terkait siklus hidup penting: IDisposable dan IPartImportsSatisfiedNotification.

Bagian yang mengharuskan pekerjaan dilakukan saat dimatikan atau yang perlu melepaskan sumber daya harus mengimplementasikan IDisposable, seperti biasa untuk objek .NET Framework. Namun, karena kontainer membuat dan mempertahankan referensi ke bagian, hanya kontainer yang memiliki bagian yang harus memanggil metode Dispose di atasnya. Kontainer itu sendiri mengimplementasikan IDisposable, dan sebagai bagian dari pembersihannya di dalamnya Dispose akan memanggil Dispose semua bagian yang dimilikinya. Untuk alasan ini, Anda harus selalu membuang kontainer komposisi ketika itu dan bagian apa pun yang dimilikinya tidak lagi diperlukan.

Untuk kontainer komposisi berumur panjang, konsumsi memori berdasarkan bagian dengan kebijakan pembuatan non-bersama dapat menjadi masalah. Bagian yang tidak dibagikan ini dapat dibuat beberapa kali dan tidak akan dibuang sampai kontainer itu sendiri dibuang. Untuk menangani hal ini, kontainer menyediakan metode ReleaseExport. Memanggil metode ini pada ekspor yang tidak dibagikan akan menghapus ekspor tersebut dari kontainer komposisi dan membuangnya. Bagian yang hanya digunakan oleh ekspor yang dihapus, dan seterusnya di bawah pohon, juga dihapus dan dibuang. Dengan cara ini, sumber daya dapat diklaim kembali tanpa membuang kontainer komposisi itu sendiri.

IPartImportsSatisfiedNotification berisi metode bernama OnImportsSatisfied. Metode ini dipanggil oleh kontainer komposisi pada bagian mana pun yang mengimplementasikan antarmuka ketika komposisi telah selesai dan impor bagian siap digunakan. Bagian dibuat oleh mesin komposisi untuk mengisi impor bagian lain. Sebelum impor bagian ditetapkan, Anda tidak dapat melakukan inisialisasi apa pun yang bergantung pada atau memanipulasi nilai yang diimpor dalam konstruktor bagian kecuali nilai tersebut telah ditentukan sebagai prasyarat dengan menggunakan atribut ImportingConstructor. Ini biasanya metode yang disukai, tetapi dalam beberapa kasus, injeksi konstruktor mungkin tidak tersedia. Dalam kasus tersebut, inisialisasi dapat dilakukan di OnImportsSatisfied, dan bagian harus menerapkan IPartImportsSatisfiedNotification.