Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы ОСиСП.doc
Скачиваний:
68
Добавлен:
11.05.2015
Размер:
1.78 Mб
Скачать

Упрощенный синтаксис № 3: не нужно определять параметры метода обратного вызова

Обычная ситуация, в которой используется описанный выше упрощенный синтаксис, — когда нужно выполнить определенный код при нажатии кнопки:

buttonl.Click += delegate(Object sender, EventArgs e)

{

MessageBox.Show("The Button was clicked!");

};

Удобно иметь возможность встроить код, вызываемый по механизму обратного вызова, не определяя другой метод. Но в этом примере код обратного вызова совсем не ссылается на параметры метода обратного вызова — sender и е. Если коду обратного вызова не нужны эти параметры, С# позволяет сократить приведенный выше код до следующего:

buttonl.Click += delegate { MessageBox.Show("The Button was clicked!"); };

Заметьте: я просто удалил подстроку (Object sender, EventArgs e). Генерируя анонимный метод, компилятор также создает метод, прототип которого в точности соответствует делегату — CLR жестко требует выполнения этого условия для обеспечения безопасности типов. В этом случае компилятор все равно создает анонимный метод, соответствующий делегату EventHandler (тип делегата, ожидаемый событием Click типа Button). Однако в этом примере код анонимного метода не ссылается на параметры делегата.

Если код обратного вызова ссылается на какой-либо из параметров, после ключевого слова delegate нужно ввести круглые скобки, типы параметров и имена переменных. Возвращаемый тип все равно определяется типом делегата, и, если возвращаемый тип не void, обязательно предусмотреть оператор return во встроенном коде обратного вызова.

Упрощенный синтаксис № 4: не нужно вручную создавать обертку локальных переменных класса для передачи их в метод обратного вызова

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

internal sealed class AClass

{

public static void Using_localVariablesInTheCallbackCode(Int32 numToDo)

{

// Некоторые локальные переменные.

Int32[] squares = new Int32[numToDo];

AutoResetEvent done = new AutoResetEvent(false);

// Выполняем некоторые задачи в других потоках.

for (Int32 n = 0; n < squares.Length; n++)

{

ThreadPool.QueueUserWorkItem(

delegate(Object obj)

{

Int32 num = (Int32)obj;

// В других условиях на выполнение этой задачи требуется больше времени.

squares[num] = num * num; //<<<<< squares – внешняя переменная

// Если это последняя задача, оставляем (не закрываем) поток.

if (Interlocked.Decrement(ref numToDo) == 0)

done.Set();

},

n

);

}

// Ожидаем завершения всех остальных потоков.

done.WaitOne();

// Отображаем результаты.

for (Int32 n = 0; n < squares.Length; n++)

Console.WriteLine("Index {0}, Square={1}", n, squares[n]);

}

}

В показанном выше методе определяется один параметр, numToDo, и две локальные переменные, squares и done. А код обратного вызова в делегате ссылается на эти переменные.

А теперь представьте, что код обратного вызова размещен в отдельном методе (как того требует CLR). Как же передать значения переменных в метод обратного вызова? Единственный способ — определить новый вспомогательный класс, где также определено по полю для каждого значения, которое передается коду обратного вызова. Кроме того, код обратного вызова в этом вспомогательном классе нужно определить как экземплярный метод. В этом случае метод UsingLocalVariablesInTheCallbackCode должен создать экземпляр вспомогательного класса, инициализировать поля значениями его локальных переменных и затем создать объект-делегат, связанный с вспомогательным классом и экземплярным методом.

Это очень нудная и чреватая ошибкам работа, которую, конечно же, компилятор C# выполняет за вас автоматически. Показанный выше код компиляторC# переписывает примерно так:

internal sealed class AClass

{

public static void UsingLocalVariablesInTheCallbackCode(Int32 numToDo)

{

// Некоторые локальные переменные.

WaitCallback callback1 = null;

// Создаем экземпляр вспомогательного класса.

<>с__DisplayClass2 class1 = new <>c DisplayClass2();

// Инициализируем поля вспомогательного класса.

classl.numToDo = numToDo;

classl.squares = new Int32[class1.numToDo];

classl.done = new AutoResetEvent(false);

// Выполняем некоторые задачи в других потоках,

for (Int32 n = 0; n < classl.squares.Length; n++)

{

if (callback1 == null)

{

// Новый объект-делегат привязывается к объекту вспомогательного класса

// и к его анонимному экземплярному методу,

callback1 = new WaitCallback(classl.<UsingLocalVariablesInTheCallbackCode>b__0);

}

ThreadPool.QueueUserWorkItem(callback1, n);

}

// Ожидаем завершения всех остальных потоков.

classl.done.Wait0ne();

// Отображаем результаты.

for (Int32 n = 0; n < classl.squares.Length; n++)

Console.WriteLine("Index {0}, Square={1}", n, classl.squares[n]);

}

// Вспомогательному классу присваивается необычное имя,

// чтобы избежать возможных конфликтов и

// предотвратить доступ из внешнего класса Aclass.

[CompilerGenerated]

private sealed class <>c__DisplayClass2 : Object

{

// В коде обратного вызова каждой локальной переменной

// соответствует одно открытое поле,

public Int32[] squares;

public Int32 numToDo;

public AutoResetEvent done;

// Открытый конструктор без параметров.

public <>с DisplayClass2 { }

// Открытый экземплярный метод, содержащий код обратного вызова.

public void <UsingLocalVariablesInTheCallbackCode>b__0(0bject obj)

{

Int32 num = (Int32) obj;

squares[num] = num * num;

if (Interlocked.Decrement^ref numToDo) ==0)

done.Set();

}

}

}

Ниже приводится код, в котором использование анонимных методов смотрится исключительно гармоничным и естественным. Без анонимных методов этот код было значительно сложнее писать, читать и поддерживать.

// Создание и инициализация массива String.

String[] names = { "Jeff", "Kristin", "Aidan" };

// Извлекаем имена, в которых есть строчная буква 'i'.

Char charToFind = 'i';

names = Array.FindAll(names, delegate(String name) { return (name.IndexOf(charToFind) >= 0); });

// Преобразуем все символы строки в верхний регистр.

names = Array.ConvertAll<String, String>(names, delegate(String name) { return name.ToUpper(); });

// Сортируем имена.

Array.Sort(names, String.Compare);

// Отображаем результаты.

Array.ForEach(names, Console.WriteLine);