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

Делегаты и отражение

Все примеры использования делегатов, показанные до сих пор, требовали, чтобы разработчик заранее знал прототип метода обратного вызова. Так, если fb — переменная, ссылающаяся на делегат Feedback, код для вызова делегата будет примерно такой:

fb(item); // Параметр item определен как Int32.

Как видите, во время кодирования разработчик должен знать, сколько параметров требует метод обратного вызова и тип каждого из них. К счастью, у вас почти всегда есть эта информация, поэтому написать код вроде предыдущего — не проблема.

Но порой у разработчика нет этих сведений на момент компиляции. Достаточно вспомнить пример из главы 10, в котором обсуждался тип EventSet. В этом примере словарь поддерживается набором разных типов делегатов. Чтобы событие сработало во время выполнения, производится поиск и вызов делегата из словаря. При компиляции нельзя точно узнать, какой делегат будет вызван и какие параметры передать его методу обратного вызова.

К счастью, тип System.Delegate предлагает методы, позволяющие создавать и вызывать делегаты, даже если на момент компиляции нет всех сведений о делегате. Вот эти методы:

public abstract class Delegate

{

// Создать делегат «тип», служащий оболочкой заданного статического метода.

public static Delegate CreateDelegate(Type type, Methodlnfo method);

public static Delegate CreateDelegate(

Type type,

Methodlnfo method,

Boolean throwOnBindFailure

);

// Создать делегат «тип», служащий оболочкой заданного экземплярного метода.

public static Delegate CreateDelegate(

Type type, // Эквивалентно this

Object firstArgument,

Methodlnfo method

);

public static Delegate CreateDelegate(

Type type,

Object firstArgument,

Methodlnfo method,

Boolean throwOnBindFailure

);

// Вызываем делегат, передав ему параметры.

public Object DynamicInvoke(params Object[] args);

}

Все версии метода CreateDelegate создают новый объект типа — потомок Delegate, заданного первым параметром type.

Параметр Methodlnfo указывает, что метод должен вызываться по методу обратного вызова; для получения этого значения нужно использовать API отражения (см. главу 22). Если делегат должен быть оболочкой для экземплярного метода, надо передать в CreateDelegate параметр firstArgument, указывающий, что объект нужно передать в экземплярный метод в параметре this (первый аргумент).

Наконец, CreateDelegate генерирует исключение ArgumentException, если делегат не может связаться с методом, указанным в параметре method. Это может произойти, если сигнатура метода method, заданная переменной, не соответствует сигнатуре, требуемой делегатом и заданной в параметре type. Однако если передать в параметре throwOnBindPailure значение false, вместо этого будет возвращено значение null.

Внимание!У класса System.Delegate намного больше перегруженных версий метода CreateDelegate, чем показано здесь. Никогда не следует вызывать ни один из этих других методов. В Microsoft даже жалеют, что вообще определили их. Причина в том, что в остальных методах в процессе привязки вызываемый метод идентифицируется с использованием типа String, а не Metbodlnfo. Это означает, что возможна неоднозначная привязка, приводящая к непрогнозируемому поведению приложения.

Метод Dynamiclnvoke типа SystemDelegate позволяет вызвать метод обратного вызова объекта-делегата, передав набор параметров, определяемых во время выполнения. При вызове Dynamiclnvoke его код проверяет совместимость переданных параметров с параметрами, ожидаемыми методом обратного вызова. Если параметры совместимы, выполняется обратный вызов, в противном случае генерируется исключение ArgumentException.

Dynamiclnvoke возвращает объект, который вернул метод обратного вызова. Следующий код демонстрирует использование методов CreateDelegate и Dynamiclnvoke:

using System;

using System.Reflection;

using System.IO;

// Вот несколько разных определений делегатов,

internal delegate Object TwoInt32s(Int32 n1, Int32 n2);

internal delegate Object OneString(String s1);

public static class Program

{

public static void Main(String[] args)

{

if (args.Length < 2)

{

String fileName =

Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location);

String usage = @"Usage:" +

"{0} {1} delType methodName [Arg1] [Arg2]" +

"{0} where delType must be TwoInt32s or OneString" +

"{0} if delType is TwoInt32s, methodName must be Add or Subtract" +

"{0} if delType is OneString, methodName must be NumChars or Reverse" +

"{0}" +

"{0} Examples:" +

"{0} {1} TwoInt32s Add 123 321" +

"{0} {1} TwoInt32s Subtract 123 321" +

"{0} {1} OneString NumChars Y'Hello there\"" +

"{0} {1} OneString Reverse Y'Hello there\"";

Console.WriteLine(usage, Environment.NewLine, fileName);

return;

// Преобразовываем параметр delType в тип делегата.

Type delType = Type.GetType(args[0]);

if (delType == null)

{

Console.WriteLine("Invalid delType argument: " + args[0]);

return;

}

Delegate d;

try

{

// Преобразовываем параметр Arg1 в метод.

MethodInfo mi = typeof(Program).GetMethod(args[1], BindingFlags.NonPublic | BindingFlags.Static);

// Создаем объект-делегат, служащий оболочкой статического метода.

d = Delegate.CreateDelegate(delType, mi);

}

catch (ArgumentException)

{

Console.WriteLine("Invalid methodName argument: " + args[1]);

return;

}

// Создаем массив, который содержит только параметры,

// передаваемые методу через объект-делегат.

Object[] callbackArgs = new Object[args.Length - 2];

if (d.GetType() == typeof(TwoInt32s))

{

try

{

// Преобразуем параметры типа String в параметры типа Int32.

for (Int32 a = 2; a < args.Length; a++)

callbackArgs[a - 2] = Int32.Parse(args[a]);

}

catch (FormatException)

{

Console.WriteLine("Parameters must be integers."); return;

}

}

if (d.GetType() == typeof(OneString))

{

// Просто копируем параметр типа String.

Array.Copy(args, 2, callbackArgs, 0, callbackArgs.Length);

}

try

{

// Вызываем делегат и показываем результат.

Object result = d.DynamicInvoke(callbackArgs);

Console.WriteLine("Result = " + result);

}

catch (TargetParameterCountException)

{

Console.WriteLine("Incorrect number of parameters specified.");

}

}

}

// Это метод обратного вызова, принимающий два параметра Int32.

private static Object Add(Int32 n1, Int32 n2)

{

return n1 + n2;

}

// Это метод обратного вызова, принимающий два параметра Int32.

private static Object Subtract(Int32 n1, Int32 n2)

{

return n1 - n2;

}

// Это метод обратного вызова, принимающий один параметр String.

private static Object NumChars(String s1)

{

return s1.Length;

}

// Это метод обратного вызова, принимающий один параметр String.

private static Object Reverse(String s1)

{

Char[] chars = s1.ToCharArray();

Array.Reverse(chars);

return new String(chars);

}

}

Вопрос № 19