Справочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией
Скачать 5.05 Mb.
|
Глава 17 Динамическая идентификация типов, отражение и атрибуты 450 Часть I. Язык C# та глава посвящена трем взаимосвязанным мощным средствам C#: динамической идентификации типов, отражению и атрибутам. Динамическая идентификация типов — это механизм, который позволяет распознать тип данных во время выполнения программы. Отражение представляет собой средство, с помощью которого можно получить информацию о типе. Используя эту информацию, во время выполнения программы можно создавать объекты, а затем работать с ними. Это средство обладает большой эффективностью, поскольку оно позволяет динамически расширять функции, выполняемые программой. Атрибут предназначен для описания элементов C#-программы. Например, можно определить атрибуты для классов, методов и полей. Информацию об атрибутах можно запрашивать и получать во время выполнения программы. Для поддержки атрибутов используются средства как динамической идентификации типов, так и отражения соответствующей информации. Динамическая идентификация типов Динамическая идентификация типов (runtime type identification — RTTI) позволяет определить тип объекта во время выполнения программы, что необходимо во многих ситуациях. Например, можно совершенно точно узнать, на объект какого типа в действительности указывает ссылка на базовый класс. Еще одно применение RTTI — заранее проверить, удачно ли будет выполнена операция приведения типа, не допустив возникновения исключения, связанного с некорректно заданной операцией приведения типа. Динамическая идентификация типов также является ключевым компонентом средства отражения (информации о типе). В C# предусмотрено три ключевых слова, которые поддерживают динамическую идентификацию типов: is , as и typeof . Рассмотрим назначение каждого из них в отдельности. Проверка типа с помощью ключевого слова is С помощью оператора is можно определить, имеет ли рассматриваемый объект заданный тип. Общая форма его записи имеет следующий вид: выражение is тип Здесь тип элемента выражение сравнивается с элементом тип. Если тип элемента выражение совпадает (или совместим) с элементом тип , результат выполнения операции принимается равным значению ИСТИНА. В противном случае — значению ЛОЖЬ. Следовательно, если результат истинен, выражение можно привести к типу, заданному элементом тип . Рассмотрим пример использования оператора is // Демонстрация выполнения оператора is. using System; class A {} class B : A {} class UseIs { public static void Main() { A a = new A(); B b = new B(); Э Глава 17. Динамическая идентификация типов, отражение и атрибуты 451 if(a is A) Console.WriteLine("Объект а имеет тип А."); if(b is A) Console.WriteLine("Объект b совместим с типом А, " + "поскольку его тип выведен из типа А."); if(a is B) Console.WriteLine("Этот текст не будет отображен, " + "поскольку объект а не выведен из класса B."); if(b is B) Console.WriteLine("Объект b имеет тип B."); if(a is object) Console.WriteLine("a -- это объект."); } } Результаты выполнения этой программы таковы: Объект а имеет тип А. Объект b совместим с типом А, поскольку его тип выведен из типа А. Объект b имеет тип B. а -- это объект. Несмотря на то что все сообщения в этой программе говорят сами за себя, некотрые из них все же требуют пояснений. Обратите внимание на следующую инструкцию: if(b is А) Console.WriteLine("Объект b совместим с типом А, " + "поскольку его тип выведен из типа А."); В данном случае if -инструкция выполнилась успешно, поскольку переменная b является ссылкой типа в, который выведен из типа А . Следовательно, объект b совместим с типом А . Однако обратное утверждение не является справедливым. При выполнении строки кода if(a is B) Console.WriteLine("Этот текст не будет отображен, " + "поскольку объект а не выведен из класса B."); if -инструкция успехом не увенчается, поскольку объект а имеет тип А , который не выведен из типа B . Следовательно, объект a и класс B несовместимы по типу. Использование оператора as Иногда во время работы программы требуется выполнить операцию приведения типов, не генерируя исключение в случае, если попытка окажется неудачной. Для этого предусмотрен оператор as , формат которого таков: выражение as тип Нетрудно догадаться, что используемый здесь элемент выражение участвует в попытке приведения его к типу, заданному элементом тип . В случае успешного выполнения этой операции возвращается ссылка на тип . В противном случае возвращается нулевая ссылка. Оператор as в некоторых случаях предлагает удобную альтернативу оператору is Рассмотрим, например, следующую программу, в которой оператор is позволяет предотвратить неверное приведение типов: // Использование оператора is для предотвращения // неверной операции приведения типов. using System; class A {} class В : A {} 452 Часть I. Язык C# class CheckCast { public static void Main() { A a = new A(); В b = new В(); // Проверяем, можно ли объект а привести к типу В. if(a is В) // При положительном результате выполняем // операцию приведения типов. b = (В) a; else //В противном случае операция приведения // типов опускается. b = null; if(b==null) Console.WriteLine( "Операция приведения типов b = (В) а НЕ РАЗРЕШЕНА."); else Console.WriteLine( "Операция приведения типов b = (В) а разрешена."); } } Результаты выполнения этой программы таковы: Операция приведения b = (В) а НЕ РАЗРЕШЕНА. Как подтверждают эти результаты, поскольку тип объекта а не совместим с типом B , операция приведения объекта а к типу В недопустима, и ее выполнение предотвращается с помощью инструкции if . Как видите, реализация такого подхода требует выполнения двух этапов. Первый состоит в подтверждении обоснованности операции приведения типов, а второй — в самом ее выполнении. С помощью оператора as эти два. этапа можно объединить в один, как показано в следующей программе. // Демонстрация использования оператора as. using System; class A {} class B : A {} class CheckCast { public static void Main() { A a = new A(); B b = new B(); b = a as B; // Выполняем операцию приведения типов, // если она возможна. if(b==null) Console.WriteLine("Операция приведения типов " + "b - (B) а НЕ РАЗРЕШЕНА."); else Console.WriteLine( "Операция приведения типов b - (B) а разрешена."); } } Вот результаты выполнения этой программы: Операция приведения типов b = (В) а НЕ РАЗРЕШЕНА. Глава 17. Динамическая идентификация типов, отражение и атрибуты 453 В этой версии оператор as проверяет допустимость операции приведения типов, а затем, если она законна, выполняет ее, причем все это реализуется в одной инструкции. Использование оператора typeof Несмотря на полезность операторов as и is , они просто проверяют (причем каждый по-своему) совместимость двух типов. Программист же зачастую сталкивается с необходимостью получить информацию о типе данных. Для таких случаев в C# предусмотрен оператор typeof . Его назначение состоит в считывании объекта класса System.Type для заданного типа. Используя этот объект, можно определить характеристики конкретного типа данных. Оператор typeof используется в следующем формате: typeof( тип ) Здесь элемент тип означает тип, информацию о котором мы хотим получить. Объект типа Туре , возвращаемый при выполнении оператора typeof , инкапсулирует информацию, связанную с заданным типом. Получив Type -объект, можно обращаться к информации о заданном типе, используя различные свойства, поля и методы, определенные в классе Туре . Класс Туре содержит множество членов, но их обсуждение мы отложим до следующего раздела, посвященного отражению информации о типе. Однако, чтобы все же продемонстрировать одно из возможных применений класса Туре , рассмотрим программу, в которой используются три его свойства: FullName , IsClass и IsAbstract . Свойство FullName позволяет получить полное имя типа. Свойство IsClass возвращает значение true , если типом объекта является класс. Свойство IsAbstract возвращает значение true , если рассматриваемый класс является абстрактным. // Демонстрация использования оператора typeof. using System; using System.IO; class UseTypeof { public static void Main() { Type t = typeof(StreamReader); Console.WriteLine(t.FullName); if(t.IsClass) Console.WriteLine("Это класс."); if(t.IsAbstract) Console.WriteLine( "Это абстрактный класс"); else Console.WriteLine("Это конкретный класс."); } } При выполнении этой программы получены такие результаты: System.IO.StreamReader Это класс. Это конкретный класс. Эта программа получает объект типа Туре с описанием типа StreamReader . Затем она отображает полное имя этого типа, определяет, класс ли это и является ли он абстрактным. 454 Часть I. Язык C# Отражение Как упоминалось в начале этой главы, отражение (reflection) — это средство C#, которое позволяет получить информацию о типе. Термин отражение произошел от характера процесса: объект класса Туре воспроизводит, или отражает, базовый тип, который он представляет. Для получения интересующей вас информации вы “задаете вопросы” объекту класса Туре , а он возвращает (отражает) для вас информацию, связанную с этим типом. Отражение — мощный механизм, позволяющий узнать характеристики типа, точное имя которого становится известным только во время выполнения программы, и соответствующим образом использовать их. Многие классы, которые поддерживают средство отражения, являются частью интерфейса .NET Reflection API, который определен в пространстве имен System.Reflection . Таким образом, в программы, которые используют средство отражения, обычно включается следующая инструкция: using System.Reflection; Ядро подсистемы отображения: класс System.Tуре Класс System.Type — “сердце” подсистемы отображения, поскольку он инкапсулирует тип. Он содержит множество свойств и методов, которые можно использовать для получения информации о типе во время выполнения программы. Класс Туре выведен из абстрактного класса System.Reflection.MemberInfо В классе MemberInfo определены следующие абстрактные свойства, которые предназначены только для чтения: Туре DeclanngType Тип класса или интерфейса, в котором объявляется анализируемый член MemberTypes MemberType Тип члена string Name Имя типа Type ReflectedType Тип отражаемого объекта Обратите внимание на то, что свойство MemberType имеет тип MemberTypes Свойство MemberTypes представляет собой перечисление, которое определяет значения, соответствующие различным типам членов. Среди прочих они включают следующие: MemberTypes.Constructor MemberTypes.Method MemberTypes.Field MemberTypes.Event MemberTypes.Property Следовательно, тип члена можно определить, опросив свойство MemberType Например, если свойство MemberType содержит значение MemberTypes.Method , значит, интересующий нас член является методом. Класс MemberInfo включает два абстрактных метода: GetCustomAttributes() и IsDefined() . Оба они связаны с атрибутами. К методам и свойствам, определенным в классе MemberInfo , класс Туре добавляет немало “своих”. Ниже перечислены наиболее часто используемые методы, определенные в классе Туре Глава 17. Динамическая идентификация типов, отражение и атрибуты 455 Метод Назначение ConstructorInfo[] GetConstructors() Получает список конструкторов для заданного типа EventInfo[] GetEvents() Получает список событий для заданного типа FieldInfo[] GetFields() Получает список полей для заданного типа MemberInfo[] GetMembers() Получает список членов для заданного типа MethodInfo[] GetMethods() Получает список методов для заданного типа PropertyInfo[] GetProperties() Получает список свойств для заданного типа Теперь ознакомьтесь с наиболее часто используемыми свойствами, определенными в классе Туре Свойство Назначение Assembly Assembly Получает компоновочный файл для заданного типа TypeAttributes Attributes Получает атрибуты для заданного типа Type BaseType Получает непосредственный базовый тип для заданного типа string FullName Получает полное имя заданного типа bool IsAbstract Истинно, если заданный тип является абстрактным bool IsArray Истинно, если заданный тип является массивом bool IsClass Истинно, если заданный тип является классом bool IsEnum Истинно, если заданный тип является перечислением string Namespace Получает пространство имен для заданного типа Использование отражения Используя методы и свойства класса Туре, во время выполнения программы можно получить подробную информацию о типе. Это чрезвычайно мощное средство, поскольку, получив информацию о типе, можно вызвать конструкторы этого типа, его методы и использовать его свойства. Таким образом, отражение позволяет использовать код, который недоступен в период компиляции программы. Программный интерфейс Reflection API —довольно обширная тема, и здесь невозможно рассмотреть ее в полном объеме. (Полное описание Reflection API — это материал для целой книги!) Но интерфейс Reflection API столь логичен, что, поняв, как использовать одну его часть, нетрудно разобраться во всем остальном. В следующих разделах описаны ключевые способы применения средства отражения: получение информации о методах, вызов методов, создание объектов и загрузка типов из компоновочных файлов. Получение информации о методах С помощью Type -объекта можно получить список методов, поддерживаемых заданным типом. Для этого используется метод GetMethods() . Один из форматов его вызова таков: MethodInfo[] GetMethods() 456 Часть I. Язык C# Метод GetMethods() возвращает массив объектов типа MethodInfo , которые описывают методы, поддерживаемые вызывающим типом. Класс MethodInfо определен в пространстве имен System.Reflection Класс MethodInfo — производный от абстрактного класса MethodBase , который, в свою очередь, выведен из класса MemberInfo . Таким образом, программисту доступны свойства и методы, определенные во всех трех классах. Например, чтобы получить имя метода, используйте свойство Name . Особого внимания заслуживают два члена класса MethodInfo : ReturnType и GetParameters() Свойство ReturnType , которое имеет тип Туре , позволяет получить тип значения, возвращаемого методом. Метод GetParameters() возвращает список параметров, связанных с методом. Формат его вызова таков: ParameterInfo[] GetParameters() Информация о параметрах содержится в объекте класса ParameterInfo . В классе ParameterInfo определено множество свойств и методов, которые используются для описания параметров. Из них стоит обратить внимание на следующие два: свойство Name , которое представляет собой строку, содержащую имя параметра, и свойство ParameterType , которое описывает тип параметра. Тип параметра инкапсулирован в объекте класса Туре Рассмотрим программу, в которой средство отражения используется для получения методов, поддерживаемых классом MyClass . Для каждого метода программа отображает его имя и тип возвращаемого им значения, а также имя и тип всех параметров, которые может иметь тот или иной метод. // Анализ методов с помощью средства отражения. using System; using System.Reflection; class MyClass { int x; int y; public MyClass(int i, int j) { x = i; y = j; } public int sum() { return x+y; } public bool isBetween(int i) { if(x < i && i < y) return true; else return false; } public void set(int a, int b) { x = a; y = b; } public void set(double a, double b) { x = (int) a; y = (int) b; Глава 17. Динамическая идентификация типов, отражение и атрибуты 457 } public void show() { Console.WriteLine(" x: {0}, y: {1}", x, y); } } class ReflectDemo { public static void Main() { Type t = typeof(MyClass); // Получаем Type-объект, // представляющий MyClass. Console.WriteLine( "Анализ методов, определенных в " + t.Name); Console.WriteLine(); Console.WriteLine("Поддерживаемые методы: "); MethodInfo[] mi = t.GetMethods(); // Отображаем методы, поддерживаемые классом MyClass. foreach(MethodInfo m in mi) { // Отображаем тип значения, возвращаемого методом, // и имя метода. Console.Write(" " + m.ReturnType.Name + " " + m.Name + "("); // Отображаем параметры. ParameterInfo[] pi = m.GetParameters(); for(int i=0; i < pi.Length; i++) { Console.Write(pi[i].ParameterType.Name + " " + pi[i].Name); if(i+1 < pi.Length) Console.Write(", "); } Console.WriteLine(")"); Console.WriteLine(); } } } Результаты выполнения этой программы такие: Анализ методов, определенных в MyClass Поддерживаемые методы: Int32 GetHashCode() Boolean Equals(Object obj) String ToString() Int32 sum() Boolean isBetween(Int32 i) Void set(Int32 a, Int32 b) 458 Часть I. Язык C# Void set(Double a, Double b) Void show() Type GetType() Обратите внимание на то, что помимо методов, определенных в классе MyClass , здесь также отображаются методы, определенные в классе object . Дело в том, что все типы в C# выведены из класса object . Также стоит отметить, что для имен типов здесь используются имена .NET-структуры. Обратите внимание еще на то, что метод set() отображен дважды. Этому есть простое объяснение: метод set() перегружен. Одна его версия принимает аргументы типа int , а вторая — аргументы типа double Эта программа требует некоторых пояснений. Прежде всего, отметим, что в классе MyClass определяется public -конструктор и ряд public -методов, включая перегруженный метод set() . При выполнении строки кода из метода Main() получаем объект класса Туре , представляющий класс MyClass : Type t = typeof(MyClass); // Получаем Type-объект, // представляющий MyClass. Используя переменную t и интерфейс Reflection API, программа отображает информацию о методах, поддерживаемых классом MyClass . Сначала с помощью следующей инструкции получаем список методов: MethodInfo[] mi = t.GetMethods(); Затем выполняется цикл foreach , на итерациях которого для каждого метода отображается тип возвращаемого им значения, имя метода и его параметры: // Отображаем тип значения, возвращаемого методом, // и имя метода. Console.Write(" " + m.ReturnType.Name + " " + m.Name + " ("); // Отображаем параметры. ParameterInfo[] pi = m.GetParameters(); for(int i=0; i < pi.Length; i++) { Console.Write(pi[i].ParameterType.Name + " " + pi[i].Name); if(i+1 < pi.Length) Console.Write(", "); } В этом фрагменте программы информация о параметрах каждого метода считывается посредством вызова метода GetParameters() и сохраняется в массиве pi . Затем в цикле for выполняется опрос массива pi и отображается тип каждого параметра и его имя. Главное здесь то, что эта информация считывается динамически во время выполнения программы, т.е. без предварительной информации о классе MyClass |