Виртуальные, запечатанные, переопределяющие и абстрактные методы доступа

Объявление свойства virtual указывает, что методы доступа свойства являются виртуальными. Модификатор virtual применяется к обоим методам доступа свойства для чтения и записи. Объявление в качестве виртуального только одного метода доступа такого свойства не допускается

Объявление свойства abstract указывает, что методы доступа свойства являются виртуальными, однако не предоставляет их фактической реализации. Вместо этого необходимо определить неабстрактные производные классы, предоставляющие собственные реализации методов доступа посредством переопределения свойства. Поскольку метод доступа для объявления абстрактного свойства не предоставляет фактической реализации, тело метода доступа содержит только точку с запятой.

Объявление свойства, содержащее одновременно модификаторы abstract и override, указывает, что свойство является абстрактным и переопределяет базовое свойство. Методы доступа такого свойства также являются абстрактными.

Объявления абстрактных свойств допускаются только в абстрактных классах (§10.1.1.1). Методы доступа унаследованного виртуального свойства можно переопределить в производном классе, включив объявление свойства, задающее директиву override. Этот способ называется переопределяющим объявлением свойства. Переопределяющее объявление свойства не объявляет новое свойство. Вместо этого оно уточняет реализацию методов доступа существующего виртуального свойства.

Переопределяющее объявление свойства должно задавать в точности такие же модификаторы доступа, тип и имя, что и у унаследованного свойства. Если унаследованное свойство содержит только один метод доступа (то есть является свойством, доступным только для чтения или только для записи), переопределяющее свойство должно включать только этот метод доступа. Если унаследованное свойство содержит оба метода доступа (является свойством, доступным на чтение и запись), переопределяющее свойство может включать один или оба метода доступа.

Объявление переопределяющего свойства может содержать модификатор sealed. Использование этого модификатора позволяет предотвратить последующее переопределение свойства в производных классах. Методы доступа запечатанного свойства также являются запечатанными.

За исключением различий в синтаксисе объявления и вызова, поведение виртуальных, запечатанных, переопределяющих или абстрактных методов доступа в точности соответствует поведению виртуальных, запечатанных, переопределяющих или абстрактных методов. В частности, правила, описанные в разделах §10.6.3, §10.6.4, §10.6.5 и §10.6.6, применяются, как если бы методы доступа были методами соответствующего вида.

· Метод доступа get соответствует не имеющему параметров методу, имеющему возвращаемое значение типа свойства и те же модификаторы, что и содержащее свойство.

· Метод доступа set соответствует методу с типом возвращаемого значения void, одним параметром значения, имеющим тип свойства, и имеющему те же модификаторы, что и содержащее свойство.

В этом примере

abstract class A
{
int y;

public virtual int X {
get { return 0; }
}

public virtual int Y {
get { return y; }
set { y = value; }
}

public abstract int Z { get; set; }
}

X — это виртуально свойство только для чтения, Y — это виртуальное свойство только для записи, а Z — это абстрактное свойство для чтения и записи. Поскольку свойство Z является абстрактным, содержащий его класс A также должен быть объявлен как абстрактный.

В следующем примере показан класс, производный от класса A.

class B: A
{
int z;

public override int X {
get { return base.X + 1; }
}

public override int Y {
set { base.Y = value < 0? 0: value; }
}

public override int Z {
get { return z; }
set { z = value; }
}
}

В этом примере объявления свойств X, Y и Z представляют собой переопределяющие объявления свойств. Каждое объявление свойства точно соответствует модификаторам доступа, типу и имени соответствующего наследуемого свойства. Метод доступа get свойства X и метод доступа set свойства Y используют ключевое слово base для обращения к наследуемым методам доступа. Объявление свойства Z переопределяет оба абстрактных метода доступа. Таким образом, в классе B отсутствуют необработанные члены абстрактной функции, в связи с чем класс B может быть неабстрактным классом.

Если свойство объявлено с помощью модификатора override, все переопределенные методы доступа должны быть доступны переопределяющему коду. Кроме того, объявленные уровни доступа для самого свойства или индексатора и его методов доступа должны соответствовать уровням доступа переопределенного члена и его методов доступа. Пример:

public class B
{
public virtual int P {
protected set {...}
get {...}
}
}

public class D: B
{
public override int P {
protected set {...} // Must specify protected here
get {...} // Must not have a modifier here
}
}

События

Событие — это член, используемый классом или объектом для уведомления. Клиенты могут присоединять к событиям исполняемый код с помощью обработчиков событий.

