На заметку! Делегаты Action<> и Func<> интенсивно используются во многих важных API-интерфейсах .NET Core, включая инфраструктуру параллельного программирования и LINQ (помимо прочих).

Итак, первоначальный экскурс в типы делегатов окончен. Теперь давайте перейдем к обсуждению связанной темы — ключевого слова event языка С#.

<p id="AutBody_Root456">Понятие событий C#</p>

Делегаты — довольно интересные конструкции в том плане, что позволяют объектам, находящимся в памяти, участвовать в двустороннем взаимодействии. Тем не менее, прямая работа с делегатами может приводить к написанию стереотипного кода (определение делегата, определение необходимых переменных-членов, создание специальных методов регистрации и отмены регистрации для предохранения инкапсуляции и т.д.).

Более того, во время применения делегатов непосредственным образом как механизма обратного вызова в приложениях, если вы не определите переменную-член типа делегата в классе как закрытую, тогда вызывающий код будет иметь прямой доступ к объектам делегатов. В таком случае вызывающий код может присвоить переменной-члену новый объект делегата (фактически удаляя текущий список функций, которые подлежат вызову) и, что даже хуже, вызывающий код сможет напрямую обращаться к списку вызовов делегата. В целях демонстрации создайте новый проект консольного приложения по имени PublicDelegateProblem и добавьте следующую переделанную (и упрощенную) версию класса Car из предыдущего примера CarDelegate:

namespace PublicDelegateproblem

{

  public class Car

  {

    public delegate void CarEngineHandler(string msgForCaller);

    // Теперь это член public!

    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...

hee, hee, hee...

Очевидно, что вы не захотите предоставлять другим приложениям возможность изменять то, на что указывает делегат, или вызывать его члены без вашего разрешения. С учетом сказанного общепринятая практика предусматривает объявление переменных-членов, имеющих типы делегатов, как закрытых.

<p id="AutBody_Root457">Ключевое слово event</p>
Перейти на страницу:

Похожие книги