static string SumToString(int x, int y)
{
return (x + y).ToString();
}
Вызовите эти методы:
Func<int, int, int> funcTarget = Add;
int result = funcTarget.Invoke(40, 40);
Console.WriteLine("40 + 40 = {0}", result);
Func<int, int, string> funcTarget2 = SumToString;
string sum = funcTarget2(90, 300);
Console.WriteLine(sum);
С учетом того, что делегаты
Action<>
и
Func<>
могут устранить шаг по ручному определению специального делегата, вас может интересовать, должны ли они применяться всегда. Подобно большинству аспектов программирования ответ таков: в зависимости от ситуации. Во многих случаях
Action<>
и
Func<>
будут предпочтительным вариантом. Однако если необходим делегат со специальным именем, которое, как вам кажется, помогает лучше отразить предметную область, то построение специального делегата сводится к единственному оператору кода. В оставшихся материалах книги вы увидите оба подхода.
На заметку! Делегаты
Action<>
и
Func<>
интенсивно используются во многих важных API-интерфейсах .NET Core, включая инфраструктуру параллельного программирования и LINQ (помимо прочих).
Итак, первоначальный экскурс в типы делегатов окончен. Теперь давайте перейдем к обсуждению связанной темы — ключевого слова
event
языка С#.
Понятие событий C#
Делегаты — довольно интересные конструкции в том плане, что позволяют объектам, находящимся в памяти, участвовать в двустороннем взаимодействии. Тем не менее, прямая работа с делегатами может приводить к написанию стереотипного кода (определение делегата, определение необходимых переменных-членов, создание специальных методов регистрации и отмены регистрации для предохранения инкапсуляции и т.д.).
Более того, во время применения делегатов непосредственным образом как механизма обратного вызова в приложениях, если вы не определите переменную-член типа делегата в классе как закрытую, тогда вызывающий код будет иметь прямой доступ к объектам делегатов. В таком случае вызывающий код может присвоить переменной-члену новый объект делегата (фактически удаляя текущий список функций, которые подлежат вызову) и, что даже хуже, вызывающий код сможет напрямую обращаться к списку вызовов делегата. В целях демонстрации создайте новый проект консольного приложения по имени
PublicDelegateProblem
и добавьте следующую переделанную (и упрощенную) версию класса
Car
из предыдущего примера
CarDelegate
:
namespace PublicDelegateproblem
{
public class Car
{
public delegate void CarEngineHandler(string msgForCaller);
<b> // Теперь это член public!</b>
public CarEngineHandler ListOfHandlers;
// Просто вызвать уведомление Exploded.
public void Accelerate(int delta)
{
if (ListOfHandlers != null)
{
ListOfHandlers("Sorry, this car is dead...");
}
}
}
}
Обратите внимание, что у вас больше нет закрытых переменных-членов с типами делегатов, инкапсулированных с помощью специальных методов регистрации. Поскольку эти члены на самом деле открытые, вызывающий код может получить доступ прямо к переменной-члену
ListOfHandlers
, присвоить ей новые объекты
CarEngineHandler
и вызвать делегат по своему желанию:
using System;
using PublicDelegateProblem;
Console.WriteLine("***** Agh! No Encapsulation! *****\n");
// Создать объект Car.
Car myCar = new Car();
// Есть прямой доступ к делегату!
myCar.ListOfHandlers = CallWhenExploded;
myCar.Accelerate(10);
// Теперь можно присвоить полностью новый объект...
// что в лучшем случае сбивает с толку.
myCar.ListOfHandlers = CallHereToo;
myCar.Accelerate(10);
// Вызывающий код может также напрямую вызывать делегат!
myCar.ListOfHandlers.Invoke("hee, hee, hee...");
Console.ReadLine();
static void CallWhenExploded(string msg)
{
Console.WriteLine(msg);
}
static void CallHereToo(string msg)
{
Console.WriteLine(msg);
}
Открытие доступа к членам типа делегата нарушает инкапсуляцию, что не только затруднит сопровождение кода (и отладку), но также сделает приложение уязвимым в плане безопасности! Ниже показан вывод текущего примера:
***** Agh! No Encapsulation! *****
Sorry, this car is dead...
Sorry, this car is dead...