類別介面簡介
沒有在 Managed 程式碼中明確定義的類別介面,是一種會公開所有在 .NET 物件上明確公開之公用方法、屬性、欄位和事件的介面。這個介面可以是雙重介面 (Dual Interface),也可以是分派 (Dispatch-only) 介面。類別介面採用了 .NET 類別本身的名稱,前面再加上一個底線。例如,對於類別 Mammal,其類別介面為 _Mammal。
對於衍生類別 (Derived Class),類別介面也會公開基底類別的所有公用方法、屬性和欄位。衍生類別也會公開每一基底類別的類別介面。舉例來講,如果類別 Mammal 擴充了類別 MammalSuperclass,而它本身又擴充了 System.Object,那麼 .NET 物件會向 COM 用戶端公開三個類別介面,分別為 _Mammal、_MammalSuperclass 和 _Object。
例如,請考量下列 .NET 類別:
' Applies the ClassInterfaceAttribute to set the interface to dual.
<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
Sub Eat()
Sub Breathe()
Sub Sleep()
End Class
// Applies the ClassInterfaceAttribute to set the interface to dual.
[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
void Eat();
void Breathe():
void Sleep();
}
COM 用戶端可以取得名為 _Mammal
的類別介面指標,這個指標在型別程式庫匯出工具 (Tlbexp.exe) 所產生的型別程式庫中有相關描述。如果 Mammal
類別實作了一個或多個介面,那麼這些介面將會出現在 Coclass 之下。
[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
[id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
pRetVal);
[id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
VARIANT_BOOL* pRetVal);
[id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
[id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
[id(0x6002000d)] HRESULT Eat();
[id(0x6002000e)] HRESULT Breathe();
[id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
[default] interface _Mammal;
}
產生類別介面是選擇性的。根據預設,COM Interop 會對您匯出到型別程式庫的每一類別,產生一個唯分派介面。您可以將 ClassInterfaceAttribute 套用到您的類別,來防止或修改這個介面的自動建立。雖然類別介面可以減輕向 COM 公開 Managed 類別的工作,不過它的運用也很有限。
警告
使用類別介面 (非自行明確定義),可能會讓將來 Managed 類別的版本控制變得很複雜。使用類別介面前,請先閱讀以下指導原則。
定義供 COM 用戶端使用的明確介面,而不產生類別介面。
因為 COM Interop 會自動產生類別介面,所以在類別版本確定之後的變更,可能會改變由 Common Language Runtime 所公開之類別介面的配置。由於 COM 用戶端通常並未預期要處理介面配置的變更,因此,如果您變更類別的成員配置,它們可能會中斷。
這項指導原則在強調一個概念,向 COM 用戶端公開的介面必須維持不變。為了降低因無意間重新安排介面配置而造成 COM 用戶端中斷的風險,請明確定義介面,將所有對類別的變更隔離於介面配置之外。
使用 ClassInterfaceAttribute 來解除類別介面的自動產生,並且實作這個類別的明確介面,如以下程式碼片段所示:
<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp
Implements IExplicit
Sub M() Implements IExplicit.M
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit {
void M();
}
ClassInterfaceType.None 值會防止在類別中繼資料匯出至型別程式庫時產生類別介面。在前面的範例中,COM 用戶端只透過 IExplicit
介面存取 LoanApp
類別。
避免快取分派識別項 (DispId)。
對於指令化的用戶端、Microsoft Visual Basic 6.0 用戶端或任何不快取介面成員之 DispId 的晚期繫結用戶端,使用類別介面是可以接受的選項。DispId 可以識別介面成員以啟用晚期繫結。
對於類別介面,DispId 的產生是依據介面中成員的位置。如果您變更了成員的順序並且將這個類別匯出至型別程式庫,您將會改變在類別介面中產生的 DispId。
若要避免在使用類別介面時中斷晚期繫結 COM 用戶端,請以 ClassInterfaceType.AutoDispatch 值套用 ClassInterfaceAttribute。這個值會實作唯分派類別介面,但會從型別程式庫略去介面描述。沒有介面描述,用戶端就不能在編譯期間快取 DispId。雖然這是類別介面的預設介面型別,但是您可以明確地套用這個屬性值。
<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp
Implements IAnother
Sub M() Implements IAnother.M
…
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch]
public class LoanApp : IAnother {
void M();
}
若要在執行階段時取得介面成員的 DispId,COM 用戶端可以呼叫 IDispatch.GetIdsOfNames。若要在介面上叫用 (Invoke) 方法,請將傳回的 DispId 當做 IDispatch.Invoke 的引數傳遞。
對類別介面限制使用雙重介面選項。
雙重介面可以啟用由 COM 用戶端早期和晚期繫結至介面成員。在設計階段和測試當中,您可能會發現將類別介面設定為雙重介面很有用處。對於永遠不會被修改的 Managed 類別 (及其基底類別),這個選項也是可以接受的。在其他的所有情況中,則應避免將類別介面設定為雙重介面。
自動產生的雙重介面在一些極少見的情況中可能很適合;但是,它也時常會產生版本相關的複雜性。例如,使用衍生類別之類別介面的 COM 用戶端,可能很容易在基底類別變更時中斷。當第三者提供了基底類別時,類別介面的配置就不是您所能控制的了。此外,與唯分派介面的不同之處在於,雙重介面 (ClassInterface.AutoDual) 會在匯出的型別程式庫中提供類別介面的描述。這種描述有助於晚期繫結用戶端在 Run Time 時快取 DispId。