| Клуб программистов Лучшая подборка книг и информации по программированию. |
-
Использование делегатов в С#.
Написано 10.05.2010 22:00 Нет комментариевДля того чтобы можно было использовать класс в С#, нужно выполнить два действия. Во-первых, необходимо определить класс, т.е. сказать компилятору, какие поля и методы образуют класс. Затем (если только мы не используем статические методы) необходимо создать экземпляр класса, т.е. создать объект этого класса. То же самое справедливо для делегатов. Сначала требуется определить делегатов, которые будут применяться. Определение делегата означает указание компилятору того, какой тип методов будут представлять делегаты этого типа. Затем нужно создать один или несколько экземпляров делегатов.
Синтаксис для определения делегатов выглядит следующим образом: delegate void VoidOperation(uint X);
В данном случае мы определили делегата VoidOperation и указали, что каждый экземпляр этого делегата может содержать сведения о методе, принимающем один параметр uint и возвращающем void. Ключевым моментом является то, что делегаты безопасны по типу. При определении делегата вы должны указать все подробности сигнатуры метода, который он будет представлять.
Удобно рассматривать делегата как способ именования сигнатуры метода.
Допустим, что мы хотим определить делегата TwoLongsOp, который будет представлять функцию, принимающую в качестве параметров два значения long и возвращающую double. Мы могли бы сделать это следующим образом:delegate double TwoLongOp(long LI, long L2);Делегата, который представляет собой метод без параметров, возвращающий строку, можно определить так:
delegate string GetAString();Синтаксис аналогичен tomv, который используется при определении методов, за исключением того, что отсутствует тело метода, а определение предваряется ключевым словом delegate. Поскольку то, что мы делаем, представляет собой по сути определение класса, делегата можно определять там же, где и класс, т.е. либо внутри другого класса, либо вне всякого класса в пространстве имен как объект наивысшего уровня. В зависимости от того, насколько доступным должно быть определение делегата, можно примените один из обычных модификаторов доступа — public, private, protected и т.п.:
public delegate string GetAString();Когда мы говорим “определение делегата”, это буквально означает “определение нового класса”. Делегаты реализуются как экземпляры классов, производных от базового класса System.Delegate. Компилятору С# известно об этом классе, и он использует его синтаксис для делегатов, чтобы скрыть от нас детали его работы. Это еще один хороший пример того, как С# работает совместно с базовыми классами для того, чтобы сделать программирование настолько простым, насколько это возможно.
Определив делегата, мы можем создать его экземпляр и использовать его для хранения сведений о конкретном методе.
К сожалению, здесь имеется проблема с терминологией. Для классов существуют два отдельных термина: “класс”, который означает определение, и “объект”, который означает экземпляр класса. К сожалению, для делегатов используется только один термин. То, что получается при создании экземпляра делегата, также называется делегатом. Поэтому когда мы говорим о делегатах, необходимо по контексту определять, о чем конкретно идет речь.
Следующий фрагмент кода демонстрирует использование делегатов. Это довольно долгий путь вызова метода ToString() для int:private delegate string GetAString();
static void Main(string[] args){
int X = 40;
GetAString FirstStringMethod = new GetAString(X.ToString);
Console.WriteLine(”String is ” + FirstStringMethod());
// С FirstStringMethod инициализированным X.ToString()
// приведенный выше оператор эквивалентен
// Console.WriteLine(”String is ” + X.ToString());В этом коде мы создали экземпляр делегата типа GetAString и инициализировали его таким образом, что он стал ссылаться на метод ToString() целочисленной переменной X. Делегаты в С# всегда синтаксически принимают конструктор с одним параметром, в качестве параметра передается метод, на который будет ссылаться делегат. Сигнатура данного метода должна соответствовать сигнатуре, указанной при объявлении делегата. Поэтому в данном случае, если бы мы попытались инициализировать FirstStringMethod любым другим методом, сигнатура которого отличается от сигнатуры ToString(), возникла бы ошибка компиляции. Так как int.ToString() является методом экземпляра (а не статическим), для корректной инициализации делегата требуется вместе с именем метода указать экземпляр (х).
Следующая строка кода использует делегата для отображения строкового значения. В любом коде указание имени делегата с последующими скобками, в которых перечислены параметры, имеет точно такое же действие, что и вызов метода, для которого делегат служит оболочкой. Поэтому в приведенном коде оператор Console.WriteLine() эквивалентен тому, что записано в комментарии.
Одна из особенностей делегатов заключается в том, что они безопасны по типу в том смысле, что сигнатура вызываемого метода является корректной. Однако их совершенно не интересует, для какого типа объекта вызывается метод и является ли метод статическим или методом экземпляра.
Экземпляр делегата может ссылаться на любой статический метод или метод экземпляра любого объекта любого типа — при условии, что сигнатура этого метода совпадает с сигнатурой делегата.
Для демонстрации этого расширим приведенный выше пример кода, в котором используется делегат FirstStringMethod, чтобы он вызывал пару других методов для другого объекта: метод экземпляра и статический метод. Вновь обратимся к структуре Currency. Напомним, что структура Currency уже имеет свою собственную перегруженную версию ToString(). Добавим в Currency статический метод с такой же сигнатурой:struct Currency {
public static string GetCurrencyUnit(); {
return “Dollar”;
}Теперь можно использовать экземпляр GetAString следующим образом:
private delegate string GetAString();
static void Main(string[] args) {
int X = 40;
GetAString FirstStringMethod = new GetAString(X.ToString);
Console.WriteLine(”String is ” + FirstStringMethod());
Currency Balance = new Currency(34, 50);
FirstStringMethod = new GetAString(Balance.ToString);
Console.WriteLine(”String is ” + FirstStringMethod());
FirstStringMethod = new GetAString(Currency.GetCurrencyUnit);
Console.WriteLine(”String is ” + FirstStringMethod());Этот код показывает, как можно вызвать метод при помощи делегата, и последовательно присваивает делегату ссылки на различные методы разных экземпляров классов: статические методы или методы экземпляров разных типов классов, при условии, что сигнатура метода совпадает с определением делегата.
Однако мы все еще не продемонстрировали процесс собственно передачи делегата другому методу и не сделали ничего полезного. Метод ToString() для объектов int и currency можно вызывать более простым способом, без использования делегатов. Однако такова природа делегатов: чтобы оценить их полезность, необходимо рассмотреть сложный пример. Мы приведем два примера применения делегатов. Первый иллюстрирует, как передавать делегатов в методы и как можно использовать массивы делегатов — но в целом он не выполняет ничего такого, что нельзя было бы сделать без делегатов. Более сложный пример — класс BubbleSorter — реализует метод для сортировки массивов объектов в порядке возрастания. Этот класс было бы трудно написать без применения делегатов.



Новые комментарии