Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Позднее связывание и кодогенерация
Механизм отражения позволяет реализовать на платформе.NET позднее связывание (late binding). Этот термин обозначает процесс динамической загрузки сборок и типов при работе приложения, создание экземпляров типов и работу с их элементами. Использование позднего связывания разберём на следующем примере. Пусть существует класс для хранения информации о человеке, и эта информация включает уникальный числовой идентификатор: namespace Domain { public class Person { public int Id { get; set; } public string Name { get; set; } } } Пусть имеется интерфейс, который описывает операции чтения и записи данных определённого человека из некоего хранилища: namespace Domain { public interface IRepository { Person Load(int id); void Save(Person person); } } Класс Person и интерфейс IRepository размещаются в сборке Domain.dll. Создадим сборку Data.dll, ссылающуюся на Domain.dll и предоставляющую реализацию интерфейса IRepository. using System.IO; using System.Runtime.Serialization; using Domain;
namespace Data { public class XmlRepository: IRepository { public Person Load(int id) { var ds = new DataContractSerializer(typeof (Person)); using (Stream s = File.OpenRead(CreateName(id))) { return (Person) ds.ReadObject(s); } }
public void Save(Person person) { var ds = new DataContractSerializer(typeof (Person)); using (Stream s = File.OpenWrite(CreateName(person.Id))) { ds.WriteObject(s, person); } }
private string CreateName(int id) { return id.ToString() + ".xml"; } } } Теперь создадим консольное приложение MainApp.exe, которое записывает и читает объекты Person. Этот приложение ссылается на сборку Domain.dll, а со сборкой Data.dll будет работать опосредованно, без прямых ссылок. Такая архитектура позволит подменять механизм сохранения объектов Person, заменяя сборку Data.dll другой реализацией интерфейса IRepository. Рис. 10. Архитектура примера для работы с объектами Person. Применим позднее связывание для работы с типами из сборки Data.dll. Первый этап позднего связывания – загрузка в память сборки с типом – выполняется при помощи метода Assembly.Load(). Для указания имени сборки можно использовать простую строку или объект класса AssemblyName. // имя сборки обычно хранят в файле конфигурации var assemblyName = new AssemblyName(" Data"); try { Assembly assembly = Assembly.Load(assemblyName); // здесь поместим код создания объекта
} catch (FileNotFoundException) { Console.WriteLine(" Data.dll was not found"); } После загрузки сборки создадим объект требуемого типа. Для этого можно воспользоваться статическим методом Activator.CreateInstance() или экземплярным методом класса Assembly с тем же именем: // имя типа обычно хранят в файле конфигурации Type type = assembly.GetType(" Data.XmlRepository"); var repository = (IRepository) Activator.CreateInstance(type); Методы CreateInstance() имеют множество перегрузок. Например, существует версия метода, принимающая массив объектов – аргументы конструктора создаваемого типа. В нашем примере известно, что созданный объект реализует интерфейс IRepository, поэтому можно вызывать методы объекта обычным способом: var person = new Person {Id = 12, Name = " John Doe" }; repository.Save(person); Person clone = repository.Load(12); Если при создании приложения нет информации об интерфейсе объекта, методы объекта можно вызвать альтернативными способами. Например, класс MethodInfo содержит экземплярный метод Invoke(). Его аргументы – целевой объект вызова и массив аргументов метода: // модификация двух предыдущих фрагментов кода Type type = assembly.GetType(" Data.XmlRepository"); object repository = Activator.CreateInstance(type);
MethodInfo mi = type.GetMethod(" Load"); var person = (Person) mi.Invoke(repository, new object[] {12}); Вызов метода при помощи отражения – медленная операция. Если так планируется вызывать метод несколько раз, выгоднее создать объект делегата, инкапсулирующий метод: MethodInfo mi = type.GetMethod(" Load"); var load = (Func< int, Person>) Delegate.CreateDelegate( typeof (Func< int, Person>), // тип делегата repository, // целевой объект mi); // метаинформация метода Person person = load(12); Механизм отражения позволяет не только исследовать готовые типы и выполнять для них позднее связывание, но и динамически создавать типы. Платформа.NET имеет средства генерации метаданных и инструкций языка CIL, сосредоточенные в пространстве имён System.Reflection.Emit. Рассмотрим простой пример – создание сборки и класса с единственным методом. // 1. Создадим сборку // 1a. Для этого получим домен (о них будет рассказано позднее) AppDomain domain = AppDomain.CurrentDomain;
// 1b. Сформируем имя сборки AssemblyName name = new AssemblyName(" Library");
// 1c. Получаем сборку, которую затем собираемся сохранять AssemblyBuilder ab = domain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
// 2. В новой сборке определим модуль ModuleBuilder mb = ab.DefineDynamicModule(" Main", " Library.dll");
// 3. В модуле создадим класс Widget с уровнем доступа public TypeBuilder tb = mb.DefineType(" Widget", TypeAttributes.Public);
// 4. В класс добавим метод SayHello() без параметров MethodBuilder method = tb.DefineMethod(" SayHello", MethodAttributes.Public, null, null);
// 5. Формируем тело метода при помощи инструкций CIL ILGenerator gen = method.GetILGenerator(); gen.EmitWriteLine(" Hello world"); gen.Emit(OpCodes.Ret);
// 6. Завершаем создание класса Type t = tb.CreateType();
// 7. Сохраняем полученную сборку ab.Save(" Library.dll");
// 8. Работаем с нашим классом, используя позднее связывание var o = Activator.CreateInstance(t); t.GetMethod(" SayHello").Invoke(o, null); // Hello world Ещё одно средство динамического создания кода предоставляют деревья выражений. Они описывают код в виде древовидной структуры. Каждый узел в дереве представляет выражение (например, вызов метода или бинарную операцию) и является объектом класса, унаследованного от Expression (пространство имён System.Linq.Expressions). Рис 11 демонстрирует некоторые классы-выражения. Рис. 11. Классы, унаследованные от Expression. Универсальный класс Expression< T> позволяет создать дерево выражений на основе лямбда-выражения: Func< int, bool> lambda = n => n < 5; // обычная лямбда Expression< Func< int, bool> > tree = n => n < 5; // дерево Класс Expression содержит статические методы, которые конструируют узлы дерева выражений особых типов: // построим вручную дерево выражений для лямбды n => n < 5 ParameterExpression n = Expression.Parameter(typeof (int), " n"); ConstantExpression five = Expression.Constant(5, typeof (int)); BinaryExpression compare = Expression.LessThan(n, five); Expression< Func< int, bool> > tree = Expression.Lambda< Func< int, bool> > (compare, new[] {n}); У созданного дерева выражений можно исследовать структуру, изменять элементы и компилировать его в инструкции CIL: Expression< Func< int, bool> > tree = n => n < 5;
// декомпозиция дерева выражений var param = tree.Parameters[0]; var op = (BinaryExpression) tree.Body; var left = (ParameterExpression) op.Left; var right = (ConstantExpression) op.Right; Console.WriteLine(" {0} => {1} {2} {3}", param.Name, left.Name, op.NodeType, right.Value);
// компиляция дерева и вызов лямбды Func< int, bool> lambda = tree.Compile(); Console.WriteLine(lambda(10));
|