Атрибуты, сборки, рефлексия
Реализация отражения. Type, InvokeMember, BindingFlags
Сначала – определения.
Раннее (статическое) связывание – деятельность, выполняемая на стадии компиляции и позволяющая:
- обнаружить и идентифицировать объявленные в приложении типы;
- выявить и идентифицировать члены класса;
- подготовить при выполнении приложения вызов методов и свойств, доступ к значениям полей – членов класса.
Позднее (динамическое) связывание – деятельность, выполняемая непосредственно при выполнении приложения и позволяющая:
- обнаружить и идентифицировать объявленные в приложении типы;
- выявить и идентифицировать члены класса;
- обеспечить в ходе выполнения приложения вызов методов и свойств, доступ к значениям полей – членов класса.
При этом вызов методов и свойств при выполнении приложения обеспечивается методом InvokeMember. Этот метод выполняет достаточно сложную работу и поэтому нуждается в изощренной системе управления, для реализации которой применяется перечисление BindingFlags. В рамках этого перечисления определяются значения флажков, которые управляют процессом динамического связывания в ходе реализации отражения.
Перечисление также применяется для управления методом GetMethod.
Список элементов перечисления прилагается.
Далее демонстрируется применение класса Type, в частности варианты использования метода – члена класса Type InvokeMember, который обеспечивает выполнения методов и свойств класса.
using System;
using System.Reflection;
// В классе объявлены поле myField, конструктор, метод String ToString(), свойство.
class MyType
{
int myField;
public MyType(ref int x)
{
x *= 5;
}
public override String ToString()
{
Console.WriteLine("This is: public override String ToString() method!");
return myField.ToString();
}
// Свойство MyProp нашего класса обладает одной замечательной особенностью:
// значение поля myField объекта - представителя класса MyType не может
// быть меньше нуля. Если это ограничение нарушается – возбуждается
// исключение.
public int MyProp
{
get
{
return myField;
}
set
{
if (value < 1)
throw new ArgumentOutOfRangeException("value", value, "value must be > 1");
myField = value;
}
}
}
class MyApp
{
static void Main()
{
// Создали объект - представитель класса Type
// на основе объявления класса MyType.
Type t = typeof(MyType);
// А это одномерный массив объектов, содержащий ОДИН элемент.
// В этом массиве будут передаваться параметры конструктору.
Object[] args = new Object[] {8};
Console.WriteLine("The value of x before the constructor is called is {0}.", args[0]);
// Вот таким образом в рамках технологии отражения производится
// обращение к конструктору. Наш объект адресуется по ссылке obj.
Object obj = t.InvokeMember(
null,
//____________________________
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.CreateInstance, // Вот распоряжение о создании объекта...
//____________________________
null,
null,
args // А так организуется передача параметров в конструктор.
);
Console.WriteLine("Type: " + obj.GetType().ToString());
Console.WriteLine("The value of x after the constructor returns is {0}.", args[0]);
// Изменение (запись и чтение) значения поля myField только что
// созданного объекта - представителя класса MyType.
// Как известно, этот объект адресуется по
// ссылке obj. Мы сами его по этой ссылке расположили!
t.InvokeMember(
"myField", // Будем менять значение поля myField...
//______________________________
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.SetField, // Вот инструкция по изменению значения поля.
//_______________________________
null,
obj, // Вот указание на то, ГДЕ располагается объект...
new Object[] {5} // А вот и само значение. Оно упаковывается в массив объектов.
);
int v = (Int32) t.InvokeMember(
"myField",
//______________________________
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.GetField, // А сейчас мы извлекаем значение поля myField.
//______________________________
null,
obj, // "Работаем" все с тем же объектом. Значение поля myField
// присваивается переменной v.
null
);
// Вот распечатали это значение.
Console.WriteLine("myField: " + v);
// "От имени" объекта будем вызывать нестатический метод.
String s = (String) t.InvokeMember(
"ToString", // Имя переопределенного виртуального метода.
//______________________________
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.InvokeMethod, // Сомнений нет! Вызываем метод!
//______________________________
null,
obj, // От имени нашего объекта вызываем метод без параметров.
null
);
// Теперь обращаемся к свойству.
Console.WriteLine("ToString: " + s);
// Изменение значения свойства. Пытаемся присвоить недозволенное
// значение. И посмотрим, что будет...
// В конце концов, мы предусмотрели перехватчик исключения.
try
{
t.InvokeMember(
"MyProp", // Работаем со свойством.
//______________________________
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.SetProperty, // Установить значение свойства.
//______________________________
null,
obj,
new Object[] {0} // Пробуем через обращение к свойству
// установить недозволенное значение.
);
}
catch (TargetInvocationException e)
{
// Фильтруем исключения... Реагируем только на исключения типа
// ArgumentOutOfRangeException. Все остальные "проваливаем дальше".
if (e.InnerException.GetType() !=typeof (ArgumentOutOfRangeException)) throw;
// А вот как реагируем на ArgumentOutOfRangeException.
// Вот так скромненько уведомляем о попытке присвоения запрещенного
// значения.
Console.WriteLine("Exception! Catch the property set.");
}
t.InvokeMember(
"MyProp",
//______________________________
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.SetProperty, // Установить значение свойства.
//______________________________
null,
obj,
new Object[] {2} // Вновь присваиваемое значение. Теперь ПРАВИЛЬНОЕ.
);
v = (int) t.InvokeMember(
"MyProp",
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance |
BindingFlags.GetProperty, // Прочитать значение свойства.
null,
obj,
null
);
Console.WriteLine("MyProp: " + v);
}
}
Листинг
10.1.
Ну вот. Создавали объект, изменяли значение его поля (данного-члена), вызывали его (нестатический) метод, обращались к свойству (подсовывали ему некорректные значения). И при этом НИ РАЗУ НЕ НАЗЫВАЛИ ВЕЩИ СВОИМИ ИМЕНАМИ! В сущности, ЭТО И ЕСТЬ ОТРАЖЕНИЕ.