Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы ОСиСП.doc
Скачиваний:
71
Добавлен:
11.05.2015
Размер:
1.78 Mб
Скачать

Этап 4: определение метода, транслирующего входную информацию в желаемое событие

У класса должен быть метод, принимающий некоторую входную информацию и преобразующий его в генерацию события. В примере MailManager метод SimulateNewMail вызывается для оповещения о прибытии нового сообщения в MailManager.

internal class MailManager

{

// Этап 4: определение метода, транслирующего входную

// информацию в желаемое событие.

public void SimulateNewMail(String from, String to, String subject)

{

// Создать объект для хранения информации, которую

// нужно передать получателям уведомления.

NewMailEventArgs е = new NewMailEventArgs(from, to, subject);

// Вызвать виртуальный метод, уведомляющий объект о событии.

// Если ни один из производных типов не переопределяет этот метод,

// объект уведомит всех зарегистрированных получателей уведомления.

OnNewMail(e);

}

}

Метод SimulateNewMail принимает информацию о сообщении и создает новый объект NewMailEventArgs, передавая его конструктору данные сообщения. Затем вызывается OnNewMail, собственный виртуальный метод объекта MailManager, чтобы формально уведомить объект MailManager о новом почтовом сообщении. Обычно это вызывает генерацию события, в результате уведомляются все зарегистрированные объекты. (Как сказано выше, тип, производный от MailManager, может переопределять это действие.)

Как реализуются события

Научившись определять класс с членом-событием, пора поближе познакомиться с самим событием и узнать, как оно работает. В классе MailManager есть строчка кода, определяющая сам член-событие:

public event EventHandler<NewMailEventArgs> NewMail;

При компиляции этой строчки компилятор превращает ее в следующие три конструкции:

// 1. ЗАКРЫТОЕ поле делегата, инициализированное null.

private EventHandler<NewMailEventArgs> NewMail = null;

// 2. ОТКРЫТЫЙ метод add_Xxx (где Xxx - это имя события).

// Позволяет объектам регистрироваться для получения уведомлений о событии.

[MethodImpl(MethodImplOptions.Synchronized)]

public void add_NewMail(EventHandler<NewMailEventArgs> value)

{

NewMail = (EventHandler<NewMailEventArgs>)Delegate.Combine(NewMail, value);

}

// 3. ОТКРЫТЫЙ метод remove_Xxx (где Ххх - это имя события).

// Позволяет объектам отменять регистрацию для получения уведомлений о событии

[MethodImpl(MethodImplOptions.Synchronized)]

public void remove_NewMail(EventHandler<NewMailEventArgs> value)

{

NewMail = (EventHandler<NewMailEventArgs>)Delegate.Remove(NewMail, value);

}

Первая конструкция — просто поле соответствующего типа делегата. Оно содержит ссылку на заголовок списка делегатов, которые будут уведомляться при возникновении события. Поле инициализируется значением null, что означает, что нет получателей, ожидающих уведомления о событии. Когда метод регистрирует получателя уведомления, это поле ссылается на экземпляр делегата EventHandler<NewMailEventArgs>, который может в свою очередь ссылаться на дополнительные делегаты EventHandler<NewMailEventArgs>. Когда получатель регистрируется для получения уведомления о событии, он просто добавляет в список экземпляр типа делегата. Ясно, что отказ от регистрации означает удаление соответствующего делегата.

Обратите внимание: в примере поле делегата, NewMail, всегда закрытое, несмотря на то, что исходная строка кода определяет событие как открытое. Причина в предотвращении доступа из кода, не относящегося к определяющему классу. Если бы поле было открытым, любой код мог бы изменить значение поля, в том числе удалить все делегаты, подписавшиеся на событие.

Вторая создаваемая компилятором C# конструкция — метод, позволяющий другим объектам регистрироваться на получение уведомления о событии. КомпиляторC# автоматически присваивает этой функции имя, добавляя приставку add_ к имени события (NewMail). КомпиляторC# также автоматически генерирует код метода, который всегда вызывает статический метод Combine типа System.Delegate. Метод Combine добавляет в список делегатов новый экземпляр и возвращает новый заголовок списка, который снова сохраняется в поле.

Третья и последняя создаваемая компилятором C# конструкция представляет собой метод, позволяющий объекту отказаться от подписки на событие. И этой функции компилятор С# присваивает имя автоматически, добавляя приставку remove_ к имени события (NewMail). Код метода всегда вызывает метод Remove типа System.Delegate. Последний метод удаляет делегат из списка и возвращает новый заголовок списка, который сохраняется в поле.

Заметим также, что оба метода — add и remove — помечены атрибутом MethodImplAttribute (определенным в пространстве имен SystemRuntime.CompilerServices).

Точнее, эти методы помечены как синхронизированные, поэтому они не нарушают безопасность потоков: множество объектов, следящих за событием, может регистрироваться или отменять свою регистрацию одновременно, не нарушая целостность связного списка.

В этом примере методы add и remove являются открытыми, поскольку в соответствующей строке исходного кода событие изначально объявлено как открытое. Если бы оно было объявлено как закрытое, то add и remove, сгенерированные компилятором, тоже были бы объявлены как закрытые. Так что, когда в типе определяется событие, модификатор доступа события определяет, какой код способен регистрироваться и отменять регистрацию для уведомления о событии, но прямым доступом к полю делегата обладает только сам тип. Члены-события также могут объявляться статическими и виртуальными; в этом случае сгенерированные компилятором методы add и remove также будут статическими или виртуальными соответственно.

Помимо генерации этих трех конструкций, компиляторы также генерируют запись с определением события и помещают ее в метаданные управляемого модуля. Эта запись содержит ряд флагов и базовый тип-делегат, а также ссылки на методы-аксессоры add и remove. Эта информация нужна просто для того, чтобы очертить связь между абстрактным понятием «событие» и его методами-аксессорами. Эти метаданные могут использовать компиляторы и другие инструменты, и, конечно же, эти сведения можно получить при помощи класса SystemReflec-tionEventlnfo. Однако сама CLR не использует эти метаданные и во время выполнения требует лишь методы-аксессоры.