русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Базовые элементы .NET Framework


Дата добавления: 2013-12-23; просмотров: 820; Нарушение авторских прав


2.1. МЕТАДАННЫЕ И МЕХАНИЗМ ОТРАЖЕНИЯ

При программировании для платформы .NET в любую сборку помещаются метаданные, которые являются описанием всех типов данных сборки и их членов. Работа с метаданными происходит при помощи специального механизма, называемого механизмом отражения (reflection).

Главные элементы, которые необходимы для использования возможностей отражения – это класс System.Type и типы из пространства имен System.Reflection.

Класс System.Type служит для получения информации о типе. Для того чтобы получить объект данного класса, существует несколько возможностей.

1. Вызвать у объекта метод GetType(). Данный метод определен на уровне System.Object, а значит присутствует у любого объекта:

Foo A = new Foo(); //Foo – это некий класс

Type t = A.GetType();

2. Использовать статический метод Type.GetType(), которому передается текстовое имя интересующего нас типа:

Type t;

t = Type.GetType("Foo");

3. Использовать ключевое слово языка C# typeof:

Type t = typeof(Foo);

Чтобы продемонстрировать возможности класса Type, опишем вспомогательный класс Foo.

public class Foo {

private int Field1;

public float Field2;

private int Method1() {

Console.WriteLine("Method1 is called");

return 0;

}

public void Method2(string s, ref int i) {

Console.WriteLine("Method2 is called");

Console.WriteLine("First parameter is " + s);

Console.WriteLine("Second parameter is " + i);

}

public int Prop {

get { return Field1; }

set { Field1 = value; }

}

}

А теперь рассмотрим код, который выводит информацию об элементах типа Foo (для использования типов, подобных FieldInfo, необходимо подключить пространство имен System.Reflection).

Type t = typeof(Foo);

 

//Это общая информация



Console.WriteLine("Full name = " + t.FullName);

Console.WriteLine("Base is = " + t.BaseType);

Console.WriteLine("Is abstract = " + t.IsAbstract);

Console.WriteLine("Is sealed = " + t.IsSealed);

Console.WriteLine("Is class = " + t.IsClass);

Console.WriteLine("******************************");

 

//Сейчас пройдемся по полям

FieldInfo[] fi = t.GetFields();

foreach(FieldInfo f in fi)

Console.WriteLine("Field = " + f.Name);

Console.WriteLine("******************************");

 

//А теперь по свойствам

PropertyInfo[] pi = t.GetProperties();

foreach(PropertyInfo p in pi)

Console.WriteLine("Property = " + p.Name);

Console.WriteLine("******************************");

 

//С методами поработаем подробнее

MethodInfo[] mi = t.GetMethods();

foreach(MethodInfo m in mi) {

Console.WriteLine("Method Name = " + m.Name);

Console.WriteLine("Method Return Type = " + m.ReturnType);

 

//Изучим параметры метода

ParameterInfo[] pri = m.GetParameters();

foreach(ParameterInfo pr in pri) {

Console.WriteLine("Parameter Name = " + pr.Name);

Console.WriteLine("Type = " + pr.ParameterType);

}

Console.WriteLine("******************************");

}

Данный код выводит следующую информацию:

Full name = refl.Foo

Base is = System.Object

Is abstract = False

Is sealed = False

Is class = True

******************************

Field = Field2

******************************

Property = Prop

******************************

Method Name = GetHashCode

Method Return Type = System.Int32

******************************

Method Name = Equals

Method Return Type = System.Boolean

Parameter Name = obj

Parameter Type = System.Object

******************************

Method Name = ToString

Method Return Type = System.String

******************************

Method Name = Method2

Method Return Type = System.Void

Parameter Name = s

Parameter Type = System.String

Parameter Name = i

Parameter Type = System.Int32&

******************************

Method Name = get_Prop

Method Return Type = System.Int32

******************************

Method Name = set_Prop

Method Return Type = System.Void

Parameter Name = value

Parameter Type = System.Int32

******************************

Method Name = GetType

Method Return Type = System.Type

******************************

Обратите внимание, что была получена информация только об открытых членах класса Foo. Кроме этого, информация включала описание собственных и унаследованных элементов класса Foo. Пространство имен System.Reflection содержит специальное перечисление BindingFlags, которое позволяет управлять получаемой о типе информацией.

