Язык программирования C#9 и платформа .NET5
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<>интенсивно используются во многих важных API-интерфейсах .NET Core, включая инфраструктуру параллельного программирования и LINQ (помимо прочих).Func<>Итак, первоначальный экскурс в типы делегатов окончен. Теперь давайте перейдем к обсуждению связанной темы — ключевого слова
языка С#.eventПонятие событий C#
Делегаты — довольно интересные конструкции в том плане, что позволяют объектам, находящимся в памяти, участвовать в двустороннем взаимодействии. Тем не менее, прямая работа с делегатами может приводить к написанию стереотипного кода (определение делегата, определение необходимых переменных-членов, создание специальных методов регистрации и отмены регистрации для предохранения инкапсуляции и т.д.).
Более того, во время применения делегатов непосредственным образом как механизма обратного вызова в приложениях, если вы не определите переменную-член типа делегата в классе как закрытую, тогда вызывающий код будет иметь прямой доступ к объектам делегатов. В таком случае вызывающий код может присвоить переменной-члену новый объект делегата (фактически удаляя текущий список функций, которые подлежат вызову) и, что даже хуже, вызывающий код сможет напрямую обращаться к списку вызовов делегата. В целях демонстрации создайте новый проект консольного приложения по имени
и добавьте следующую переделанную (и упрощенную) версию классаPublicDelegateProblemиз предыдущего примераCar:CarDelegatenamespace 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и вызвать делегат по своему желанию:CarEngineHandlerusing 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...