
- •1. Понятие модуля. Принципы модульного программирования. Понятие объекта как динамического модуля.
- •2. Понятие класса. Понятие метода. Представление метода в виде обычной процедуры. Понятие конструктора и деструктора.
- •3. Понятие свойства. Методы получения и установки значений свойств. Свойства-массивы (в некоторых языках программирования). Индексаторы (в некоторых языках программирования).
- •Информация о типе времени выполнения программы
- •5. Классы в программных модулях. Атрибуты доступа к элементам объектов. Термин «инкапсуляция».
- •Термин «инкапсуляция»
- •Virtual – в базовом классе
- •7. Понятие ссылки на метод объекта (или делегата – в зависимости от языка программирования). Понятие события. Применение ссылок на методы для расширения объектов.
- •8. Понятие метакласса (в некоторых языках программирования). Методы, применяемые к классам. Виртуальные конструкторы (в некоторых языках).
- •Понятие метакласса (в некоторых языках программирования)
- •Виртуальные конструкторы (в некоторых языках)
- •3) Finally – вызвать free.
- •Если глобально-уникальный идентификатор назначается интерфейсу, то он записывается после ключевого слова interface и заключается в квадратные скобки, например:
- •Microsoft Visual Studio
- •Import Network;
- •Var Stat: SocketStat;
- •Var SocketStatCollection: … ;
- •Конструкторы и деструкторы
- •Стандартные конструкторы
- •Создание объектов по значению (на стеке) и по ссылке (в динамической памяти)
- •Операторы new и delete
- •Размещающий оператор new
- •Порядок конструирования и разрушения объектов
- •Вложенные определения классов
- •«Друзья» класса
- •Статические члены класса
- •19. Перегрузка бинарных операторов. Перегрузка унарных операторов. Перегрузка операторов преобразования типа.
- •Индексаторы
- •Механизм вызова событий
- •Создание пользовательских обобщенных коллекций
- •Создание обобщенных интерфейсов
- •Несколько слов о вложенных делегатах
- •25. Понятие итератора в языке c#. Оператор foreach. Оператор yield.
- •И напоследок... Блок finally
- •26. Понятие атрибутов в языке c#. Создание пользовательских атрибутов. Анализ атрибутов во время выполнения программы. Понятие рефлексии (reflection) в языке c#. Сериализация объектов.
- •Что такое метаданные и зачем они нужны?
- •1. Метаданные в .Net обязательны и универсальны.
- •2. Метаданные в .Net общедоступны.
- •3. Метаданные в .Net исчерпывающи.
- •4. Метаданные в .Net расширяемы.
- •5. Метаданные в .Net конструируемы программно.
- •Получение экземпляра класса Type
- •Динамическая загрузка сборок
- •Динамическая загрузка типов
- •Исследование типа
- •Характеристики типа как целого
- •Члены класса
- •Исследование объекта
- •Динамическое создание объекта и вызов методов
- •Создание объекта по его типу
- •Декларативное программирование
- •Новые механизмы абстракции?
- •Динамическое создание типов
- •Роль графов объектов
- •Formatter сериализации
- •XmlSerializer
- •Интерфейсы iFormatter и iRemotingFormatter
- •Точность типов среди форматеров
- •28*. Ооп в языке программирования Smalltalk. Достоинства и недостатки этого языка в сравнениии с языком программирования c#.
7. Понятие ссылки на метод объекта (или делегата – в зависимости от языка программирования). Понятие события. Применение ссылок на методы для расширения объектов.
Понятие ссылки на метод объекта
Delphi:
Существуют процедурные типы данных для методов объектов.
type
TReadLineEvent = procedure(Reader: TTextReader; constLine: string) of object;
Переменная такого типа – указатель на метод (method pointer). Она занимает в памяти 8 байт и хранит одновременно ссылку на объект и адрес его метода.
type
TTextReader = class
private
FOnReadLine: TReadLineEvent;
...
public
property OnReadLine: TReadLineEvent read FOnReadLine write FOnReadLine;
end;
Методы объектов, объявленные по приведенному выше шаблону, становятся совместимы по типу со свойством OnReadLine.
Получается, что один объект может получить какие-то уведомления от другого объекта. Такой механизм называется делегированием, поскольку он позволяет передать часть работы другому объекту, например, сосредоточить в одном объекте обработку событий, возникающих в других объектах. Это избавляет программиста от необходимости порождать многочисленные классы-наследники и перекрывать в них виртуальные методы. Делегирование широко применяется в среде Delphi. Например, все компоненты делегируют обработку своих событий той форме, в которую они помещены.
В C# тип делегата представляет собой ссылки на методы с конкретным списком параметров и типом возвращаемого значения. С помощью делегатов методы обрабатываются как сущности, которым можно передавать параметры и которые можно присваивать переменным. Понятие делегата близко к понятию указателя на функцию, используемому в некоторых других языках. Однако делегаты, в отличие от указателей на функции, представляют собой пример объектно-ориентированного и строго типизированного подхода к программированию.
В следующем примере объявляется и используется тип делегата Function.
delegate double Function(double x);
class Multiplier { double factor;
public Multiplier(double factor)
{ this.factor = factor; }
public double Multiply(double x)
{ return x * factor; } }
class Test { static double Square(double x)
{ return x * x; }
static double[] Apply(double[] a, Function f)
{ double[] result = new double[a.Length]; for (int i = 0; i < a.Length; i++) result[i] = f(a[i]); return result; }
static void Main()
{ double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, Square);
double[] sines = Apply(a, Math.Sin);
Multiplier m = new Multiplier(2.0); double[] doubles = Apply(a, m.Multiply); } }
Делегат может ссылаться как на статический метод, так и на метод экземпляра. Делегат, ссылающийся на метод экземпляра, также ссылается на конкретный объект. При вызове такого метода экземпляра с помощью делегата этот объект становится объектом this.
Делегаты также могут создаваться с помощью анонимных функций, которые представляют собой «встроенные методы», создаваемые в процессе выполнения. Анонимные функции могут видеть локальные переменные окружающих их методов. Таким образом, приведенный выше пример множителя может быть записан проще с использованием класса Multiplier:
double[] doubles = Apply(a, (double x) => x * 2.0);
Интересной и полезной особенностью делегата является то, что для него неважен тип класса или метода, на который он ссылается. Единственным требованием является наличие у метода, на который ссылается делегат, такого же числа параметров и типа возвращаемого значения.
Понятие события
Наиболее общее определение события – любое происшествие, вызванное вмешательством пользователя, поведением системы или логикой кода приложения. Событие в программе может быть связано с некоторым кодом, называемым обработчиком события и отвечающим на это происшествие. С точки зрения разработчика, событие – это имя, к которому обработчик события может быть присоединен в программе.
Если разработчик реализовал код обработчика события, то он выполняется, когда это событие происходит. В противном случае обычно ничего не происходит.
Имеется два вида событий: взаимодействие пользователя и изменение состояния. События взаимодействия пользователя почти всегда вызываются в соответствии с сообщениями Windows, указывая, что пользователь делал кое-что, поэтому компонент может ответить. События изменения состояния также могут быть сведены к сообщениям Windows.
Обработчики событий могут не возвращать никакой информации о событии, которое называется в таком случае событием уведомления или возвращать дополнительную специфическую информацию, связанную с событием. В этом случае его называют специфическим событием.
События уведомления просто сообщают, что некоторое событие случилось, без предоставления информации о том, где и когда. Уведомления обычно использую тип TNotifyEvent, который имеет только один параметр – отправитель события. Специфические события отличаются от событий уведомления, и используются в тех случаях, когда хотелось бы знать, не только какое событие случилось и какой компонент его вызвал, но и получить сопутствующую дополнительную информацию. В подобных случаях необходимы обработчики, которые включают параметры для дополнительной информации. В подобных случаях нужны обработчики специфических событий.
Синтаксически события объявляются почти полностью аналогично свойствам:
Property <имя события>: <указателъ на метод>
[Read <поле события метод чтения>]
[Write <поле события\метод записи>];
• Хотя в командах чтения и записи могут использоваться методы, на практике они не используются. Обычно используется прямой доступ к полю.
• Все события в Delphi принято именовать: OnCreate, OnClick и т.д.
В данном случае событие и соответствующее поле имеют тип TNotifyEvent.
Таким образом, в общем случае для объявления события необходимо сделать следующее:
• определить или выбрать тип данных события (тип указателя на метод) (процедурный тип);
• объявить поле соответствующего типа для хранения значения события;
• объявить (корреспондирующий – event-dispatching method) метод, который будет вызывать обработчик события, когда оно произойдет. Представить реализацию этого метода и определить механизм его вызова;
Стандартный синтаксис для реализации метода уведомления о событии имеет следующий вид:
Procedure <имя класса>.<имя метода>[(<объявление параметров>)];
Begin
<некий код> If Assigned(<пoлe события>)
Then <поле coбытия>(Self[,<дополнительные параметры>]);
<некий код>
End;
type
TNotifyEvent = procedure(Sender : TObject) of object;
TControl = class(TComponent)
private
FOnClick : TNotifyEvent;
protected
procedure Click; dynamic;
property OnClick : TNotifyEvent read FOnClick write FOnClick;
end;
procedure TControl.Click;
begin
if Assigned(FOnClick) then
FOnClick(Self);
end;
Класс TButton наследуется от TControl и публикует свойство OnClick. Object Inspector, видя свойство процедурного типа, размещает его на странице обработчиков событий. При нажатии на кнопку класс TButton вызывает метод Click, и тот вызывает соответствующий обработчик события, если последний присвоен.
Можно также присвоить обработчику события указатель на обычную процедуру, не являющуюся методом класса. В этом случае предыдущий пример будет выглядеть так:
procedure ButtonClick (Self : TButton; Sender : TButton); begin ShowMessageFmt ('Нажата кнопка %s', [Sender.Caption]); ShowMessageFmt ('Self = %s', [Self.Name]); end;
procedure AssignButtonClick (const Button : TButton); var Method : TMethod; begin Method.Code := @ButtonClick; Method.Data := Button; Button.OnClick := TNotifyEvent (Method); end; |
В этом случае в процедуре, предназначенной быть обработчиком события, необходимо явно описать еще один, дополнительный параметр, который и будет получать значение Self. Правильный метод его описания зависит от соглашения о связях (calling convention), используемого в процедурном типе и методе. Для модели register (которая используется для всех стандартных обработчиков и принята по умолчанию для всех процедур в Delphi) этот параметр должен идти первым, а за ним – все остальные, так, как они описаны в типе обработчика события. Для приведения типов используется специальный тип TMethod, описанный в модуле System; он представляет собой внутреннюю реализацию объектного процедурного типа.
В пользу такого подхода можно найти два аргумента. Во-первых, в этом случае мы легко можем передать в обработчик события любой параметр Self (так, в примере, мы передаем словно бы собственный метод кнопки). Во-вторых, зная действительные типы параметров, мы можем использовать их при декларации процедуры и избежать лишних приведений типов: так, в ButtonClick параметры Self и Sender определены как TButton, хотя, вообще говоря, корректней бы было описать их как TObject.
Операция @
Особым моментом, о котором необходимо упомянуть, является операция @ (взятие адреса). Вообще говоря, ее следовало бы использовать при всех операциях с процедурными типами. Так, процедуру Example из самого первого примера было бы в некотором смысле правильнее записать так:
procedure Example; var CalcFunction : TCalcFunction; begin CalcFunction := @Add; ShowMessageFmt('CalcFunction (2, 3) = %d', [CalcFunction (2, 3)]); end; |
Обратите внимание на использование операции @ в присваиваниях. Результат в этом случае будет абсолютно таким же: компилятор отслеживает операции с процедурными типами и при необходимости добавляет неявную операцию взятия адреса.
Мы могли бы написать:
if CalcFunction = Add then { ... } |
и тем поставить компилятор перед выбором: то ли мы хотели сравнить указатели, то ли - вызвать две функции и сравнить их результаты. В случае если бы TCalcFunction определял функцию без параметров, оба этих толкования были бы синтаксически верны. Во избежание конфликтов в таких случаях компилятор всегда использует вызов процедуры, а для сравнения указателей необходимо использовать операцию @ – то есть писать
if @CalcFunction = @Add then { ... } |
Как правило, использования операции @ не требуется.
Применение ссылок на методы для расширения объектов
type
TTextReader = class
private
FOnReadLine: TReadLineEvent;
...
public
property OnReadLine: TReadLineEvent read FOnReadLine write FOnReadLine;
end;
Методы объектов, объявленные по приведенному выше шаблону, становятся совместимы по типу со свойством OnReadLine.
type
TForm1 = class(TForm)
procedure HandleLine(Reader: TTextReader; constLine: string);
end;
var
Form1: TForm1;
Reader: TTextReader;
Reader.OnReadLine := Form1.HandleLine;
Function TTextReader.NextLine: Boolean;
begin
if Assigned(FOnReadLine) then
FOnReadLine(Self, S); // уведомление о чтении очередной строки
end;
Объект Form1 через метод HandleLine получит уведомление об очередной считанной строке. Обратите внимание, что вызов метода через указатель происходит лишь в том случае, если указатель не равен nil. Эта проверка выполняется с помощью стандартной функции Assigned, которая возвращает True, если ее аргумент является связанным указателем.
Такой механизм называется делегированием, поскольку он позволяет передать часть работы другому объекту, например, сосредоточить в одном объекте обработку событий, возникающих в других объектах. Это избавляет программиста от необходимости порождать многочисленные классы-наследники и перекрывать в них виртуальные методы. Делегирование широко применяется в среде Delphi. Например, все компоненты делегируют обработку своих событий той форме, в которую они помещены.