Таблица 7

Элементы перечисления BindingFlags

Элемент BindingFlags Описание
Default Поиск по умолчанию
IgnoreCase Поиск, не чувствительный к регистру
DeclaredOnly Игнорировать унаследованные члены
Instance Поиск экземплярных членов
Static Поиск статических членов
Public Поиск открытых членов
NonPublic Поиск внутренних членов
FlattenHierarchy Поиск статических членов, заданных в базовых типах

Методы класса Type, подобные GetFields(), имеют перегруженные версии, в которых одним из параметров выступает набор флагов BindingFlags. Изменим приведенный выше код, чтобы получать информацию об открытых и внутренних методах, определенных в самом классе Foo:

BindingFlags bf = BindingFlags.DeclaredOnly |

BindingFlags.Public | BindingFlags.NonPublic |

BindingFlags.Static | BindingFlags.Instance;

MethodInfo[] mi = t.GetMethods(bf);

// Далее по тексту примера...

Некоторые типы пространства System.Reflection, которые могут быть полезны при работе с метаданными, перечисдены в таблице 8.

Таблица 8

Избранные типы пространства имен System.Reflection

Тип Назначение
Assembly Класс для загрузки сборки, изучения ее состава и выполнения операций со сборкой
AssemblyName Класс для получения идентификационной информации о сборке
EventInfo Хранит информацию о событии
FieldInfo Хранит информацию о поле
MemberInfo Абстрактный класс для классов вида *Info
MethodInfo Хранит информацию о методе
Module Позволяет обратиться к модулю в многофайловой сборке
ParameterInfo Хранит информацию о параметре метода
PropertyInfo Хранит информацию о свойстве

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

Для демонстрации позднего связывания поместим класс Foo в динамическую библиотеку с именем FooLib.dll. Основное приложение будет загружать данную библиотеку и исследовать ее типы:

using System;

using System.Reflection;

using System.IO; //Нужно для FileNotFoundException

 

class MainClass {

public static void Main() {

Assembly A = null;

try {

//Используется текстовое имя без расширения

//Файл FooLib.dll находится в директории программы

A = Assembly.Load("FooLib");

} catch (FileNotFoundException e) {

Console.WriteLine(e.Message);

return;

}

foreach(Module M in A.GetModules())

foreach(Type T in M.GetTypes())

// Выводить особо нечего – одна строке Foo

Console.WriteLine(T.Name);

}

}

Позднее связывание не ограничивается загрузкой сборки и изучением состава ее элементов. При помощи позднего связывания можно создать объекты типов, определенных в сборке, а также работать с элементами созданных объектов (например, вызывать методы). Для создания определенного объекта используется метод CreateInstance() класса System.Activator. Существует несколько перегруженных версий данного метода. Можно использовать вариант, при котором в качестве параметра метода используется объект Type или строка-имя типа. Метод возвращает значение типа object. Класс MethodInfo имеет метод Invoke(), который позволяет вызвать метод объекта. Первый параметр метода Invoke() – это тот объект, у которого вызывается метод, второй параметр – массив объектов, представляющих параметры метода.

Представим листинг приложения, которое пытается интерактивно создавать объекты определенных типов и вызывать их методы (для простоты полностью отсутствует обработка исключительных ситуаций):

using System;

using System.Reflection;

using System.IO;

 

