Симон Робинсон - C# для профессионалов. Том II
class MyClass {
public int X; // и т.д.
}
// где-то в другом месте кода
MyClass Mine = new MyClass(); // выполнить обработку
fixed (int *pX = Mine.X) {
// можно использовать рХ в этом блоке
}
Возможно вкладывание блоков fixed для объявления более одного указателя. Можно также объявить более одного указателя в одной инструкции fixed при условии, что оба указателя имеют один тип объекта ссылки.
fixed (int *рХ = Mine.X, *рХ2 = Mine2.X) {
Объявление массивов в стеке
C# предоставляет оператор stackalloc, который используется в соединении с указателями для объявления массива в стеке без накладных расходов. Массив, размещаемый таким образом, не является полным объектом System.Array в стиле C#, он является просто массивом чисел, аналогичным одномерному массиву C++. Элементы этого массива не инициализируются и доступны с помощью такого же синтаксиса, как и в C++, с использованием квадратных скобок для указателя.
Оператор stackalloc требует спецификации типа данных и числа размещаемых элементов.
Синтаксис C++:
unsigned long рМуArray[20];
Синтаксис C#:
ulong *pMyArray = stackalloc ulong[20];
Отметим, однако, что хотя эти массивы похожи, версия C# позволяет определить размер во время выполнения:
int X;
// инициализировать X
ulong *pMyArray = stackalloc ulong[X];
Интерфейсы
Интерфейсы являются особенностью C#, которая не имеет аналога в ANSI C++, хотя компания Microsoft ввела интерфейсы в C++ с помощью специального ключевого слова. Идея интерфейса развилась из интерфейсов COM, которые предназначены служить контрактом, который указывает, какие методы или свойства реализует объект.
Интерфейс в C# не совсем такой, как интерфейс COM, так как он не имеет связанного с ним GUID, не является производным из IUnknown и не имеет связанных с ним записей в реестре (хотя можно отобразить интерфейс C# на интерфейс COM). Интерфейс C# является просто множеством определений функций и свойств. Он может рассматриваться как аналог абстрактного класса и определяется с помощью синтаксиса аналогичного класса.
interface IMyInterface {
void MyMethod(int X);
}
Можно заметить, однако, следующие синтаксические различия с определением класса:
□ Методы не имеют модификаторов доступа.
□ Методы никогда не реализуются в интерфейсе.
□ Методы не объявляются виртуальными или явно абстрактными. Выбор методов принадлежит классу, который реализует этот интерфейс.
Класс реализует интерфейс, наследуя из него. Класс может быть производным только от одного класса, но от любого числа интерфейсов. Если класс реализует интерфейс, то он должен предоставить реализации всех методов, определенных этим интерфейсом.
class MyClass : MyBaseClass, IMyInterface, IAnotherInterface // и т.д.
{
public virtual void MyMethod(int X) {
// реализация
}
// и т.д.
В этом примере выбрана реализация MyMethod как виртуального метода с открытым доступом.
Интерфейсы могут также выводиться из других интерфейсов, и в этом случае производный интерфейс содержит свои собственные методы, а также методы базового интерфейса.
interface IMyInterface : IBaseInterface
Можно проверить, что объект реализует интерфейс, используя либо оператор is, либо оператор as для преобразования его в этот интерфейс. Альтернативно можно преобразовать его напрямую, но в этом случае будет получено исключение, если объект не реализует интерфейс, поэтому этот подход годится только в том случае, если известно, что преобразование пройдет успешно. Можно использовать полученную таким образом ссылку на интерфейс, чтобы вызывать методы на этом интерфейсе (реализация будет предоставляться экземпляром класса).
IMyInterface MyInterface;
MyClass Mine = new MyClass();
MyInterface = Mine as IMyInterface;
if (MyInterface != null) MyInterface.MyMethod(10);
Основные применения интерфейсов следующие:
□ Взаимодействовать и устанавливать обратную совместимость с компонентами COM.
□ Служить в качестве контракта для других классов .NET. Интерфейс может использоваться для указания, что класс реализует некоторые свойства. Например, цикл C# foreach работает внутренне, проверяя, что класс, в котором он используется, реализует интерфейс IEnumerate, и вызывая затем методы, определенные этим интерфейсом.
Делегаты
Делегаты в C# не имеют прямого эквивалента в C++ и выполняют ту же самую задачу, что и указатели на функции в C++. Идея делегата состоит в том, что указатель на метод помещается в специальный класс вместе со ссылкой на объект, на котором вызывается метод (для метода экземпляра или со ссылкой null для статического метода). Это означает, что в отличие от указателя на функцию в C++, делегат C# содержит достаточно информации для вызова метода экземпляра.
Формально делегат является классом, который выводится из класса System.Delegate. Следовательно, создание экземпляра делегата включает два этапа: определение этого производного класса и объявление переменной соответствующего типа. Определение класса делегата включает данные полной сигнатуры (с возвращаемым типом) метода, который содержит делегат.
Основное использование делегатов состоит в передаче и вызове ссылок на методы: ссылки на методы нельзя передавать непосредственно, но они могут передаваться внутри делегата. Делегат обеспечивает безопасность типа данных, не позволяя вызывать метод с неверной сигнатурой. Метод, который содержит делегат, может вызываться синтаксически как вызов делегата. Следующий код показывает общие принципы. Первое, необходимо определить класс делегата:
// определить класс делегата, который представляет метод,
// получающий int и возвращающий void
delegate void MyOp(int X);
Затем, для целей этого примера объявим класс, который содержит вызываемый метод:
// затем определение класса
class MyClass {
void MyMethod(int X) {
// и т.д.
}
}
Еще позже, может быть при реализации некоторого другого класса, имеется метод, которому должна быть передана ссылка на метод с помощью делегата:
void MethodThatTakesDelegate(MyOp Op) {
// вызвать метод, передавая ему значение 4
Oр(4);
}
// и т.д.
И, наконец, код, который реально использует делегата:
MyClass Mine = new MyClass();
// Создать экземпляр делегата MyOp. Настроить его,
// чтобы он указывал на метод MyMethod из Mine.
MyOp DoIt = new MyOp(Mine.MyMethod);
После объявления переменной делегата можно вызвать метод с помощью делегата:
DoIt();
Или передать его в другой метод:
MethodThatTakesDelegate(DoIt);
В частном случае, когда делегат представляет метод, который возвращает void, этот делегат является широковещательным делегатом и может одновременно представлять более одного метода. Вызов делегата заставляет все методы вызываться по очереди. Можно использовать операторы + и += для добавления метода делегату, а - и -= — для удаления метода, который уже находится в делегате. Делегаты рассматриваются более подробно в главе 6.
События
События являются специальной формой делегатов, которые используются для поддержки модели уведомления о событии с помощью обратного вызова. Событие имеет следующую сигнатуру:
delegate void EventClass(obj Sender, EventArgs e);
Это сигнатура, которую должен иметь любой обработчик событий с обратным вызовом. Ожидается, что Sender будет ссылкой на объект, который инициирует событие, в то время как System.EventArgs (или любой класс, производный из EventArgs, который также допустим в качестве параметра) является классом, используемым средой выполнения .NET для передачи базовой информации, имеющей отношение к деталям события.
Для объявления события используется специальный синтаксис:
public event EventClass OnEvent;
Клиенты используют синтаксис += широковещательных делегатов для информирования, что они хотят получить уведомление.
// EventSource ссылается на экземпляр класса, который содержит событие
EventSource.OnEvent += MyHandler;
Источник просто вызывает событие, когда потребуется, используя такой же синтаксис, который был показан выше для делегатов. Так как событие является широковещательным делегатом, то все обработчики событий будут вызваны в ходе этого процесса. События рассматриваются более подробно в главе 6.
OnEvent(this, new EventArgs());