События объявляются с помощью объявлений событий:

event-declaration:
attributesopt event-modifiersopt event type variable-declarators ;
attributesopt event-modifiersopt event type member-name { event-accessor-declarations }

event-modifiers:
event-modifier
event-modifiers event-modifier

event-modifier:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

event-accessor-declarations:
add-accessor-declaration remove-accessor-declaration
remove-accessor-declaration add-accessor-declaration

add-accessor-declaration:
attributesopt add block

remove-accessor-declaration:
attributesopt remove block

Объявление события может включать набор атрибутов (§17) допустимое сочетание любых из четырех модификаторов доступа (§10.3.5), а также модификаторы new (§10.3.4), static (§10.6.2), virtual (§10.6.3), override (§10.6.4), sealed (§10.6.5), abstract (§10.6.6) и extern (§10.6.7).

В отношении использования сочетаний модификаторов объявления событий подчиняются тем же правилам, что и объявления методов (§10.6).

Тип объявления события должен быть типом делегата (§4.2), и этот тип делегата должен иметь уровень доступности не меньший, чем у самого события (§3.5.4).

Объявление события может включать объявления методов доступа к событиям. Однако если такие объявления отсутствуют, они автоматически предоставляются компилятором для событий, не являющихся абстрактными или внешними (§10.8.1). Для внешних событий методы доступа предоставляются извне.

Объявление события, в котором опущены объявления методов доступа к событиям, определяет одно или несколько событий — по одному для каждого декларатора переменной. Атрибуты и модификаторы применяются ко всем членам, объявленным с помощью такого объявления события.

Если объявление события содержит одновременно модификатор abstract и заключенные в фигурные скобки объявления методов доступа событий, возникает ошибка времени компиляции.

Если объявление события содержит модификатор extern, событие является внешним событием. Поскольку объявления внешних событий не предоставляют фактической реализации, при наличии одновременно модификатора extern и объявлений методов доступа событий возникает ошибка.

Если декларатор переменной объявления события с модификатором abstract или external содержит инициализатор переменной, возникает ошибка времени компиляции.

Событие может использоваться в качестве левого операнда в операторах += и -= (§7.17.3). Эти операторы используются для присоединения к событию обработчика событий или его удаления соответственно. Модификаторы доступа события определяют контексты, в которых такие операции разрешены.

Операторы += и -= являются единственно допустимыми операторами для события вне типа, в котором оно объявлено. В связи с этим внешний код может добавлять обработчики к событию и удалять их, однако не может получать или изменять базовый список обработчиков событий.

В операциях вида x += y или x -= y (где для события x выполняется ссылка за пределы типа, который содержит объявление x) результат операции имеет тип void (а не значение x со значением x после присваивания). Это правило не допускает непосредственного просмотра базового делегата события внешним кодом.

В следующем примере показан порядок вложения обработчиков событий в экземпляры класса Button:

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control
{
public event EventHandler Click;
}

public class LoginDialog: Form
{
Button OkButton;
Button CancelButton;

public LoginDialog() {
OkButton = new Button(...);
OkButton.Click += new EventHandler(OkButtonClick);
CancelButton = new Button(...);
CancelButton.Click += new EventHandler(CancelButtonClick);
}

void OkButtonClick(object sender, EventArgs e) {
// Handle OkButton.Click event
}

void CancelButtonClick(object sender, EventArgs e) {
// Handle CancelButton.Click event
}
}

В этом примере с помощью конструктора экземпляра LoginDialog создается два экземпляра класса Button и присоединяются обработчики событий Click.

События, подобные полям

В тексте программы в классе или структуре, содержащих объявление события, некоторые события можно использовать как поля. Для использования таким образом событие не должно иметь модификаторы abstract и extern; кроме того, оно не может включать явные объявления методов доступа к событиям. Такое событие можно использовать в любом контексте, где разрешено использование полей. Поле содержит делегат (§15), ссылающийся на список обработчиков событий, добавленных к событию. Если обработчики событий не добавлены, поле содержит null.

В этом примере

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control
{
public event EventHandler Click;

protected void OnClick(EventArgs e) {
if (Click != null) Click(this, e);
}

public void Reset() {
Click = null;
}
}