class MainClass {

public static void Main() {

//Просим пользователя ввести имя сборки и считываем ее

Console.Write("Enter the name of assembly: ");

string AssemblyName = Console.ReadLine();

Assembly A = Assembly.Load(AssemblyName);

 

//Перечисляем все типы в сборке

//(вернее, проходим по модулям сборки и ищем типы в них)

Console.WriteLine("Assembly {0} has types:", AssemblyName);

foreach(Module M in A.GetModules())

foreach(Type T in M.GetTypes())

Console.WriteLine(T.Name);

 

//Просим пользователя ввести имя типа

Console.Write("Enter namespace.typename: ");

string TypeName = Console.ReadLine();

 

//Запрашиваем указанный тип и создаем его экземпляр

Type UserType = A.GetType(TypeName);

object obj = Activator.CreateInstance(UserType);

 

//Перечисляем методы типа (с применением BindingFlags)

Console.WriteLine("Type {0} has methods:", TypeName);

BindingFlags bf = BindingFlags.DeclaredOnly |

BindingFlags.Public |

BindingFlags.NonPublic |

BindingFlags.Static |

BindingFlags.Instance;

foreach(MethodInfo m in UserType.GetMethods(bf))

Console.WriteLine("Method Name = " + m.Name);

 

//Просим пользователя ввести имя метода

Console.Write("Enter the name of method to call: ");

string MethodName = Console.ReadLine();

 

//Запарашиваем метод и выводим список его параметров

MethodInfo Method = UserType.GetMethod(MethodName);

Console.WriteLine("Method {0} has parameters:",

MethodName);

foreach(ParameterInfo pr in Method.GetParameters()) {

Console.WriteLine("Parameter Name = " + pr.Name);

Console.WriteLine("Parameter Type = " +

pr.ParameterType);

Console.WriteLine("****************************");

}

 

// Создаем пустой массив для фактических параметров

object[] paramArray =

new object[Method.GetParameters().Length];

//Вызываем метод

Method.Invoke(obj, paramArray);

}

}

2.2. ПОЛЬЗОВАТЕЛЬСКИЕ И ВСТРОЕННЫЕ АТРИБУТЫ

Помимо метаданных в платформе .NET представлена система атрибутов. Атрибуты позволяют определить дополнительную информацию, связанную с элементом метаданных и предоставляют механизм для обращения к этой информации в ходе выполнения программы. Следовательно, атрибуты позволяют динамически изменять поведение программы на основе анализа этой дополнительной информации.

Все атрибуты можно разделить на четыре группы:

1. Атрибуты, используемые компилятором. Информация, предоставляемая этими атрибутами, используется компилятором для генерации конечного кода (атрибуты выступают как своеобразные директивы компилятора).

2. Атрибуты, используемые средой исполнения.

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

4. Пользовательские атрибуты. Это атрибуты, созданные программистом.

Остановимся подробно на процессе создания пользовательского атрибута. Любой атрибут (в том числе и пользовательский) является классом. К классу атрибута предъявляются следующие требования: он должен быть потомком класса System.Attribute, имя класса должно заканчиваться суффиксом Attribute[2], атрибут должен иметь public-конструктор, на тип параметров конструктора атрибута, а также на тип его открытых полей и свойств наложены ограничения (тип может быть не произвольным, а только типом из определенного набора).

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

using System;

public class AuthorAttribute : Attribute {

private string fName;

private string fDate;

public AuthorAttribute(string name) {

fName = name;

}

public string Name {

get { return fName; }

}

public string CreationDate {

get { return fDate; }

set { fDate = value;}

}

}

Данный класс можно скомпилировать и поместить в отдельную сборку (оформленную в виде DLL) или отдельный модуль (.netmodule).

Далее мы можем применить созданный атрибут к произвольному классу:

using System;

//Применяем атрибут

//(Не забудьте добавить ссылку на сборку с атрибутом!)

[AuthorAttribute("Volosevich")]

class MainClass {

. . .

}

Рассмотрим синтаксис использования атрибутов подробнее. Атрибуты записываются в квадратных скобках. Можно записать несколько атрибутов через запятую в виде списка. Список атрибутов должен находиться перед тем элементом, к которому этот список применяется. Если возникает неоднозначность трактовки применения атрибута, то возможно использование специальных модификаторов – assembly, module, field, event, method, param, property, return, type. Например, запись вида [assembly: Имя_атрибута] означает применение атрибута к сборке. Если атрибут применяется к сборке или модулю, то он должен быть записан после секций импортирования using, но перед основным кодом. После имени атрибута указываются в круглых скобках параметры конструктора атрибута. Если конструктор атрибута не имеет параметров, круглые скобки можно не указывать. Для сокращения записи разрешено указывать имя атрибута без суффикса Attribute.

Наряду с параметрами конструктора при применении атрибута можно указать именованные параметры, предназначенные для задания значения открытого поля или свойства. При этом используется синтаксис Имя_поля_или_свойства = Значение-константа. Именованные параметры всегда записываются в конце списка параметров. Если некое поле или свойство инициализируется через параметр конструктора и через именованный параметр, то конечное значение элемента – это значение именованного параметра.

Таким образом, описанный нами ранее атрибут может быть использован для класса в следующем виде:

[Author("Volosevich", CreationDate = "18.03.2005")]

