Поиск

Составные делегаты

Объединение делегатов — создание одного делегата из нескольких — одна из тех возможностей, которая поначалу не кажется такой уж полезной, но если вы столкнетесь с такой потребностью, то будете признательны команде разработчиков С# за то, что они это предусмотрели. Разберем некоторые примеры, когда может быть полезно объединение делегатов. В первом примере мы имеем дистрибьюторскую систему и класс, просматривающий все наименования товара на данном складе, вызывая метод обратного вызова для каждого наименования, запасов которого менее 50 единиц. В реальном дистрибьюторском приложении формула должна учитывать не только наличные запасы, но также заказанные и находящиеся "в пути". Но возьмем простой пример: если наличие на складе менее 50 единиц, возникает исключение.

Фокус в том, что мы хотим разделить методы, которые вызываются, если запасы ниже допустимых: нам нужно запротоколировать сам факт и, кроме того, послать письмо по электронной почте менеджеру по закупкам. Итак, составим делегат из нескольких других:

using System;
using System.Threading;
class Part {
public Part(string sku)
{
this.Sku = sku;
Random r = new Random(DateTime.Now.Millisecond);
double d = r.NextDoubleO * 100;
this.OnHand = (int)d; }
protected string Sku; public string sku {
get
{
return this.Sku;
}
set
{
this.Sku = value;
} }
protected int OnHand; public int onhand
{
get
{
return this.OnHand;
}
set
{
this.OnHand = value;
} } }
class InventoryManager
{
protected const int MIN_ONHAND = 50;
public Part[] parts; public InventoryManagerO
{
parts = new Part[5];
for (int 1=0; i < 5; i++)
{
Part part = new Part("Part " + (i + 1));
> Thread.Sleep(10); // Генератор случайных чисел,
// установленный по времени.
partsti] = part;
Console,WriteLine("Adding part '{0}' on-hand = {1}",
part.sku, part.onhand);
} }
public delegate void OutOfStockExceptionMethod(Part part);
public void ProcessInventory(OutOfStockExceptionMethod exception)
{
Console.WriteLine("\nProcessing inventory...");
foreach (Part part in parts)
{
if (part.onhand < MIN_ONHAND) {
Console.WriteLine
("{0} ({1}) is below minimum on-hand {2}",
part.sku, part.onhand, MIN_ONHAND);
exception(part); > } } }
class ComposlteDelegatelApp {
public static void LogEvent(Part part)
{
Console.WriteLine("\tlogging event...");
}
public static void EmailPurchasingMgr(Part part) {
Console.WriteLine("\teraailing Purchasing manager..."); }
public static void MainQ {
InventoryManager mgr = new InventoryManagerO;
InventoryManager.OutOfStockExceptionMethod
LogEventCallback = new InventoryManager.OutOfStockExceptionMethod(LogEvent);
Invento ryManage r.OutOfStockExceptionMethod
EmailPurchasingNgrCallback = new InventoryManager.OutOfStockExceptionMethod
(EmailPurchasingMgr);
InventoryManager.OutOfStockExceptionMethod
OnHandExceptionEventsCallback =
EmailPurchasingMgrCallback + LogEventCallback;
mgr.ProcessInventory(OnHandExceptionEventsCallback); } }
В результате выполнения мы увидим результат вроде этого:
Adding part 'Part V on-hand = 16
Adding part 'Part 2' on-hand = 98
Adding part 'Part 3' on-hand = 65
Adding part 'Part 4' on-hand = 22
Adding part 'Part 5' on-hand = 70
Processing inventory...
Part 1 (16) is below minimum on-hand 50
logging event...
entailing Purchasing manager... Part 4 (22)
is below minimum on-hand 50
logging event...
emailing Purchasing manager...

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

Тот факт, что эти методы вызываются последовательно, заставляет спросить: почему просто не связать методы в цепочку, чтобы каждый метод вызывал последующий? В нашем примере, где у нас всего два метода и вызываются они всегда оба, это сделать можно. Но усложним при-- мер. Допустим, у нас несколько магазинов, расположенных в разных местах, и каждый сам решает какие методы вызывать. К примеру, на территории одного магазина находится общий товарный склад, и здесь нужно запротоколировать событие и сообщить менеджеру о закупках, а в других магазинах — запротоколировать событие и сообщить управляющему магазина.

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

using System;
class Part {
public Part(string sku)
{
this.Sku = sku;
Random г = new Random(DateTime.Now.Millisecond);
double d = r.NextDoubleO * 100;
this.OnHand = (int)d; }
protected string Sku; public string sku {
get
{
return this.Sku;
}
set
{
this.Sku = value;
} }
protected int OnHand; public int onhand {
get {
return this.OnHand; }
set {
this.OnHand = value; } } }
class InventoryManager {
protected const int MIN_ONHAND = 50;
public Part[] parts; public InventoryManagerO {
parts = new Part[5];
for (int i = 0; i < 5; i++)
{
Part part = new Part("Part " + (i + 1));
parts[i] = part; Console.WriteLine
("Adding part '{0}' on-hand = {1}", part.sku, part.onhand); } }
public delegate void OutOfStockExceptionMethod(Part part);
public void ProcessInventory(OutOfStockExceptionMethod exception) {
Console.WriteLine("\nProcessing inventory...");
foreach (Part part in parts)
<
if (part.onhand < MIN_ONHAND) {
Console.WriteLine
("{0} ({1" is below minimum onhand {2}",
part.sku, part.onhand, MIN_ONHANO);
exception(part); } } } }
class CompositeDelegate2App {
public static void LogEvent(Part part)
{
Console.WriteLine("\tlogging event...");
>
public static void EmailPurchasingMgr(Part part)
{
Console.WriteLine("\temailing Purchasing manager..,"); }
public static void EmailStoreMgr(Part part) <
Console.WriteLine("\temailing store manager..."); >
public static void MainQ {
InventoryManager mgr = new InventoryManagerO;
InventoryManager.OutOfStockExceptionMethod[]
exceptionMethods = new
InventoryManager.OutOfStockExceptionMethod[3]; exceptionMethods[0] = new
InventoryManager.OutOfStockExceptionMethod
(LogEvent); exceptionMethods[1] = new
InventoryManager.OutOfStockExceptionMethod
(EmailPurchasingMgr); exceptionMethods[2] = new
InventoryManager.OutOfStockExceptionMethod (EmailStoreMgr);
int location = 1;
InventoryManager.OutOfStockExceptionMethod compositeDelegate;
if (location == 2) {
compositeDelegate =
exceptionMethods[0] + exceptionMethods[1]; }
else {
compositeDelegate =
exceptionMethods[0] + exceptionMethods[2]; }
mgr.ProcessInventory(compositeDelegate); } }

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