MEF(Managed Extensibility Framework)에서 프로그래밍 모델은 MEF가 작동하는 개념 개체 집합을 정의하는 특정 메서드입니다. 이러한 개념 개체에는 파트, 가져오기 및 내보내기가 포함됩니다. MEF는 이러한 개체를 사용하지만 표현 방법을 지정하지는 않습니다. 따라서 사용자 지정된 프로그래밍 모델을 포함하여 다양한 프로그래밍 모델이 가능합니다.
MEF에서 사용되는 기본 프로그래밍 모델은 특성이 지정된 프로그래밍 모델입니다. 특성이 지정된 프로그래밍 모델 파트에서 가져오기, 내보내기 및 기타 개체는 일반 .NET Framework 클래스를 데코레이트하는 특성으로 정의됩니다. 이 항목에서는 특성이 지정된 프로그래밍 모델에서 제공하는 특성을 사용하여 MEF 애플리케이션을 만드는 방법을 설명합니다.
기본 사항 가져오기 및 내보내기
내보내기는 컨테이너의 다른 부분에 제공하는 값이고, 가져오기는 사용 가능한 내보내기에서 채워질 컨테이너에 대한 요구 사항을 표현하는 것입니다. 특성이 지정된 프로그래밍 모델에서 가져오기 및 내보내기가 클래스 또는 멤버를 Import
및 Export
어트리뷰트로 데코레이터를 사용하여 선언됩니다.
Export
특성은 클래스, 필드, 속성 또는 메서드를 데코레이트할 수 있고, Import
특성은 필드, 속성 또는 생성자 매개변수를 데코레이트할 수 있습니다.
가져오기를 내보내기와 일치하려면 가져오기 및 내보내기가 동일한 계약을 가져야 합니다. 계약은 계약 이름이라고 하는 문자열과 계약 형식이라고 하는 내보내거나 가져온 개체의 형식으로 구성 됩니다. 계약 이름과 계약 유형이 모두 일치해야 내보내기가 특정 가져오기를 충족하는 것으로 간주됩니다.
계약 매개 변수 중 하나 또는 둘 다 암시적 또는 명시적일 수 있습니다. 다음 코드는 기본 가져오기를 선언하는 클래스를 보여 줍니다.
Public Class MyClass1
<Import()>
Public Property MyAddin As IMyAddin
End Class
public class MyClass
{
[Import]
public IMyAddin MyAddin { get; set; }
}
가져오기 Import
에서 특성은 계약 형식과 계약 이름 매개 변수가 모두 부착되어 있지 않습니다. 따라서 데코레이션된 속성에서 두 요소 모두 추론됩니다. 이 경우 계약 유형이 되고 IMyAddin
계약 이름은 계약 형식에서 만든 고유한 문자열이 됩니다. 다시 말하면, 계약 이름은 형식 IMyAddin
에서 유추된 이름의 내보내기만 일치합니다.
다음은 이전 가져오기와 일치하는 내보내기를 보여줍니다.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
이 내보내기에서 계약 형식은 특성의 IMyAddin
매개 변수로 지정되기 때문입니다Export
. 내보낸 형식은 계약 형식과 동일하거나, 계약 형식에서 파생되거나, 인터페이스인 경우 계약 형식을 구현해야 합니다. 이 내보내기에서 실제 형식 MyLogger
은 인터페이스 IMyAddin
를 구현합니다. 계약 이름은 계약 유형에서 유추됩니다. 즉, 이 내보내기가 이전 가져오기와 일치합니다.
비고
내보내기 및 가져오기는 일반적으로 공용 클래스 또는 멤버에 선언되어야 합니다. 다른 선언은 지원되지만 프라이빗, 보호된 또는 내부 멤버를 내보내거나 가져오면 파트에 대한 격리 모델이 중단되므로 권장되지 않습니다.
내보내기 및 가져오기가 일치하는 것으로 간주되려면 계약 유형이 정확히 일치해야 합니다. 다음 수출을 검토합니다.
<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 { }
이 내보내기에서는 계약 유형이 MyLogger
가 아니라 IMyAddin
입니다.
MyLogger
가 IMyAddin
를 구현했기 때문에 IMyAddin
개체로 캐스팅될 수 있지만, 계약 유형이 동일하지 않아 이 내보내기는 이전 가져오기와 일치하지 않을 것입니다.
일반적으로 계약 이름을 지정할 필요는 없으며 대부분의 계약은 계약 유형 및 메타데이터 측면에서 정의해야 합니다. 그러나 특정 상황에서 계약 이름을 직접 지정하는 것이 중요합니다. 가장 일반적인 경우는 클래스가 기본 형식과 같은 공통 형식을 공유하는 여러 값을 내보내는 경우입니다. 계약 이름은 Import
또는 Export
특성의 첫 번째 매개 변수로 지정할 수 있습니다. 다음 코드는 지정된 계약 이름을 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;
}
계약 형식을 지정하지 않으면 가져오기 또는 내보내기 형식에서 계속 유추됩니다. 그러나 계약 이름을 명시적으로 지정하더라도 가져오기 및 내보내기가 일치하는 것으로 간주되려면 계약 형식도 정확히 일치해야 합니다. 예를 들어 필드가 MajorRevision
문자열인 경우 유추된 계약 형식은 일치하지 않으며 내보내기가 동일한 계약 이름을 가지고 있음에도 불구하고 가져오기와 일치하지 않습니다.
메서드 가져오기 및 내보내기
이 특성은 Export
클래스, 속성 또는 함수와 같은 방식으로 메서드를 데코레이트할 수도 있습니다. 메서드 내보내기에서는 형식을 유추할 수 없으므로 계약 형식 또는 계약 이름을 지정해야 합니다. 지정된 형식은 사용자 지정 대리자 또는 제네릭 형식(예: Func
.)일 수 있습니다. 다음 클래스는 이름이 지정된 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);
}
이 클래스에서 DoSomething
메서드는 단일 int
매개 변수를 받아 string
를 반환합니다. 이 내보내기와 일치하려면 가져오기 파트가 적절한 멤버를 선언해야 합니다. 다음 클래스는 메서드를 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; }
}
개체를 사용하는 Func<T, T>
방법에 대한 자세한 내용은 다음을 참조하세요 Func<T,TResult>.
가져오기 유형
MEF는 동적, 지연, 필수 구성 요소 및 선택 사항을 비롯한 여러 가져오기 형식을 지원합니다.
동적 가져오기
일부 경우에는 가져오기 클래스가 특정 계약 이름을 가진 모든 유형의 수출을 일치시키고자 할 수 있습니다. 이 시나리오에서 클래스는 동적 가져오기를 선언할 수 있습니다. 다음 가져오기는 계약 이름이 "TheString"인 내보내기와 일치하는 것입니다.
Public Class MyClass1
<Import("TheString")>
Public Property MyAddin
End Class
public class MyClass
{
[Import("TheString")]
public dynamic MyAddin { get; set; }
}
계약 유형이 dynamic
키워드를 통해 유추되면, 모든 계약 유형과 일치합니다. 이 경우 가져오기는 항상 일치시킬 계약 이름을 지정해야 합니다. (계약 이름이 지정되지 않은 경우 가져오기는 내보내기 없음과 일치하는 것으로 간주됩니다.) 다음 두 내보내기 모두 이전 가져오기와 일치합니다.
<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 { }
물론 임의 형식의 개체를 처리하도록 가져오기 클래스를 준비해야 합니다.
느린 불러오기
일부 경우 가져오기 클래스는 개체가 즉시 인스턴스화되지 않도록 가져온 개체에 대한 간접 참조가 필요할 수 있습니다. 이 시나리오에서 클래스는 계약 형식을 사용하여 Lazy<T>
를 선언할 수 있습니다. 다음 가져오기 속성은 지연 가져오기를 선언합니다.
Public Class MyClass1
<Import()>
Public Property MyAddin As Lazy(Of IMyAddin)
End Class
public class MyClass
{
[Import]
public Lazy<IMyAddin> MyAddin { get; set; }
}
컴퍼지션 엔진의 관점에서 계약 형식은 계약 형식 Lazy<T>
T
과 동일한 것으로 간주됩니다. 따라서 이전 가져오기는 다음 내보내기와 일치합니다.
<Export(GetType(IMyAddin))>
Public Class MyLogger
Implements IMyAddin
End Class
[Export(typeof(IMyAddin))]
public class MyLogger : IMyAddin { }
계약 이름과 계약 유형은 앞서 "기본 가져오기 및 내보내기" 섹션에서 설명한 대로 지연 가져오기에 대한 Import
속성에 지정할 수 있습니다.
필수 구성 요소 가져오기
내보낸 MEF 파트는 일반적으로 컴퍼지션 엔진에서 직접 요청 또는 일치하는 가져오기를 채울 필요성에 따라 생성됩니다. 기본적으로 파트를 만들 때 컴퍼지션 엔진은 매개 변수가 없는 생성자를 사용합니다. 엔진이 다른 생성자를 사용하도록 하려면 특성으로 ImportingConstructor
표시할 수 있습니다.
각 파트에는 컴퍼지션 엔진에서 사용할 생성자가 하나만 있을 수 있습니다. 매개 변수가 없는 기본 생성자와 ImportingConstructor
특성을 제공하지 않거나 ImportingConstructor
특성을 하나 이상 제공하면 오류가 발생합니다.
특성으로 표시된 생성자의 매개 변수를 ImportingConstructor
채우기 위해 해당 매개 변수는 모두 자동으로 가져오기로 선언됩니다. 이는 파트 초기화 중에 사용되는 가져오기를 선언하는 편리한 방법입니다. 다음 클래스는 가져오기를 선언하는 데 사용합니다 ImportingConstructor
.
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;
}
}
기본적으로 이 특성은 ImportingConstructor
모든 매개 변수 가져오기에 유추된 계약 형식 및 계약 이름을 사용합니다. 매개 변수를 특성으로 Import
데코레이팅하여 이를 재정의할 수 있으며, 그러면 계약 형식 및 계약 이름을 명시적으로 정의할 수 있습니다. 다음 코드에서는 이 구문을 사용하여 부모 클래스 대신 파생 클래스를 가져오는 생성자를 보여 줍니다.
<ImportingConstructor()>
Public Sub New(<Import(GetType(IMySubAddin))> ByVal MyAddin As IMyAddin)
End Sub
[ImportingConstructor]
public MyClass([Import(typeof(IMySubAddin))]IMyAddin MyAddin)
{
_theAddin = MyAddin;
}
특히 컬렉션 매개 변수에 주의해야 합니다. 예를 들어, ImportingConstructor
를 형식 IEnumerable<int>
의 매개 변수와 함께 생성자에 지정하면, 가져오기는 형식 IEnumerable<int>
의 내보내기 집합이 아닌 형식 int
의 단일 내보내기와 일치합니다. 형식 int
의 내보내기 집합과 일치하려면 매개 변수에 ImportMany
속성을 추가해야 합니다.
특성에서 ImportingConstructor
가져오기로 선언된 매개 변수도 필수 구성 요소 가져오기로 표시됩니다. MEF는 일반적으로 내보내기 및 가져오기가 주기를 형성하도록 허용합니다. 예를 들어 주기는 개체 A가 개체 B를 가져오는 위치이며, 이 개체는 개체 A를 가져옵니다. 일반적인 상황에서는 주기가 문제가 되지 않으며 컴퍼지션 컨테이너는 두 개체를 정상적으로 생성합니다.
가져온 값이 파트의 생성자에 필요한 경우 해당 개체는 주기에 참여할 수 없습니다. 개체 A는 개체 B가 먼저 생성되어야만 생성될 수 있는데, 개체 B가 개체 A를 참조하면 주기가 해결되지 않으며 컴퍼지션 오류가 발생합니다. 따라서 생성자 매개 변수에 선언된 가져오기는 필수 구성 요소 가져오기이므로 필요한 개체에서 내보내기 전에 모두 채워야 합니다.
선택적 가져오기
이 특성은 Import
파트가 작동하기 위한 요구 사항을 지정합니다. 가져오기를 수행할 수 없는 경우 해당 파트의 컴퍼지션이 실패하고 파트를 사용할 수 없습니다.
속성을 사용하여 가져오기가 AllowDefault
임을 지정할 수 있습니다. 이 경우 가져오기가 사용 가능한 내보내기와 일치하지 않더라도 구성이 성공하고 가져오기 속성이 해당 속성 형식의 기본값으로 설정됩니다. 개체 속성의 경우 null
, 부울의 경우 false
, 숫자 속성의 경우 0으로 설정됩니다. 다음 클래스는 선택적 가져오기를 사용합니다.
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.
}
여러 개체 가져오기
성공적으로 구성되려면 Import
특성이 오직 하나의 내보내기와 일치해야 합니다. 다른 경우에는 컴퍼지션 오류가 발생합니다. 동일한 계약과 일치하는 여러 내보내기를 가져오려면 ImportMany
특성을 사용하세요. 이 특성으로 표시된 가져오기는 항상 선택 사항입니다. 예를 들어 일치하는 내보내기가 없으면 컴포지션이 실패하지 않는 상태입니다. 다음 클래스는 형식 IMyAddin
의 내보내기를 여러 개 가져옵니다.
Public Class MyClass1
<ImportMany()>
Public Property MyAddin As IEnumerable(Of IMyAddin)
End Class
public class MyClass
{
[ImportMany]
public IEnumerable<IMyAddin> MyAddin { get; set; }
}
가져온 배열은 일반 IEnumerable<T>
구문 및 메서드를 사용하여 액세스할 수 있습니다. 대신 일반 배열(IMyAddin[]
)을 사용할 수도 있습니다.
이 패턴은 구문과 Lazy<T>
함께 사용할 때 매우 중요할 수 있습니다. 예를 들어 , ImportMany
및 IEnumerable<T>
를 사용하여 Lazy<T>
임의의 수의 개체에 대한 간접 참조를 가져오고 필요한 개체만 인스턴스화할 수 있습니다. 다음 클래스는 이 패턴을 보여 줍니다.
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; }
}
발견 회피
경우에 따라 카탈로그의 일부로 부품이 검색되지 않도록 할 수 있습니다. 예를 들어 파트는 상속할 기본 클래스일 수 있지만 사용되지는 않습니다. 이 작업을 수행하는 방법에는 두 가지가 있습니다. 먼저 파트 클래스에서 abstract
키워드를 사용할 수 있습니다. 추상 클래스는 직접적인 내보내기를 제공하지 않지만, 이를 파생한 클래스에는 상속된 내보내기를 제공할 수 있습니다.
클래스를 추상화할 수 없는 경우 특성으로 PartNotDiscoverable
데코레이트할 수 있습니다. 이 특성으로 데코레이팅된 부분은 카탈로그에 포함되지 않습니다. 다음 예제에서는 이러한 패턴을 보여 줍니다.
DataOne
는 카탈로그에서 검색됩니다.
DataTwo
추상적이므로 검색되지 않습니다.
DataThree
가 PartNotDiscoverable
특성을 사용했기 때문에 검색되지 않습니다.
<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.
}
메타데이터 및 메타데이터 뷰
내보내기에서는 메타데이터라고 하는 자신에 대한 추가 정보를 제공할 수 있습니다. 메타데이터를 사용하여 내보낸 개체의 속성을 가져오기 파트로 전달할 수 있습니다. 가져오기 파트는 이 데이터를 사용하여 사용할 내보내기를 결정하거나 내보내기를 생성하지 않고도 내보내기 정보를 수집할 수 있습니다. 이러한 이유로 메타데이터를 사용하려면 가져오기가 지연되어야 합니다.
메타데이터를 사용하려면 일반적으로 사용할 수 있는 메타데이터를 선언하는 메타데이터 뷰라는 인터페이스를 선언합니다. 메타데이터 뷰 인터페이스에는 속성만 있어야 하며 이러한 속성에는 get
접근자가 있어야 합니다. 다음 인터페이스는 예제 메타데이터 뷰입니다.
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; }
}
제네릭 컬렉션을 IDictionary<string, object>
메타데이터 보기로 사용할 수도 있지만 형식 검사의 이점을 상실하므로 피해야 합니다.
일반적으로 메타데이터 뷰에 명명된 모든 속성이 필요하며 이를 제공하지 않는 모든 내보내기가 일치하는 것으로 간주되지 않습니다.
DefaultValue
속성은 특성이 선택 사항임을 지정합니다. 속성이 포함되지 않으면 매개 변수 DefaultValue
로 지정된 기본값이 할당됩니다. 다음은 메타데이터로 데코레이팅된 두 가지 클래스입니다. 이러한 두 클래스는 이전 메타데이터 뷰와 일치합니다.
<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
{
}
메타데이터는 Export
특성 후에 ExportMetadata
특성을 사용하여 표현됩니다. 메타데이터의 각 부분은 이름/값 쌍으로 구성됩니다. 메타데이터의 이름 부분은 메타데이터 보기에서 해당 속성의 이름과 일치해야 하며 값은 해당 속성에 할당됩니다.
사용 중인 메타데이터 보기(있는 경우)를 지정하는 가져오기입니다. 메타데이터가 있는 가져오기는 지연 가져오기로 선언되고 메타데이터 인터페이스는 두 번째 형식 매개 변수 Lazy<T,T>
로 사용됩니다. 다음 클래스는 메타데이터를 사용하여 이전 부분을 가져옵니다.
Public Class Addin
<Import()>
Public Property plugin As Lazy(Of IPlugin, IPluginMetadata)
End Class
public class Addin
{
[Import]
public Lazy<IPlugin, IPluginMetadata> plugin;
}
대부분의 경우 사용 가능한 가져오기를 구문 분석하고 하나만 선택 및 인스턴스화하거나 특정 조건에 맞게 컬렉션을 필터링하기 위해 메타데이터 ImportMany
를 특성과 결합하려고 합니다. 다음 클래스는 값이 "Logger"인 IPlugin
개체만 Name
인스턴스화합니다.
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;
}
}
상속 가져오기 및 내보내기
클래스가 파트에서 상속되는 경우 해당 클래스도 파트가 될 수 있습니다. 임포트는 항상 서브클래스에 상속됩니다. 따라서 파트의 하위 클래스는 항상 부모 클래스와 동일한 가져오기를 사용하여 파트가 됩니다.
특성을 사용해 Export
로 선언된 내보내기는 하위 클래스에 상속되지 않습니다. 그러나 파트는 InheritedExport
속성을 사용하여 자신을 내보낼 수 있습니다. 파트의 하위 클래스는 계약 이름 및 계약 형식을 포함하여 동일한 내보내기를 상속하고 제공합니다. 특성 Export
과 InheritedExport
달리 멤버 수준이 아닌 클래스 수준에서만 적용할 수 있습니다. 따라서 멤버 단위의 내보내기는 절대 상속될 수 없습니다.
다음 네 가지 클래스는 가져오기 및 내보내기 상속의 원칙을 보여 줍니다.
NumTwo
는 NumOne
로부터 상속받습니다, 그래서 NumTwo
가 IMyData
를 가져옵니다. 일반 내보내기가 상속되지 않으므로 NumTwo
아무것도 내보내지 않습니다.
NumFour
에서 상속됩니다 NumThree
.
NumThree
가 InheritedExport
을 사용했기 때문에, NumFour
에는 계약 유형이 NumThree
인 내보내기가 하나 있습니다. 멤버 수준 내보내기는 상속되지 않으므로 IMyData
내보내지 않습니다.
<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.
}
특성과 InheritedExport
연결된 메타데이터가 있는 경우 해당 메타데이터도 상속됩니다. (자세한 내용은 이전의 "메타데이터 및 메타데이터 뷰" 섹션을 참조하세요.) 상속된 메타데이터는 하위 클래스에서 수정할 수 없습니다. 그러나 InheritedExport
특성을 동일한 계약 이름 및 계약 형식으로 다시 선언하여 새 메타데이터를 사용하면, 하위 클래스는 상속된 메타데이터를 새 메타데이터로 바꿀 수 있습니다. 다음 클래스는 이 원칙을 보여 줍니다.
MegaLogger
부품은 Logger
에서 상속받고 InheritedExport
특성을 포함합니다.
MegaLogger
Status라는 새 메타데이터를 다시 선언하므로 이름 및 버전 메타데이터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.
}
메타데이터를 재정의하기 위해 InheritedExport
특성을 다시 선언할 때 계약 종류가 동일한지 확인합니다. 이전 예제에서 IPlugin
는 계약 유형입니다. 이들이 다를 경우, 재정의하는 대신 두 번째 속성은 파트에서 독립적인 두 번째 내보내기를 생성합니다. 일반적으로, 이는 InheritedExport
특성을 재정의할 때, 이전 예제에서 보여준 것처럼 계약 유형을 명시적으로 지정해야 한다는 것을 의미합니다.
인터페이스는 직접 인스턴스화할 수 없으므로 일반적으로 특성을 사용하여 Export
Import
데코레이팅할 수 없습니다. 그러나 인터페이스는 인터페이스 수준에서 InheritedExport
특성으로 데코레이팅할 수 있으며, 내보내기 작업과 관련된 메타데이터는 구현 클래스에 의해 상속됩니다. 그러나 인터페이스 자체는 일부로 사용할 수 없습니다.
사용자 지정 내보내기 특성
기본 내보내기 특성 Export
및 InheritedExport
메타데이터를 특성 속성으로 포함하도록 확장할 수 있습니다. 이 기술은 유사한 메타데이터를 여러 부분에 적용하거나 메타데이터 특성의 상속 트리를 만드는 데 유용합니다.
사용자 지정 특성은 계약 유형, 계약 이름 또는 기타 메타데이터를 지정할 수 있습니다. 사용자 지정 특성을 정의하려면 ExportAttribute
또는 InheritedExportAttribute
를 상속받는 클래스에 MetadataAttribute
특성을 데코레이트해야 합니다. 다음 클래스는 사용자 지정 특성을 정의합니다.
<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; }
}
이 클래스는 계약 형식 MyAttribute
과 명명IMyAddin
된 일부 메타데이터를 사용하여 명명된 MyMetadata
사용자 지정 특성을 정의합니다. 특성으로 표시된 클래스의 MetadataAttribute
모든 속성은 사용자 지정 특성에 정의된 메타데이터로 간주됩니다. 다음 두 선언은 동일합니다.
<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; }
첫 번째 선언에서 계약 유형 및 메타데이터는 명시적으로 정의됩니다. 두 번째 선언에서 계약 유형 및 메타데이터는 사용자 지정된 특성에서 암시적입니다. 특히 많은 부분(예: 작성자 또는 저작권 정보)에 많은 양의 동일한 메타데이터를 적용해야 하는 경우 사용자 지정 특성을 사용하면 많은 시간과 중복을 절약할 수 있습니다. 또한 변형을 허용하도록 사용자 지정 특성의 상속 트리를 만들 수 있습니다.
사용자 지정 특성에서 선택적 메타데이터를 만들려면 이 특성을 사용할 DefaultValue
수 있습니다. 이 특성이 사용자 지정 특성 클래스의 속성에 적용되는 경우 데코레이팅된 속성은 선택 사항이며 내보내기자가 제공할 필요가 없음을 지정합니다. 속성 값이 제공되지 않으면 속성 형식의 기본값(일반적으로 null
, false
또는 0)이 할당됩니다.
생성 정책
파트가 임포트와 컴포지션을 지정하면, 컴포지션 컨테이너는 일치하는 엑스포트를 찾으려고 시도합니다. 가져오기와 내보내기가 일치하는 경우 가져오기 멤버는 내보낸 개체의 인스턴스로 설정됩니다. 이 인스턴스의 위치는 내보내기 파트의 생성 정책에 의해 제어됩니다.
두 가지 가능한 만들기 정책은 공유 되고 공유되지 않습니다. 공유 생성 정책이 있는 파트는 해당 계약이 있는 파트에 대해 컨테이너 내 모든 가져오기 간에 공유됩니다. 컴퍼지션 엔진이 일치 항목을 찾고 가져오기 속성을 설정해야 하는 경우 아직 없는 경우에만 파트의 새 복사본을 인스턴스화합니다. 그렇지 않으면 기존 복사본을 제공합니다. 즉, 많은 개체에 동일한 부분에 대한 참조가 있을 수 있습니다. 이러한 부분은 여러 위치에서 변경될 수 있는 내부 상태에 의존해서는 안 됩니다. 이 정책은 정적 파트, 서비스를 제공하는 파트 및 많은 메모리 또는 기타 리소스를 사용하는 파트에 적합합니다.
비공유의 만들기 정책이 있는 파트는 해당 내보내기 중 하나에 대해 일치하는 가져오기를 찾을 때마다 만들어집니다. 따라서 해당 파트의 내보낸 계약과 일치하는 경우, 컨테이너의 모든 가져오기에 대해 새 복사본이 생성됩니다. 이러한 복사본의 내부 상태는 공유되지 않습니다. 이 정책은 각 가져오기에 자체 내부 상태가 필요한 부분에 적합합니다.
가져오기 및 내보내기 둘 다 값 Shared
NonShared
중에서 파트의 만들기 정책을 지정할 수 있습니다Any
. 기본값은 Any
가져오기 및 내보내기 모두에 대한 것입니다.
Shared
또는 NonShared
을 지정하는 내보내기는 동일한 것을 지정하거나 Any
을 지정하는 가져오기와만 일치합니다. 마찬가지로, Shared
또는 NonShared
을 지정하는 가져오기는 동일한 것을 지정하거나 Any
을 지정하는 내보내기와만 일치합니다. 호환되지 않는 만들기 정책을 사용하는 가져오기 및 내보내기에서는 계약 이름 또는 계약 형식이 일치하지 않는 가져오기 및 내보내기와 같은 방식으로 일치 항목으로 간주되지 않습니다. 가져오기와 내보내기가 모두 Any
을(를) 지정하거나 생성 정책을 지정하지 않고 기본값이 Any
일 경우, 생성 정책은 기본값으로 공유로 설정됩니다.
다음 예제에서는 만들기 정책을 지정하는 가져오기 및 내보내기를 모두 보여 줍니다.
PartOne
에서는 만들기 정책을 지정하지 않으므로 기본값은 .입니다 Any
.
PartTwo
에서는 만들기 정책을 지정하지 않으므로 기본값은 .입니다 Any
. 가져오기 및 내보내기 모두 기본값이므로 Any
PartOne
공유됩니다.
PartThree
는 Shared
만들기 정책을 지정하므로 PartTwo
PartThree
동일한 복사본 PartOne
을 공유합니다.
PartFour
는 NonShared
만들기 정책을 지정하므로 PartFour
는 PartFive
에서 공유되지 않습니다.
PartSix
는 만들기 정책을 지정합니다 NonShared
.
PartFive
와 PartSix
는 각각 PartFour
의 별도 복사본을 받게 됩니다.
PartSeven
는 만들기 정책을 지정합니다 Shared
. 생성 정책이 PartFour
인 내보낸 Shared
이 없으므로 PartSeven
가져오기는 어떤 것도 일치하지 않아 채워지지 않습니다.
<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.
}
수명 주기 및 폐기
파트는 컴퍼지션 컨테이너에 호스트되므로 해당 수명 주기는 일반 개체보다 더 복잡할 수 있습니다. 파트는 두 가지 중요한 수명 주기 관련 인터페이스를 구현할 수 있습니다 IDisposable
및 IPartImportsSatisfiedNotification
.
종료 시 작업을 수행해야 하거나 리소스를 해제해야 하는 파트는 .NET Framework 개체에 대해 평소와 같이 구현 IDisposable
해야 합니다. 그러나 컨테이너는 파트에 대한 참조를 만들고 유지 관리하므로 파트를 소유하는 컨테이너만 해당 파트에 대한 메서드를 Dispose
호출해야 합니다. 컨테이너 자체는 IDisposable
을(를) 구현하며, 정리 작업의 일환으로 Dispose
에서 소유하고 있는 모든 파트에 대해 Dispose
을(를) 호출합니다. 이러한 이유로, 컴퍼지션 컨테이너와 그에 속한 모든 부품이 더 이상 필요하지 않게 되면 항상 폐기해야 합니다.
수명이 긴 컴퍼지션 컨테이너의 경우 공유하지 않는 만들기 정책을 사용하는 파트의 메모리 사용이 문제가 될 수 있습니다. 이러한 비 공유 파트는 여러 번 만들 수 있으며 컨테이너 자체가 삭제될 때까지 삭제되지 않습니다. 이를 처리하기 위해 컨테이너는 메서드를 ReleaseExport
제공합니다. 공유가 아닌 내보내기에서 이 메서드를 호출하면 컴퍼지션 컨테이너에서 해당 내보내기가 제거되고 삭제됩니다. 제거된 내보내기에서만 사용되는 부분 및 트리 아래로 사용되는 부분도 제거되고 삭제됩니다. 이러한 방식으로 컴퍼지션 컨테이너 자체를 삭제하지 않고 리소스를 회수할 수 있습니다.
IPartImportsSatisfiedNotification
에는 이름이 1개인 OnImportsSatisfied
메서드가 포함되어 있습니다. 이 메서드는 컴퍼지션이 완료되고 파트의 가져오기를 사용할 준비가 되었을 때 인터페이스를 구현하는 모든 파트의 컴퍼지션 컨테이너에 의해 호출됩니다. 컴포지션 엔진은 부품을 생성하여 다른 부품의 가져오기를 충족합니다. 파트 가져오기를 설정하기 전에 해당 값을 특성을 사용하여 ImportingConstructor
필수 구성 요소로 지정하지 않는 한 파트 생성자에서 가져온 값을 사용하거나 조작하는 초기화를 수행할 수 없습니다. 이는 일반적으로 기본 설정 방법이지만 경우에 따라 생성자 주입을 사용할 수 없는 경우도 있습니다. 이러한 경우 OnImportsSatisfied
에서 초기화를 수행할 수 있으며, 파트는 IPartImportsSatisfiedNotification
을 구현해야 합니다.
.NET