Делегаты

Делегаты, лямбда-выражения и события

Делегат предоставляет возможность инкапсулировать метод, а событие уведомляет о том, что произошло некоторое действие. Делегаты и события тесно связаны друг с другом, поскольку событие основывается на делегате. Оба средства расширяют круг прикладных задача, решаемых при программировании на С#. А лямбда-выражение представляет собой новое синтаксическое средство, обеспечивающее упрощенный, но в то же время эффективный способ определения того, что по сути является единицей исполняемого кода. Лямбда-выражения обычно служат для работы с делегатами и событиями, поскольку делегат может ссылаться на лямбда-выражение.

Попросту говоря, делегат представляет собой объект, который может ссылаться на метод. Следовательно, когда создается делегат, то в итоге получается объект, содержащий ссылку на метод. Более того, метод можно вызывать по этой ссылке. Иными словами, делегат позволяет вызывать метод, на который он ссылается. Ниже будет показано, насколько действенным оказывается такой принцип.

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

Тип делегата объявляется с помощью ключевого слова delegate. Ниже приведена общая форма объявления делегата:

delegate возвращаемый_тип имя(список_параметров) ;

где возвращаемый_тип обозначает тип значения, возвращаемого методами, которые будут вызываться делегатом; имя — конкретное имя делегата; список_параметров — параметры, необходимые для методов, вызываемых делегатом.

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

Для того чтобы показать делегат в действии, рассмотрим для начала простой пример его применения.

// Объявить тип делегата.delegate string StrMod(string str) ; class Program { // Заменить пробелы дефисами. static string ReplaceSpaces(string s) { Console.WriteLine("Замена пробелов дефисами."); return s.Replace(' ', '-'); } // Удалить пробелы. static string RemoveSpaces(string s) { string temp = ""; int i; Console.WriteLine("Удаление пробелов."); for(i=0; i < s.Length; i++) if(s[i] != ' ') temp += s[i]; return temp; } // Обратить-строку. static string Reverse(string s) { string temp = ""; int i, j; Console.WriteLine("Обращение строки."); for(j=0, i=s.Length-1; i >= 0; i--, j++) temp += s[i]; return temp; } static void Main(string[] args) { // Сконструировать делегат. StrMod strOp = new StrMod(ReplaceSpaces); string str; // Вызвать методы с помощью делегата. str = strOp("ЭTO простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine(); strOp = new StrMod(RemoveSpaces) ; str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.WriteLine(); strOp = new StrMod(Reverse); str = strOp("Это простой тест."); Console.WriteLine("Результирующая строка: " + str); Console.ReadLine(); } }

Рассмотрим данный пример более подробно. В его коде сначала объявляется делегат StrMod типа string, как показано ниже.

delegate string StrMod(string str);

Как видим, делегат StrMod принимает один параметр типа string и возвращает одно значение того же типа.

Далее в классе DelegateTest объявляются три статических метода с одним параметром типа string и возвращаемым значением того же типа. Следовательно, они соответствуют делегату StrMod. Эти методы видоизменяют строку в той или иной форме. Обратите внимание на то, что в методе Rep 1а се Spaces () для замены пробелов дефисами используется один из методов типа string — Replace ().

В методе Main () создается переменная экземпляра strOp ссылочного типа StrMod и затем ей присваивается ссылка на метод ReplaceSpaces (). Обратим особое внимание на следующую строку кода.

StrMod strOp = new StrMod(ReplaceSpaces);

В этой строке метод ReplaceSpaces () передается в качестве параметра. При этом указывается только его имя, но не параметры. Данный пример можно обобщить: при получении экземпляра делегата достаточно указать только имя метода, на который должен ссылаться делегат. Ясно, что сигнатура метода должна совпадать с той, что указана в объявлении делегата. В противном случае во время компиляции возникнет ошибка.

Далее метод ReplaceSpaces () вызывается с помощью экземпляра делегата strOp, как показано ниже.

str = strOp("Это простой тест.");

Экземпляр делегата strOp ссылается на метод ReplaceSpaces (), и поэтому вызывается именно этот метод. Затем экземпляру делегата strOp присваивается ссылка на метод RemoveSpaces (), и с его помощью вновь вызывается указанный метод — на этот раз RemoveSpaces (). И наконец, экземпляру делегата strOp присваивается ссылка на метод Reverse (). А в итоге вызывается именно этот метод.

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