Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Позднее связывание и кодогенерация






Механизм отражения позволяет реализовать на платформе.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));

 






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.