Событие Click используется как поле внутри класса Button. Как показано в примере, это поле можно проверять, изменять и использовать в выражениях вызова делегата. Метод OnClick класса Button "вызывает" событие Click. Понятие вызова события совершенно эквивалентно вызову делегата, представленного событием. Поэтому не существует специальных языковых конструкций для вызова событий. Обратите внимание, что вызову делегата предшествует проверка, что делегат не равен null.

Вне объявления класса Button член Click может использоваться только с левой стороны операторов += и –=, как в следующем примере.

b.Click += new EventHandler(…);

В этом примере делегат добавляется к списку вызовов события Click, а в примере

b.Click –= new EventHandler(…);

делегат удаляется из списка вызовов события Click.

При компиляции события, подобного полю, компилятор автоматически создает хранилище для хранения делегата и создает методы доступа для события, добавляющие или удаляющие обработчики событий поля делегата. Операции добавления и удаления являются потокобезопасными; их можно (но не обязательно) выполнять при блокировке (§8.12) содержащего объекта для события экземпляра или объекта типа (§7.6.10.6) для статического события.

Так, объявление события экземпляра вида:

class X
{
public event D Ev;
}

будет откомпилировано в нечто, эквивалентное:

class X
{
private D __Ev; // field to hold the delegate

public event D Ev {
add {
/* add the delegate in a thread safe way */
}

remove {
/* remove the delegate in a thread safe way */
}
}
}

В классе X ссылки на Ev on в левом операнде операторов += и –= приводят к вызову методов доступа add и remove. Все остальные ссылки на Ev компилируются в ссылки на скрытое поле __Ev (§7.6.4). Имя "__Ev" произвольное; скрытое поле могло бы иметь любое имя или вообще не иметь никакого имени.

Методы доступа к событиям

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

Объявления методов доступа к событиям события указывают исполняемые операторы, связанные с добавлением и удалением обработчиков событий.

Объявления методов доступа состоят из объявления метода доступа add и объявления метода доступа remove. Каждое объявление метода доступа состоит из лексемы add или remove, за которой следует блок. В блоке, связанном с объявлением метода доступа add, указываются операторы, выполняемые при добавлении обработчика событий, а в блоке, связанном с объявлением метода доступа remove, указываются операторы, выполняемые при удалении обработчика событий.

Каждое объявление метода доступа add и объявление метода доступа remove соответствует методу с одним параметром значения типа события и типом возвращаемого значения void. Неявный параметр метода доступа к событию называется value. Когда событие используется в назначении события, используется соответствующий метод доступа к событию. В частности, если оператором присваивания является +=, используется метод доступа add, а если оператор присваивания -=, используется метод доступа remove. В любом случае правый операнд оператора присваивания используется в качестве аргумента метода доступа к событию. Блок объявления метода доступа add или объявления метода доступа remove должен соответствовать правилам для методов void, описанным в §10.6.10. В частности, операторам return в таком блоке запрещено указывать выражение.

Поскольку в методе доступа к событию неявно имеется параметр с именем value, объявление в методе доступа к событию локальной переменной или константы с таким именем является ошибкой времени компиляции.

В этом примере

class Control: Component
{
// Unique keys for events
static readonly object mouseDownEventKey = new object();
static readonly object mouseUpEventKey = new object();

// Return event handler associated with key
protected Delegate GetEventHandler(object key) {...}

// Add event handler associated with key
protected void AddEventHandler(object key, Delegate handler) {...}

// Remove event handler associated with key
protected void RemoveEventHandler(object key, Delegate handler) {...}

// MouseDown event
public event MouseEventHandler MouseDown {
add { AddEventHandler(mouseDownEventKey, value); }
remove { RemoveEventHandler(mouseDownEventKey, value); }
}

// MouseUp event
public event MouseEventHandler MouseUp {
add { AddEventHandler(mouseUpEventKey, value); }
remove { RemoveEventHandler(mouseUpEventKey, value); }
}

// Invoke the MouseUp event
protected void OnMouseUp(MouseEventArgs args) {
MouseEventHandler handler;
handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
if (handler != null)
handler(this, args);
}
}

класс Control реализует механизм внутреннего хранилища для событий. Метод AddEventHandler связывает значение делегата с ключом, метод GetEventHandler возвращает делегат, в данный момент связанный с ключом, а метод RemoveEventHandler удаляет делегат в качестве обработчика событий для указанного события. Предположительно, лежащий в основе механизм хранилища разработан так, что отсутствуют затраты на связывание значения делегата null с ключом, и таким образом необрабатываемые события не расходуют емкость хранилища.

Наши рекомендации