Динамические типы – новый элемент языка C# и платформы .NET четвёртой версии. При использовании динамического типа компилятор не выполняет у объекта проверку элементов типа. Такая проверка происходит при выполнении кода, после связывания динамической переменной с конкретным объектом.
Мотивом введения динамических типов была необходимость упростить работу C#-кода с COM-объектами и сценарными языками. Ниже приведён листинг, в котором выполняется взаимодействие с Microsoft Excel.
// получаем тип, соответствующий объекту Microsoft Excel
Type xlAppType = Type.GetTypeFromProgID("Excel.Application");
// объявляем переменную, используя динамический тип
dynamic xl = Activator.CreateInstance(xlAppType);
// вызываем некоторые методы Excel
xl.Visible = true; // показываем окно Excel
dynamic workbooks = xl.Workbooks; // создаем новый лист Excel
workbooks.Add(-4167);
xl.Cells[1, 1].Value2 = "C# Rocks!"; // заполняем ячейку листа
Пример показывает, что объект динамического типа объявляется при помощи ключевого слова dynamic. У такого объекта можно записать вызов любого метода или свойства, это не влияет на компиляцию. С точки зрения компилятора dynamic является эквивалентом object. Задача компилятора – «упаковать» информацию о действиях, производимых с динамическим объектом, чтобы среда исполнения могла правильно таким объектом распорядиться.
// объявляем простой класс
public class Foo
{
public void Do(string s) { Console.WriteLine(s); }
}
// код компилируется (!),
// но при выполнении 3-я строка генерирует исключение
dynamic obj = new Foo();
obj.Do("Hello");
obj.Prop = 3; // исключение!
Действия среды исполнения зависят от вида объекта, связываемого с динамической переменной:
-
Обычные объекты .NET – элементы типа определяются при помощи механизма отражения, работа с элементами происходит при помощи позднего связывания.
-
Объект, реализующий интерфейс IDynamicMetaObjectProvider – сам объект запрашивается о том, содержит ли он заданный элемент. В случае успеха работа с элементом делегируется объекту.
-
COM-объекты – работа с элементами происходит через интерфейс IDispatch.
Интерфейс IDynamicMetaObjectProvider позволяет разработчикам создавать типы, обладающие динамическим поведением. Обычно данный интерфейс не реализуется напрямую, а выполняется наследование от класса DynamicObject (интерфейс и класс находятся в пространстве имён System.Dynamic). Ниже приведён пример класса, унаследованного от DynamicObject.
public class MyDynamicType : DynamicObject
{
public override bool TryInvokeMember(InvokeMemberBinder binder,
object[] args, out object result)
{
Console.WriteLine("Вызов {0}.{1}()",
GetType(), binder.Name);
result = null;
return true;
}
public override bool TrySetMember(SetMemberBinder binder,
object value)
{
Console.WriteLine("Установка {0}.{1} в значение {2}",
GetType(), binder.Name, value);
return true;
}
public void DoDefaultWork()
{
Console.WriteLine("Некое действие");
}
}
// пример использования
dynamic d = new MyDynamicType();
d.DoDefaultWork(); // "Некое действие"
d.DoWork(); // "Вызов MyDynamicType.DoWork()"
d.Value = 42; // "Установка MyDynamicType.Value в значение 42"
d.Count = 12; // "Установка MyDynamicType.Count в значение 12"
Класс System.Dynamic.ExpandoObject позволяет при выполнении программы добавлять и удалять элементы своего экземпляра:
public sealed class ExpandoObject : IDynamicMetaObjectProvider,
IDictionary,
INotifyPropertyChanged
Благодаря динамическому типизированию, работа с пользовательскими элементами ExpandoObject происходит как работа с обычными элементами объекта. Ниже приведён пример расширения ExpandoObject двумя свойствами и методом (в виде делегата).
dynamic sample = new ExpandoObject();
sample.Caption = "Свойство"; // добавляем свойство Caption
sample.Number = 10; // и числовое свойство Number
sample.Increment = (Action)(() => { sample.Number++; });
// работаем с объектом sample
Console.WriteLine(sample.Caption); // Свойство
Console.WriteLine(sample.Caption.GetType()); // System.String
sample.Increment();
Console.WriteLine(sample.Number); // 11
Объект ExpandoObject явно реализует IDictionary. Это позволяет инспектировать элементы объекта при выполнении программы. Также при помощи словаря удаляются элементы объекта ExpandoObject.
dynamic employee = new ExpandoObject();
employee.Name = "John Smith";
employee.Age = 33;
foreach (var property in (IDictionary)employee)
{
Console.WriteLine(property.Key + ": " + property.Value);
}
((IDictionary)employee).Remove("Name");
Реализация в ExpandoObject интерфейса INotifyPropertyChanged позволяет получать уведомления при изменении свойств объекта ExpandoObject.
2.25. АТРИБУТЫ
Помимо метаданных, в платформе .NET представлена система атрибутов. Атрибуты позволяют определить дополнительную информацию в метаданных, связанных с типом, и дают механизм для обращения к этой информации в ходе компиляции или выполнения программы.
Согласно синтаксису C#, чтобы использовать атрибут, нужно записать его имя в квадратных скобках перед тем элементом, к которому он относится. Разрешено указывать имя атрибута без суффикса Attribute, который обязателен для всех атрибутов. Можно задать в квадратных скобках несколько атрибутов через запятую. Если возникает неоднозначность трактовки цели атрибута, то нужно указать перед именем атрибута специальный префикс – assembly, module, field, event, method, param, property, return, type. Например, запись [assembly: AssemblyKeyFile] означает применение атрибута к сборке1. Любой атрибут является классом, производным от System.Attribute, а применение атрибута условно соответствует созданию объекта. Поэтому после имени атрибута указываются в круглых скобках аргументы конструктора атрибута. Если у атрибута конструктор без параметров, круглые скобки можно не писать.
При применении атрибута наряду с аргументами конструктора можно указать именованные параметры, предназначенные для задания значения открытого поля или свойства. При этом используется синтаксис имя_элемента = значение-константа. Именованные параметры всегда записываются в конце списка аргументов конструктора.
Платформа .NET предоставляет для использования обширный набор атрибутов, некоторая часть которых представлена в табл. 19.
Таблица 19
Некоторые атрибуты, применяемые в платформе .NET
Имя атрибута
|
Область применения
|
Описание
|
AttributeUsage
|
Класс
|
Задает область применения класса-атрибута
|
Conditional
|
Метод
|
Компилятор может игнорировать вызовы помеченного метода при заданном условии
|
DllImport
|
Метод
|
Импорт функций из DLL
|
MTAThread
|
Метод Main()
|
Для приложения используется модель COM Multithreaded apartment
|
NonSerialized
|
Поле
|
Указывает, что поле не будет сериализовано
|
Obsolete
|
Кроме param, assembly, module, return
|
Информирует, что в будущих реализациях данный элемент может отсутствовать
|
ParamArray
|
Параметр
|
Позволяет одиночному параметру быть обработанным как набор параметров params
|
Serializable
|
Класс, структура, перечисление, делегат
|
Указывает, что все поля типа могут быть сериализованы
|
STAThread
|
Метод Main()
|
Для приложения используется модель COM Single-threaded apartment
|
StructLayout
|
Класс, структура
|
Задает схему размещения данных класса или структуры в памяти (Auto, Explicit, Sequential)
|
ThreadStatic
|
Статическое поле
|
В каждом потоке будет использоваться собственная копия данного статического поля
|
Рассмотрим единичный пример использования стандартных атрибутов. Атрибуты применяются для настройки взаимодействия программ платформы .NET и библиотек на неуправляемом коде. Атрибут [DllImport] предназначен для импортирования функций из библиотек динамической компоновки, написанных на неуправляемом коде. В следующей программе показан импорт системной функции MessageBoxA():
using System.Runtime.InteropServices;
public class MainClass
{
[DllImport("user32.dll")]
public static extern int MessageBoxA(int hWnd, string text,
string caption, uint type);
public static void Main()
{
MessageBoxA(0, "Hello World", "nativeDLL", 0);
}
}
Для использования атрибута [DllImport] требуется подключить пространство имен System.Runtime.InteropServices. Кроме этого, необходимо объявить импортируемую функцию статической и пометить её модификатором extern. Атрибут [DllImport] допускает использование дополнительных параметров, подробное описание которых можно найти в документации MSDN.
Исполняемая среда .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). В противном случае при работе с системной функцией вероятно возникновение ошибок.
Кроме использования готовых атрибутов возможна их самостоятельная разработка. Чтобы создать атрибут, нужно написать класс, удовлетворяющий перечисленным ниже требованиям:
-
Класс должен быть потомком класса System.Attribute.
-
Имя класса должно заканчиваться суффиксом Attribute1.
-
Тип открытых полей и свойств класса, а также параметров конструктора ограничен следующим набором: bool, byte, char, short, int, long, float, double, string; тип System.Type; перечисления; тип object; одномерные массивы перечисленных выше типов.
Пусть нужно создать атрибут, предназначенный для указания автора и даты создания некоторого элемента кода. Для этого опишем следующий класс:
public class AuthorAttribute : Attribute
{
public string Name { get; private set; }
public string CreationDate { get; set; }
public AuthorAttribute(string name)
{
Name = name;
}
}
Далее можно применить атрибут [Author] к произвольному типу:
[Author("Developer")]
public class A { . . . }
[Author("Developer", CreationDate = "01.01.2010")]
public struct B { . . . }
При создании пользовательских атрибутов полезным оказывается использование специального класса AttributeUsageAttribute. Как видно из названия, это атрибут, который следует применить к пользовательскому классу атрибута. Конструктор класса AttributeUsageAttribute принимает единственный параметр – набор элементов перечисления AttributeTargets, определяющих область действия пользовательского атрибута (класс, метод, сборка и т. д.). Булево свойство AllowMultiple определяет, может ли атрибут быть применён к программному элементу более одного раза. Булево свойство Inherited указывает, будет ли атрибут проецироваться на потомков программного элемента.
Используем возможности класса AttributeUsageAttribute при описании пользовательского атрибута:
// атрибут Author можно применить к классу или методу несколько раз
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
AllowMultiple = true)]
public class AuthorAttribute : Attribute { . . . }
Опишем возможности получения информации о применённых атрибутах. Метод Attribute.GetCustomAttributes() возвращает все атрибуты некоторого элемента в виде массива. Метод Attribute.GetCustomAttribute() получает атрибут заданного типа:
Attribute GetCustomAttribute(MemberInfo element, Type attributeType)
При помощи параметра element задается элемент, у которого надо получить атрибут. Второй параметр – это тип получаемого атрибута.
[Author("Developer", CreationDate = "01.01.2010")]
public class SomeClass { . . . }
// пример получения атрибута
var author = Attribute.GetCustomAttribute(typeof(SomeClass),
typeof(AuthorAttribute));
if (author != null)
{
Console.WriteLine(((AuthorAttribute) author).Name);
}
Следует иметь в виду, что объект, соответствующий классу атрибута, создаётся исполняющей средой только в тот момент, когда из атрибута извлекается информация. Задание атрибута перед некоторым элементом к созданию объекта не приводит. Количество созданных экземпляров атрибута равно количеству запросов к данным атрибута1.
Поделитесь с Вашими друзьями: |