class MainClass {

. . .

}

Ранее упоминалось, что на тип параметров конструктора атрибута, а также на тип его открытых полей и свойств наложены определенный ограничения. Тип параметров, указываемых при использовании атрибута, ограничен следующим набором: типы bool, byte, char, short, int, long, float, double, string; тип System.Type; перечисления; тип object (фактическим параметром в этом случае должна быть константа одного из типов, перечисленного выше); одномерные массивы перечисленных выше типов. Этот набор ограничивает типы для открытых свойств и полей класса атрибута. Закрытые элементы атрибута могут иметь произвольный тип.

При создании пользовательских атрибутов полезным оказывается использование специального класса AttributeUsageAttribute. Как видно из названия, это атрибут, который следует применить к пользовательскому классу атрибута. Конструктор класса AttributeUsageAttribute принимает единственный параметр, определяющий область действия пользовательского атрибута. Допустимые значения параметра перечислены в таблице 9.

Таблица 9

Значения параметра конструктора класса AttributeUsageAttribute

Значение параметра Атрибут может применяться к …
AttributeTargets.All любому элементу, указанному далее
AttributeTargets.Assembly сборке
AttributeTargets.Class классу
AttributeTargets.Constructor конструктору
AttributeTargets.Delegate делегату
AttributeTargets.Enum перечислению
AttributeTargets.Event событию
AttributeTargets.Field полю
AttributeTargets.Interface интерфейсу
AttributeTargets.Method методу
AttributeTargets.Module модулю
AttributeTargets.Parameter параметру метода
AttributeTargets.Property свойству
AttributeTargets.ReturnValue возвращаемому значению функции
AttributeTargets.Struct структуре

Свойство AllowMultiple класса AttributeUsageAttribute определяет, может ли быть атрибут применен к программному элементу более одного раза. Тип данного свойства – bool, значение по умолчанию – false.

Свойство Inherited класса AttributeUsageAttribute определяет, будет ли атрибут проецироваться на потомков программного элемента. Тип данного свойства – bool, значение по умолчанию – true.

Используем возможности класса AttributeUsageAttribute при описании пользовательского атрибута:

// Атрибут Author можно применить к классу или методу,

// причем несколько раз

[AttributeUsage(AttributeTargets.Class |

AttributeTargets.Method,

AllowMultiple = true)]

public class AuthorAttribute : Attribute {

. . .

}

Опишем возможности получения информации об атрибутах. Для этой цели можно использовать метод GetCustomAttribute() класса System.Attribute. Имеется несколько перегруженных версий данного метода. Мы рассмотрим только одну из версий:

public static Attribute GetCustomAttribute(MemberInfo element,

Type attributeType)

При помощи параметра element задается требуемый элемент, у которого надо получить атрибут. Второй параметр – это тип получаемого атрибута. Возвращаемое функцией значение обычно приводится к типу получаемого атрибута. Рассмотрим следующий пример:

using System;

 

[Author("Volosevich", CreationDate = "18.03.2005")]

class SomeClass {

. . .

}

 

class MainClass {

public static void Main() {

Attribute A = Attribute.GetCustomAttribute(

typeof(SomeClass),

typeof(AuthorAttribute));

if (A != null)

Console.WriteLine(((AuthorAttribute)A).Name);

}

}

В данном примере к классу SomeClass был применен пользовательский атрибут AuthorAttribute. Затем в методе другого класса этот атрибут был прочитан, и из него извлеклась информация.

Следует иметь в виду, что объект, соответствующий классу атрибута, создается исполняющей средой только в тот момент, когда из атрибута извлекается информация. Задание атрибута перед некоторым элементом к созданию объекта не приводит. Количество созданных экземпляров атрибута равно количеству запросов к данным атрибута[3].

Метод Attribute.GetCustomAttributes() позволяет получить все атрибуты некоторого элемента в виде массива объектов. Одна из перегруженных версий данного метода описана следующим образом:

public static Attribute[] GetCustomAttributes(MemberInfo element);

Модифицируем предыдущий пример, использовав GetCustomAttributes:

using System;

 

[Author("Volosevich"), Author("Ivanov")]

class SomeClass {

. . .

}

 

class MainClass {

public static void Main() {

Attribute[] A;

A = Attribute.GetCustomAttributes(typeof(SomeClass));

for(int i = 0; i < A.Length; i++)

if(A[i] is AuthorAttribute)

Console.WriteLine(((AuthorAttribute)A[i]).Name);

}

}

