Рейтинговые книги
Читем онлайн ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 72 73 74 75 76 77 78 79 80 ... 259

// Этот класс определяет методы, которые будут вызываться

// типом Car.CarDelegate.

public class ServiceDepartment {

 public void WashCar(Car c) {

  if (c.Dirty) Console.WriteLine("Моем машину");

  else Console.WriteLine("Эта машина уже помыта…");

 }

 public void RotateTires(Car с) {

  if (c.Rotate) Console.WriteLine("Меняем шины");

  else Console.WriteLine("Менять шины не требуется…");

 }

}

Теперь проиллюстрируем взаимодействие между новыми типами Car, CarDelegate, Garage и ServiceDepartment, рассмотрев их использование в следующем фрагменте программного кода.

// Garage направляет все заказы в ServiceDepartment

// (найти хорошего механика всегда проблема…)

public class Program {

 static void Main(string[] args) {

  // Создание гаража.

  Garage g = new Garage();

  // Создание отделения обслуживания,

  ServiceDepartment sd = new ServiceDepartment();

  // Garage моет машины и меняет шины,

  // делегируя соответствующие полномочия ServiceDepartment.

  g.ProcessCars(new Car.CarDelegate(sd.WashCar));

  g.ProcessCars(new Car.CarDelegate(sd.RotateTires));

  Console.ReadLine();

 }

}

На рис. 8.6 показан соответствующий вывод.

Рис. 8.6. Перекладывание ответственности

Анализ программного кода делегирования

Предложенный выше метод Main() начинается с создания экземпляров типов Garage и ServiceDepartment. Когда вы пишете

// Помыть все грязные машины.

g.ProcessCars(new Car.CarDelegate(sd.WashCar));

это на самом деле означает: "Добавить указатель на метод ServiceDepartment.WashCar() к объекту Car.CarDelegate и передать этот объект в Garage.ProcessCars()". Подобно любому автомобильному предприятию в реальном мире, все заказы передаются в отдел технического обслуживания (что и объясняет, почему замена масла, обычно требующая 30 минут, занимает целых 2 часа). С учетом этого ProcessCars() можно интерпретировать так.

// CarDelegate указывает на функцию ServiceDepartment.WashCar.

public void ProсessCars(Car.CarDelegate proc) {

 foreach (Car с in theCars)

 proc(c); // proc(c) =› ServiceDepartment.WashCar(c)

 …

}

Точно так же, если вы говорите

// Поменять шины.

g.ProcessCars(new Car.CarDelegate(sd.RotateTires));

то ProcessCars() можно интерпретировать, как

// CarDelegate указывает на функцию ServiceDepartment.RotateTires.

public void ProcessCars(Car.CarDelegate proc) {

 …

 foreach(Car с in the Cars)

 proc(c); //proc(c) =› ServiceDepartment.RotateTires(e)

 …

}

Исходный код. Проект CarGarage размещен в подкаталоге, соответствующем главе 8.

Ковариантность делегатов

К этому моменту вы должны чувствовать себя более уверенно при создании и использовании типов делегата. Перед тем как перейти к изучению синтаксиса событий в C#, мы рассмотрим новую возможность .NET 2.0, связанную с делегатами и обозначенную термином ковариантность. Вы могли обратить внимание на то, что все делегаты, созданные нами до сих пор, указывали на методы, возвращающие простые числовые типы данных (или не возвращающие значений вообще). Но предположим, что нам нужен делегат, способный указывать на методы, возвращающие пользовательский тип класса.

// Определение делегата, который позволит указывать на объекты,

// возвращающие типы Car.

public delegate Car ObtainCarDelegate();

Мы можем определить целевой объект для делегата так, как обычно.

class Program {

 public delegate Car ObtainCarDelegate();

 public static Car GetBasicCar() {return new Car();}

 static void Main(string[] args) {

  ObtainCarDelegate targetA = new ObtainCarDelegate(GetBasicCar);

  Car c = targetA();

  Console.ReadLine();

 }

}

Пока что все выглядит прекрасно. Но что делать, если мы получим новый класс SportsCar из типа Car и потребуется делегат, который сможет указывать на методы, возвращаемые этим новым типом класса? До появления .NET 2.0 в таком случае вам пришлось бы определить новый делегат.

