КАТЕГОРИИ:
АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Делегаты и отражениеВсе примеры использования делегатов, показанные до сих пор, требовали, чтобы разработчик заранее знал прототип метода обратного вызова. Так, если 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); } }
|