Поделиться через


Введение в интерфейс класса

Интерфейс класса, не определяемый явно в управляемом коде — это интерфейс, который предоставляет доступ ко всем открытым методам, свойствам, полям и событиям, к которым предоставлен явный доступ в объекте .NET. Этот интерфейс может быть сдвоенным или интерфейсом диспетчеризации. Интерфейс классов получает имя самого класса .NET с символом подчеркивания перед ним. Например, если имя класса — Mammal, интерфейс класса получит имя _Mammal.

В случае с производными классами интерфейс класса также предоставляет доступ ко всем открытым методам, свойствам и полям базового класса. Производный класс также предоставляет доступ к интерфейсу класса для каждого базового класса. Например, если класс 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) tool generates. Если класс Mammal реализовал один или несколько интерфейсов, они будут отображены в компонентном классе.

   [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-взаимодействие создает для каждого класса, экспортируемого в библиотеку типов, интерфейс диспетчеризации. Автоматическое создание этого интерфейса можно предотвратить или изменить, применяя к классу атрибут ClassInterfaceAttribute. Хотя интерфейс класса и может упростить задачу обеспечения доступа из COM к управляемым классам, возможности его использования ограничены.

Предупреждение

Использование интерфейса класса вместо явного определения разработчиком собственного интерфейса может усложнить в будущем отслеживание версий управляемого класса.Перед началом работы с интерфейсом класса следует прочитать следующие рекомендации.

Вместо создания интерфейса класса лучше определить явный интерфейс, который могли бы использовать COM-клиенты.

Поскольку COM-взаимодействие создает интерфейс класса автоматически, изменения, вносимые в класс в следующих версиях, могут повлиять на компоновку интерфейса класса, предоставляемого средой CLR. Поскольку COM-клиенты обычно не готовы работать с изменениями в компоновке интерфейса, изменение компоновки членов класса вызовет сбой работы 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-клиенты могут получить доступ к классу LoanApp только с помощью интерфейса IExplicit.

Отказ от кэширования идентификаторов диспетчеризации (DispId).

Использование интерфейса класса является допустимым вариантом для клиентов со сценарием, клиентов Microsoft Visual Basic 6.0, а также клиентов с поздним связыванием, которые не кэширует идентификаторы DispId членов интерфейса. Идентификаторы DispId определяют члены интерфейса, разрешающие позднее связывание.

Для интерфейса класса генерация идентификаторов DispId осуществляется на основе позиции члена в интерфейсе. Изменение порядка членов и экспорт класса в библиотеку типов изменяют и идентификаторы DispId, созданные в интерфейсе класса.

Во избежание нарушений в работе COM-клиентов с поздним связыванием при использовании интерфейсов классов необходимо применить атрибут ClassInterfaceAttribute со значением ClassInterfaceType.AutoDispatch. Это значение реализует интерфейс диспетчеризации класса, но пропускает описание интерфейса в библиотеке типов. Без описания интерфейса клиенты не могут кэшировать идентификаторы 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. Чтобы вызвать метод для интерфейса, следует передать возвращенный идентификатор DispId в качестве аргумента для IDispatch.Invoke.

Ограничьте использование сдвоенного интерфейса для интерфейса класса.

Сдвоенные интерфейсы позволяют COM-клиентам выполнять раннее и позднее связывание с членами интерфейсов. В режиме разработки и при тестировании может оказаться полезным сделать интерфейс класса сдвоенным. Этот вариант также допустим и для управляемого класса (и его базовых классов), который никогда не будет изменяться. Во всех других случаях следует воздержаться от того, чтобы делать интерфейс класса сдвоенным.

Автоматически созданный сдвоенный интерфейс может оказаться полезным в довольно редких случаях, но зачастую он создает трудности при работе с версиями. Например, внесение изменений в базовый класс легко может нарушить работу COM-клиентов, использующих интерфейс производного. Если базовый класс предоставляется независимым поставщиком, компоновка интерфейса класса становится недоступной разработчику. Кроме того, сдвоенный интерфейс в отличие от ситуации с интерфейсом диспетчеризации (ClassInterface.AutoDual) предоставляет в экспортированную библиотеку типов описание интерфейса класса. Это описание стимулирует клиентов с поздним связыванием кэшировать идентификаторы DispId во время выполнения.

См. также

Ссылки

ClassInterfaceAttribute

Основные понятия

Вызываемая оболочка COM

Уточнение типов .NET для взаимодействия