Жанр: Учеба
Си шарп: создание приложений для windows
...ssAttrApp
i
public static void Main()
{
Type type - typeof(MyRemotableClass);
foreach (Attribute attr in type.GetCustomAttributes(true))
(
RemoteObjectAttribute remoteAttr = attr as RemoneObjectAttribute;
if (null != remoteAttr)
f
Console.WriteLine("Создайте этот объект на {0} . " , remoteAttr. Server);
Как можно ожидать, приложение выдаст следующее:
Создайте этот объект на COOKER.
Поскольку все вариации данного примера будут использовать общий
код, рассмотрим, что здесь происходит с точки зрения отражения и как
оно возвращает значение атрибута в период выполнения.
Первая строка в методе Main использует оператор typeof:
Type type = typeof (MyRemotableClass);
Этот оператор возвращает ассоциированный с типом объект System Type,
который передается как его единственный аргумент. Как только этот объект
оказался в вашем распоряжении, к нему можно выполнить запрос.
Относительно следующей строки в пояснении нуждаются два момента:
foreach (Attribute attr in type.GetCustomAttributes (true) )
Первый - вызов метода Type.GetCustomAttributes. Этот метод возвращает
массив значений типа Attribute, который в данном случае будет содержать
все атрибуты, прикрепленные к классу MyRemotableClass. Второй - оператор
foreach, циклически обрабатывающий возвращенный массив, помещая каждое
последовательное значение в переменную attr типа Attribute.
Следующее выражение использует оператор as, чтобы попытаться преобразовать
переменную attr в тип RemoteObjectAttribute:
RemoteObjectAttribute remoteAttr = attr as RemoteObjectAttribute;
Далее выполняется проверка на пустое значение, которое указывает
на наличие сбоя при использовании оператора as. Если значение не
пустое, значит, переменная remoteAttr содержит верный атрибут, прикрепленный
к типу MyRemotableClass - мы вызываем одно из свойств
RemoteObjectAttribute, чтобы вывести имя удаленного сервера:
if (null !=^ remoteAttr)
Атрибуты 161
Console.WriteLine{"Создайте этот объект на {0}", remoteAttr.Server);
Атрибуты поля
В качестве последнего примера запроса членов как прикрепленных к
ним атрибутов мы рассмотрим способ запроса полей класса. Допустим, наш
класс содержит поля, значения которых нужно сохранить в реестре. Для
этого можно определить атрибут с конструктором, принимающим как параметр
enum с ульями реестра, и строку, представляющую имя параметра
реестра. Затем можно выполнить запрос к полю как к разделу реестра:
using System;
using System.Reflection;
public enum RegistryHives
{
HKEYCLASSES_ROOT,
HKEYCURRENTJJSER,
HKEYLOCAL__RACHINE,
HKEY_USERS,
HKEYCURRENT__CONFIG
)
public class RegistryKeyAttribute: Attribute
{
public RegistryKeyAttribute (RegistryHives Hive, String ValueName)
{
this.Hive = Hive;
this.ValueName = ValueName;
protected RegistryHives hive
public RegistryHives Hive
"
get { return hive; }
set ( hive = value; )
}
protected String valueName;
public String ValueName
{
get { return valueName; }
set { valueName = vslue; )
class Тез-Class
{
[RegistryKey(RegistryHives.HKEYCURRENTJJ5ER, "Foo")
public int Foepublic
int Bar;
162 Раздел I I . Фундаментальные понятия
class FieldAttrApp
{
public static void Main!)
{
Type type = Type.GetType("TestClass");
foreach(Fieldlnfo field in type.GetFields())
{
foreach (Attribute attr in field.GetCustomAttributes(true))
{
RegistryKeyAttribute registryKeyAttr = attr as RegistryKeyAttribute;
if(null != registryKeyAttr)
{
Console.WriteLine("i 0} будет сохранен в {1}\\\\{2}", field.Name,
registryKeyAttr.Hive, registryKeyAttr.VaiueName);
Результат работы программы:
Foo будет сохранен в HKEYCURRENTJJSERWFoo
Этот пример в чем-то дублирует предыдущий. Однако пара деталей все
же важна для нас. Как и объект Methodlnfo, определенный для получения
информации о методе из объекта типа, объект Fieldlnfo предоставляет аналогичную
функциональность для получения из объекта сведений о поле.
Как и в предыдущем примере, мы начнем с получения объекта типа,
ассоциированного с нашим тестовым классом. Затем мы циклически обработаем
массив Fieldlnfo, а также все атрибуты каждого объекта Fieldlnfo,
пока не найдем нужный - RegistryKeyAttribute. Если мы его обнаружим, то
выведем имя поля и значения полей атрибута Hive и ValueName.
ПАРАМЕТРЫ АТРИБУТОВ
В вышеприведенном примере было рассказано об использовании прикрепленных
атрибутов с помощью их конструкторов. А теперь рассмотрим
некоторые вопросы, связанные с конструкторами атрибутов, о которых
раньше не упоминалось.
Типы параметров
В примере FieldAttrApp вы могли видеть атрибут с именем RegistryKeyAttribute.
Его конструктор имел такой вид:
public RegistryKeyAttribute(RegistryHives Hive, String ValueName)
Далее к полю был прикреплен атрибут на основе сигнатуры этого
конструктора:
[RegistryKey(RegistryHives.HKEYCURENT_USER, "Foo")]
public int Foo;
Пока все просто. У конструктора есть два параметра, и два параметра
использовались, чтобы прикрепить этот атрибут к полю. Однако мы
можем упростить код. Если параметр большую часть времени останется
неизменным, зачем каждый раз заставлять пользователя класса типизировать
его? Мы можем установить значения по умолчанию, применив
позиционные и именованные параметры.
Позиционными называются параметры конструктора атрибута. Они
обязательны и должны задаваться каждый раз при использовании атрибута.
В нашем примере ReglstryKeyAttribute позиционными являются оба
параметра, Hive и ValueName. Именованные параметры на самом деле не
определяются в конструкторе атрибута. Они скорее представляют собой
нестатические поля и свойства. Поэтому именованные параметры позволяют
клиенту устанавливать значения полей и свойств атрибута при
создании его экземпляра, не требуя от вас создания конструктора для
каждой возможной комбинации полей и свойств, значения которых может
понадобиться установить клиенту.
Каждый открытый конструктор может определять последовательность
позиционных параметров. Это верно и в отношении любого типа класса.
Но в случае атрибутов после указания позиционных параметров пользователь
может ссылаться на некоторые поля или свойства, применяя синтаксис
Имя_поля_или_свойства=3начение.
Чтобы проиллюстрировать это, изменим атрибут RegistryKeyAttribute.
Мы создадим лишь один позиционный параметр RegistryKeyAttribute.ValueName,
а необязательным именованным параметром будет RegistryKeyAttribute.Hive.
Итак, возникает вопрос: "Как определить что-либо как
именованный параметр?". Поскольку в определение конструктора включены
только позиционные и поэтому необходимые параметры, просто
удалите параметр из определения конструктора. Впоследствии пользователь
может указывать как именованный параметр любое поле, не
являющееся readonly, static или const, или любое поле, у которого есть
метод-аксессор для установки его значения, или установщик, который
не является статическим. Поэтому, чтобы сделать RegistryKeyAttribute.Hive
именованным параметром, мы уберем его из определения конструктора,
так как он уже существует в виде открытого свойства, доступного
для чтения и записи:
public RegistryKeyAttribute(String ValueName)
Теперь пользователь может прикрепить атрибут любым из следующих
способов:
[RegistryKey("Foo")]
[RegistryKey("Foo", Hive = RegistryHives.HKEYLOCAL_MACHINE)]
Это дает гибкость, обеспечиваемую наличием у поля значения по
умолчанию, в то же время предоставляя пользователю возможность изменять
это значение при необходимости. Но если пользователь не устанавливает
значения поля RegistryKeyAttribute.Hive, как мы зафиксируем для
него значение по умолчанию? Вы можете подумать: "Хорошо, посмот164
Раздел I I . Фундаментальные понятия
рим, не установлено ли оно в конструкторе". Однако проблема в том,
что RegistryKeyAttribute.Hive - это enum, в основе которого лежит int - размерный
тип. Это значит, что по умолчанию компилятор инициализирует
его значением 0! Если мы изучим значение RegistryKeyAttribute.Hive в
конструкторе и найдем его равным 0, то не сможем узнать, установлено
ли оно вызывающим кодом через именованный параметр или инициализировано
компилятором как размерный тип. К сожалению, единственный
способ решения проблемы -это изменить код так, чтобы значение,
равное 0, стало неверным. Это можно сделать, модифицировав
Registry Hives enum:
public enum RegistryHives
(
HKEYCLASSES_ROOT =1,
HKEYCURRENTJJSER, HKEYLOCAL_MACHINE,
HKEY_USERS,
HKEYCURRENT_CONFIG
}
Теперь мы знаем, что единственный способ, позволяющий RegistryKeyAttribute.Hive
быть равным 0,- инициализация его компилятором этим
значением, если после пользователь не изменил его значение через именованный
параметр. Сейчас мы можем написать для инициализации
примерно такой код:
public RegistryKeyAttribute( String ValueName)
{
if (this.Hive == 0 )
this. Hive = RegistryHives .HKEYCURRENT__USER;
this.ValueName = ValueName;
]
Используя именованные параметры, вы должны указать сначала позиционные
параметры. После этого именованные параметры можно перечислить
в любом порядке, так как перед ними идет имя поля или свойства.
При компиляции приведенного ниже примера возникнет ошибка
компилятора:
// Это ошибочный код, поскольку позиционные параметры не могут
II стоять после именованных параметров.
[RegistryKey (Hive=RegistryHives .HKEYLOCAL__MACHIHE, "Foo") ]
Кроме того, вы не можете именовать позиционные параметры. При
компиляции атрибутов компилятор сначала попытается разрешить именованные
параметры, затем разрешить оставшиеся именованные параметры
с помощью сигнатуры метода. Хотя компилятор сможет разрешить
каждый именованный параметр, следующий код не будет компилироваться,
так как по завершении разрешения именованных параметров
компилятор не найдет ни одного позиционного параметра и выдаст сообщение
"No overload for method 'RegistryKeyAttribute' takes "0" arguments
":
[RegistryKey(ValueName="Foo", Hive = RegistryHives.HKEYLOCAL MACHINE)]
Атрибуты 165
Наконец, именованными параметрами могут быть любые открытые
поля или свойства, включая метод-установщик, не определенные как static
или const.
Типы позиционных и именованных параметров класса атрибута ограничены
следующим набором:
• bool, byte, char, double, float, int, long, short, string;
• System.Type;
• object;
• enum - при условии, что он и все типы, по отношению к которым он
является вложенным, открытые (как в примере, где используется перечисление
ульев реестра);
• одномерный массив значений любого из вышеуказанных типов.
Поскольку набор типов параметров ограничен приведенным выше
списком, вы не можете передавать конструктору структуры данных наподобие
класса. Это ограничение имеет смысл, так как атрибуты прикрепляются
в период разработки и в это время у вас не будет созданного
экземпляра класса (объекта). Допускается применение вышеуказанных
типов, так как они позволяют жестко запрограммировать их значения во
время разработки.
ТИПЫ АТРИБУТОВ
Кроме пользовательских параметров, которые вы задаете для аннотации
обычных типов С#, с помощью атрибута Attribute Usage можно определить
способ применения этих атрибутов. Согласно документации правила
вызова атрибута AttributeUsage таковы;
[AttributeUsage (
validon,
AIlowMultiple = allowmultiple,
Inherited - Inherited
)]
Как видите, позиционные параметры легко отличить от именованных.
Настоятельно рекомендуется так документировать ваши атрибуты,
чтобы у их пользователя не возникало необходимости обращаться к исходному
коду класса атрибута для поиска открытых полей, доступных
для чтения и записи, которые могут применяться как именованные атрибуты.
Определение целевого типа атрибута
А сейчас снова взгляните на атрибут AttributeUsage из предыдущего
раздела. Заметьте: параметр validon является позиционным и, естественно,
обязательным. Он позволяет задавать типы, к которым может быть
прикреплен атрибут. На самом деле тип параметра validon атрибута
166 Раздел I I . Фундаментальные понятия
AttributeUsage - AttributeTargets, представляющий собой перечисление, определяемое
так:
public enum AttributeTargets
{
Assembly = 0x0001,
Module - 0x0002,
Class = 0x0004,
Struct = 0x0008,
Enum - 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x0200,
Interface = 0x0400,
Parameter - 0x0800,
Delegate =- 0x1000,
All = Assembly [ Module I Class I Struct I Enun I Constructor I
Method I Property I Field I Event I Interface I Parameter I
Delegate,
ClassMembers = Class | Struct | Enum | Constructor | Method ! Property | Field
1 Event I Delegate | Interface;
}
При использовании атрибута AttributeUsage можно заявить: AttributeTargets.AII.
Это позволяет прикрепить атрибут к любому из типов в списке
перечисления AttributeTargets. Если вы вообще не определили AttributeUsage,
можете прикрепить атрибут к любому типу -это значение по умолчанию.
И тут вы можете спросить: "А зачем вообще значение validon?" А затем,
что с вашим атрибутом могут применяться именованные параметры, которые
вы, может быть, захотите изменить. Помните: используя именованный
параметр, вы должны поставить все позиционные параметры перед
ним. Это позволяет легко задавать применение атрибутов по умолчанию,
определяемое AttributeTargets.AII, и при этом устанавливать именованные
параметры.
Итак, когда и для чего задавать параметр validon (AtributeTargets)? Когда
вам нужно точно контролировать способы применения атрибута. В наших
примерах мы создали атрибут RemoteObjectAttribute, применимый
только к классам, атрибут TransactionableAttribute, применимый только к
методам, и атрибут RegistryKeyAttribute, который имеет смысл использовать
лишь по отношению к полям. Если мы хотим убедиться, что они
были использованы для аннотации только тех типов, для которых разработаны,
то можем определить их так (для ясности тела атрибутов не
приводятся):
[AttributeUsage(AttributeTargets.Class)]
[public class RemoteObjectAttribute: Attribute]
Атрибуты 167
[AttributeUsage {AttributeTargets.Method)j
[public class TransactionableAttribute: Attribute]
iAttributeUsage (AttributeTargets.Field) ]
[public class RegistryKeyAttribute: Attribute]
{ ... )
И последний момент относительно перечисления AttributeTargets. Вы
можете комбинировать члены с помощью оператора !. Если у вас есть
атрибут, применимый и к полям, и к свойствам, AttributeUsage можно
прикрепить так:
[AttributeUsage (AttributeTargets.Field | AttributeTargets.Property) ]
Атрибуты однократного и многократного использования
AttributeUsage позволяет задать атрибуты для одно- и многократного
применения. Это определит, как много раз один атрибут может быть
задействован с одним полем. По умолчанию все атрибуты используются
однократно. Это означает, что при компиляции следующего кода возникнет
ошибка компилятора:
using System;
using System.Reflection;
public class SingleUseAttribute: Attribute
{
public SinglellseAttrlbute (String str)
// ОШИБКА: возникает ошибка компилятора "duplicate attribute"
[SingleUse("abc")]
[SingleUse("def")]
class MyClass
class SingleUseApp
i
public static void Main ()
Чтобы исправить эту ошибку, укажите в строке AttributeUsage, что хотите
разрешить многократное использование атрибута с данным типом.
Такой код будет работать:
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.All, AllowMultiple - true)!
public class SingleUseAttribute: Attribute
168 Раздел I I . Фундаментальные понятия
public SinglellseAttribute(String str)
[SingleUse("abc")1
[SingleUse("def")]
class MyClass
class SingleUseApp
{
public static void Main f)
Практическим примером использования этого подхода служит атрибут
RegistryKeyAttribute (см. подраздел "Определение атрибутов"). Поскольку
вполне реально, что поле может быть сохранено в нескольких местах
реестра, попробуйте прикрепить атрибут Attribute Usage с именованным параметром
AllowMultiple, как показано в примере выше.
Наследование атрибутов
Последний параметр атрибута AttributeUsage - флаг inherited, определяет,
может ли атрибут наследоваться. Его значение по умолчанию равно
false. Если же установить флаг inherited в true, его значение зависит от
значения флага AllowMultiple. Если inherited находится в true, a AllowMultiple-
в false, установленный атрибут заменит унаследованный. Однако
если и inherited, и AllowMultiple установлены в true, атрибуты члена будут
аккумулироваться, то есть собираться вместе.
ИДЕНТИФИКАТОРЫ АТРИБУТОВ
Взгляните на следующий код и попробуйте определить, что аннотирует
атрибут - возвращаемое значение или метод:
class MyClass
{
[HRESULT]
public long Fool);
1
Если у вас есть опыт работы с СОМ, вы должны знать, что HRESULT -
это стандартный возвращаемый тип для всех методов, кроме AddRef или
Атрибуты 169
Release. Однако нетрудно заметить, что если имя атрибута применимо и
к возвращаемому значению, и к имени метода, то компилятору будут
непонятны ваши намерения. Вот несколько сценариев, в которых компилятор
не поймет ваших намерений из контекста:
• метод или возвращаемый тип;
• событие, поле или свойство;
• делегат или возвращаемый тип;
• свойство, аксессор, возвращаемое значение метода-получателя или
параметр значения установщика.
В каждом из этих случаев компилятор производит определение на
основе того, что считается "наиболее употребительным". Чтобы обойти
такой путь принятия решения, используйте идентификаторы атрибута:
assembly, module, type, method, property, event, field, param, return.
Чтобы воспользоваться идентификатором атрибута, поставьте перед
именем атрибута идентификатор и двоеточие. В примере MyClass, чтобы
быть уверенным в том, что компилятор сможет определить HRESULT как
атрибут, аннотирующий возвращаемое значение, а не как метод, вы должны
задать его следующим образом:
class MyClass
{
[return: HRESULT]
public long Foo( );
15. ИНТЕРФЕЙСЫ
Ключ к пониманию интерфейсов лежит в их сравнении с классами.
Классы - это объекты, обладающие свойствами и методами, которые на
эти свойства воздействуют. Хотя классы проявляют некоторые характеристики,
связанные с поведением (методы), они представляют собой предметы,
а не действия, присущие интерфейсам. Интерфейсы же позволяют
определять характеристики или возможности действий и применять их к
классам независимо от иерархии последних. Допустим, у вас есть дистрибьюторское
приложение, составляющие которого можно упорядочить.
Среди них могут быть классы Customer, Supplier и Invoice. Некоторые другие,
скажем, MaintenanceView или Document, упорядочивать не надо. Как
упорядочить только выбранные вами классы? Очевидный способ - создать
базовый класс с именем типа Serializable. Но у этого подхода есть большой
минус: одна ветвь наследования здесь не подходит, так как нам не требуется
наследование всех особенностей поведения. С# не поддерживает
множественное наследование, так что невозможно произвести данный
класс от нескольких классов. А вот интерфейсы позволяют определять
набор семантически связанных методов и свойств, способных реализовать
избранные классы независимо от их иерархии.
Концептуально интерфейсы представляют собой связки между двумя
в корне отличными частями кода. Иначе говоря, при наличии интерфейса
и класса, определенного как реализующий данный интерфейс, клиентам
класса дается гарантия, что у класса реализованы все методы, определенные
в интерфейсе. Скоро вы это поймете на примерах.
Прочитав данную главу, вы поймете, почему интерфейсы являются
такой важной частью С# в частности и программирования на основе
компонентов вообще. Затем мы познакомимся с объявлением и реализацией
интерфейсов в приложениях на С#. В завершение мы углубимся в
специфику использования интерфейсов и преодоления врожденных проблем
с множественным наследованием и конфликтами имен.
Когда вы создаете интерфейс и в определении класса задаете его использование,
говорят, что класс реализует интерфейс. Интерфейсы - это набор
характеристик поведения, а класс определяется как реализующий их.
ИСПОЛЬЗОВАНИЕ ИНТЕРФЕЙСОВ
Чтобы понять, где интерфейсы приносят пользу, рассмотрим традиционную
проблему программирования в Windows, когда нужно обеспечить
универсальное взаимодействие двух совершенно различных фрагментов
Интерфейсы 171
кода без использования интерфейсов. Представьте себе, что вы работаете
на Microsoft и являетесь ведущим программистом команды по разработке
панели управления. Вам надо предоставить универсальные средства,
которые дают возможность клиентским апплетам "закрепляться" на панели
управления, показывая при этом свой значок и позволяя клиентам
выполнять их. Если учесть, что эта функциональность разрабатывалась
до появления СОМ, возникает вопрос: как создать средства интеграции
любых будущих приложений с панелью управления? Задуманное решение
долгие годы было стандартной частью разработки Windows.
Как ведущий программист по разработке панели управления, вы
создаете и документируете функцию (функции), которая должна быть реализована
в клиентском приложении, и некоторые правила. В случае апплетов
панели управления, Microsoft определила, что для их написания
вам нужно создать динамически подключаемую библиотеку, которая реализует
и экспортирует функцию CPLApplet. Вам также потребуется добавить
к имени этой DLL расширение .cpl и поместить ее в папку Windows
System32 (для Windows ME или Windows 98 это будет Windows\System32,
а для Windows 2000 - WINNT\System32). При загрузке панель управления
загружает все DLL с расширением .cpl из папки System32 (с помощью функции
LoadLibrary), а затем вызывает функцию GetProcAddress для загрузки
функции CPLApplet, проверяя таким образом выполнение вами соответствующих
правил и возможность корректного взаимодействия с панелью
управления.
Как уже говорилось, эта стандартная модель программирования в
Windows позволяет выйти из ситуации, когда вы хотите, чтобы ваш код
универсальным образом взаимодействовал с кодом, который будет разработан
в будущем. Однако это не самое элегантное решение. Главный недостаток
этой методики в том, что она вынуждает включать в состав клиента -
в данном случае в код панели управления - большие порции проверяющего
кода. Например, панель управления не может просто полагаться на допущение,
что каждый .cpl-файл в папке является DLL Windows. Панель
управления также должна проверить наличие в этой DLL функций коррекции
и что эти функции делают именно то, что описано в документации.
Здесь-то интерфейсы и вступают в дело. Интерфейсы позволяют создавать
такие же средства, связывающие несовместимые фрагменты кода, но при
этом они более объектно-ориентированны и гибки. Кроме того, поскольку
интерфейсы являются частью языка С#, компилятор гарантирует, что если
класс определен как реализующий данный интерфейс, то он выполняет
именно те действия, о которых заявляет, что должен их выполнять.
В С# интерфейс - понятие первостепенной важности, объявляющее
ссылочный тип, который включает только объявления методов. Но что
значит "понятие первостепенной важности"? Необходимо сказать, что эта
встроенная функция является неотъемлемой частью языка. Иначе говоря,
это не то, что было добавлено позже, после того как разработка языка
была закончена. Давайте подробнее познакомимся с интерфейсами, узнаем,
что они собой представляют и как их объявлять.
172 Раздел I I . Фундаментальные понятия
ОБЪЯВЛЕНИЕ ИНТЕРФЕЙСОВ
Интерфейсы могут содержать методы, свойства, индексаторы и события,
но ни одна из этих сущностей не реализуется в самом интерфейсе.
Рассмотрим их применение на примере. Допустим, вы создаете для вашей
компании редактор, содержащий элементы управления Windows. Вы
пишете редактор и тестовые программы для проверки элементов управления,
размещаемых пользователями на форме редактора. Остальная
часть команды пишет элементы управления, которыми будет заполнена
форма. Вам почти наверняка понадобятся средства проверки на уровне
формы. В определенное время, скажем, когда пользователь явно приказывает
форме проверить все элементы управления или при обработке
формы, последняя циклически проверяет все прикрепленные к ней элементы
управления, или, что более подходяще, заставляет элемент управления
проверить самого себя.
Как предоставить такую возможность проверки элемента управления?
Именно здесь проявляется превосходство интерфейсов. Вот пример простого
интерфейса с единственным методом Validate:
interface IValidate
{
bool Validate() ;
}
Теперь вы можете задокументировать тот факт, что если элемент управления
реализует интерфейс IValidate, то этот элемент управления может
быть проверен.
Рассмотрим пару аспектов, связанных с приведенным кодом. Вы не
должны задавать для метода интерфейса модификатор доступа, такой
как public. При указании модификатора доступа перед объявлением метода
возникает ошибка периода компиляции. Дело в том, что все методы
интерфейса открыты по умолчанию (разработчики на C++ могут также
заметить, что, поскольку интерфейсы по определению - это абстрактные
классы, не требуется явно объявлять метод как чисто виртуальный (pure
virtual), прибавляя "= 0" к его определению).
Кроме методов, интерфейсы могут определять свойства, индексаторы
и события:
interface IExampleInterface
{
// Пример объявления свойства. .
int testProperty { get; }
// Пример объявления события,
event testEvent Changed;
// Пример объявления индексатора
string this[int index] { get; set; }
Интерфейсы 173
СОЗДАНИЕ ИНТЕРФЕЙСОВ
Поскольку интерфейс определяет связь между фрагментами кода, любой
класс, реализующий интерфейс, должен определять любой и каждый
элемент этого интерфейса, иначе код не будет компилироваться. Используя
IValidate из нашего примера, клиентский класс должен реализовать
лишь методы интерфейса. В следующем примере есть базовый класс
FancyControl и интерфейс IValidate. Кроме того, в нем имеется класс MyControi,
производный от FancyControl, реализующий интерфейс IValidate. Обратите
внимание на синтаксис и способ приведения объекта MyControi к интерфейсу
IValidate для ссылки на его члены.
using System;
public class FancyControl
{
protected string Data;
public string data
{
get
{
retur
...Закладка в соц.сетях