// Новый делегат, указывающий на целевые объекты,

// возвращающие типы SportsCar.

public delegate SportsCar ObtainSportsCarDelegate();

У нас теперь два типа делегата, и мы должны создать по экземпляру каждого из них, чтобы получить типы Car и SportsCar.

class Program {

 public delegate Car ObtainCarDelegate();

 public delegate SportsCar ObtainSportsCarDelegate();

 public static Car GetBasicCar() {return new Car(); }

 public static SportsCar GetSportsCar() {return new SportsCar();}

 static void Main(string[] args) {

  ObtainCarDelegate targetA = new ObtainCarDelegate(GetBasicCar);

  Car с = targetA();

  ObtainSportsCarDelegate targetB = new ObtainSportsCarDelegate(GetSportsCar);

  SportsCar sc = targetB();

  Console.ReadLine();

 }

}

По законам классического наследования в идеале лучше иметь один тип делегата, который мог бы указывать на методы, возвращающие либо тип Car, либо тип SportsCar (в конце концов, тип SportsCar связан с Car отношением наследования). Ковариантность позволяет реализовать именно такую возможность, т.е. построить один делегат, способный указывать на методы, возвращающие типы класса, связанные классическим отношением наследования.

class Program {

 // Определение делегата, способного возвращать

 // как Car, так и SportsCar.

 public delegate Car ObtainVehicalDelegate();

 public static Car GetBasicCar() {return new Car();}

 public static SportsCar GetSportsCar() { return new SportsCar();}

 static void Main(string[] args) {

  Console.WriteLine("***** Ковариантность делегатов *****n");

  ObtainVehicalDelegate targetA = new ObtainVehicalDelegate(GetBasicCar);

  Car c = targetA();

  // Такое присваивание возможно вследствие ковариантности.

  ObtainVehicalDelegate targetB = new ObtainVehicalDelegate(GetSportsCar);

  SportsCar sc = (SportsCar)targetB();

  Console.ReadLine();

 }

}

Обратите внимание на то, что тип делегата ObtainVehicalDelegate был определен для того, чтобы указывать на методы, возвращающие строго типизованный Car. Однако в условиях ковариантности мы получаем возможность указывать и на методы, возвращающие производные типы. Чтобы получить производный тип, нужно просто выполнить явное преобразование.

Замечание. Точно так же ковариантность обеспечивает возможность создания делегата, который позволит указать на множество методов, получающих объекты, связанные классическим отношением наследования. Более подробная информация имеется в документации .NET Framework 2.0 SDK.

Исходный код. Проект DelegateCovariance размещен в подкаталоге, соответствующем главе 8.

События в C#

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

Поскольку возможность обратного вызови объектов другим объектом является очень полезной, в C# предлагается специальное ключевое слово event, позволяющее минимизировать неудобства программиста, связанные с непосредственным применением делегатов. При обработке ключевого слова event компилятор автоматически создает для вас методы регистрации и отмены регистрации, а также члены-переменные, необходимые для вашего типа делегата. Ключевое слово event Можно назвать синтаксической "конфеткой", позволяющей экономить время при вводе программного кода.

Замечание. Даже при использовании в C# ключевого слова event вам все равно придется вручную определять связанные с делегатом типы.

Процесс определения события состоит из двух шагов. Во-первых, вы должны определить делегат, который будет содержать методы, вызываемые при наступлении соответствующего события. Затем вы объявляете события (используя ключевое слово C# event) в терминах соответствующего делегата. Определение типа, способного посылать события, имеет следующий шаблон (записанный здесь в псевдокоде).

public class SenderOfEvents {

 public delegate возврЗначение AssociatedDelegate(аргументы);

 public event AssociatedDelegate ИмяСобытия;

 …

}

События типа Car будут иметь те же имена, что и предыдущие делегаты (AboutToBlow и Exploded). Новому делегату, с которым будут ассоциироваться события, будет назначено имя CarEventHandler. Вот начальные изменения, вносимые в определение типа Car.

1 ... 72 73 74 75 76 77 78 79 80 ... 259
На этой странице вы можете бесплатно читать книгу ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание - Эндрю Троелсен бесплатно.
Похожие на ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание - Эндрю Троелсен книги

Оставить комментарий