Платформа .NET предоставляет для использования обширный набор атрибутов, некоторые из которых представлены в таблице 10:

Таблица 10

Некоторые атрибуты, применяемые в .NET Framework

Имя атрибута Область применения Описание
AttributeUsage Класс Задает область применения класса-атрибута
Conditional Метод Компилятор может игнорировать вызовы помеченного метода при заданном условии
DllImport Метод Указывает DLL, содержащую реализацию метода
MTAThread Метод (Main) Для приложения используется модель COM Multithreaded apartment
NonSerialized Поле Указывает, что поле не будет сериализовано
Obsolete Любая, исключая assembly, module, param, return Информирует, что в будущих реализациях данный элемент может отсутствовать
ParamArray Параметр Позволяет одиночному параметру быть обработанным как набор параметров params
Serializable Класс, структура, перечисление, делегат Указывает, что все поля типа могут быть сериализованы
STAThread Метод (Main) Для приложения используется модель COM Single-threaded apartment
StructLayout Класс, структура Задает схему размещения данных класса или структуры в памяти (Auto, Explicit, Sequential)
ThreadStatic Статическое поле В каждом потоке будет использоваться собственная копия данного статического поля

Атрибут DllImport предназначен для импортирования функций из библиотек динамической компоновки, написанных на «родном» языке процессора. В следующем примере данный атрибут используется для импортирования системной функции MessageBoxA():

using System;

using System.Runtime.InteropServices;

 

class MainClass {

[DllImport("user32.dll")]

public static extern int MessageBoxA(int h, string m,

string c, int type);

public static void Main() {

MessageBoxA(0, "Hello World", "nativeDLL", 0);

}

}

Обратите внимание, что для использования атрибута DllImport требуется подключить пространство имен System.Runtime.InteropServices. Кроме это, необходимо объявить импортируемую функцию статической и пометить ее модификатором extern. Атрибут DllImport допускает использование дополнительных параметров, подробное описание которых можно найти в документации.

Исполняемая среда .NET выполняет корректную передачу параметров примитивных типов между управляемым и неуправляемым кодом. Для правильной передачи параметров-структур требуется использование специального атрибута StructLayout при объявлении пользовательского типа, соответствующего структуре. Например, пусть выполняется экспорт системной функции GetLocalTime():

[DllImport("kernel32.dll")]

public static extern void GetLocalTime(SystemTime st);

В качестве параметра используется объект класса SystemTime. Этот класс должен быть описан следующим образом:

[StructLayout(LayoutKind.Sequential)]

public class SystemTime {

public ushort wYear;

public ushort wMonth;

public ushort wDayOfWeek;

public ushort wDay;

public ushort wHour;

public ushort wMinute;

public ushort wSecond;

public ushort wMilliseconds;

}

Атрибут StructLayout указывает, что поля объекта должны быть расположены в памяти в точности так, как это записано в объявлении класса (LayoutKind.Sequential). В противном случае при работе с системной функцией возможно возникновение ошибок.

Атрибут Conditional (описан в пространстве имен System.Diagnostics) может быть применен к любому методу, возвращающему значение void. Параметром атрибута является строковый литерал. Применение атрибута указывает компилятору, что вызовы помеченного метода следует опускать, если в проекте не определен строковый литерал. Задание литерала производится либо директивой препроцессора #define в начале текста программы (#define D), либо параметром компилятора /define:<symbol list>, либо в диалогах настройки интегрированной среды.

Рассмотрим пример использования атрибута Conditional:

using System;

using System.Diagnostics;

 

class MainClass {

[Conditional("D")]

public static void SomeDebugFunc() {

Console.WriteLine("SomeDebugFunc");

}

public static void Main() {

SomeDebugFunc();

Console.WriteLine("Hello!");

}

}

Если в проекте не определен параметр D (а так, естественно, и будет по умолчанию), вызова метода SomeDebugFunc() не произойдет. В принципе, подобный эффект достигается использованием директив препроцессора:

public static void Main() {



<== предыдущая лекция | следующая лекция ==>
ВВЕДЕНИЕ | ТЕхнология .NET Remoting 1 страница


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Полезен материал? Поделись:

Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.016 сек.