Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Базовые технологии платформы .NET.docx
Скачиваний:
13
Добавлен:
03.11.2018
Размер:
614.46 Кб
Скачать

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

Механизм отражения позволяет реализовать на платформе .NET позднее связывание (late binding). Этот термин обозначает процесс динамической загрузки типов при работе приложения, создание экземпляров типов и работу с элементами экземпляров.

Продемонстрируем позднее связывание на примере класса SimpleClass, размещённого в локальной сборке SimpleLibrary.dll.

namespace SimpleLibrary

{

public class SimpleClass

{

public SimpleClass() { }

public SimpleClass(int offset) { Offset = offset; }

public int Offset { get; set; }

public int Sum(int x) { return x + Offset; }

}

}

Первый этап позднего связывания – загрузка в память сборки с типом – выполняется при помощи метода Assembly.Load(). Для указания имени сборки можно использовать простую строку или объект класса AssemblyName.

AssemblyName assemblyName = new AssemblyName("SimpleLibrary");

// возможные исключения не обрабатываются!

Assembly assembly = Assembly.Load(assemblyName);

После загрузки сборки нужно создать объект требуемого типа. Для этого следует воспользоваться экземплярным методом Assembly.CreateInstance()1.

var typeName = "SimpleLibrary.SimpleClass";

// короткая версия

var o1 = (SimpleLibrary.SimpleClass)assembly.CreateInstance(typeName);

// полная версия, можно задать параметры конструктора (5-й аргумент)

var o2 = (SimpleLibrary.SimpleClass)assembly.CreateInstance(typeName,

false, BindingFlags.Default, null, new object[] {10},

CultureInfo.InvariantCulture, null);

В листинге, приведённом ниже, имя сборки, имя типа и метод объекта запрашиваются у пользователя в процессе выполнения программы.

Console.Write("Input assembly name: ");

Assembly assembly = Assembly.Load(Console.ReadLine());

Console.Write("Input full typename: ");

Type type = assembly.GetType(Console.ReadLine());

object obj = Activator.CreateInstance(type);

Console.Write("Input method name: ");

MethodInfo method = type.GetMethod(Console.ReadLine());

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

var paramArray = new object[method.GetParameters().Length];

// вызываем метод, указывая целевой объект и набор аргументов

method.Invoke(obj, paramArray);

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

Средства генерации инструкций промежуточного языка CIL сосредоточены в пространстве имён System.Reflection.Emit. Данные средства востребованы, в основном, разработчиками компиляторов для платформы .NET.

Пространство имён System.CodeDom содержит типы для описания структуры документа с исходным кодом программы. Чтобы получить по такому документу листинг на выбранном языке программирования и скомпилировать его, нужны типы из пространства имён System.CodeDom.Compiler.

var compileUnit = new CodeCompileUnit(); // единица компиляции

// опишем пространство имён и свяжем его с единицей компиляции

var namespase = new CodeNamespace("SimpleLibrary");

namespase.Imports.Add(new CodeNamespaceImport("System"));

compileUnit.Namespaces.Add(namespase);

// опишем очень простой класс

var type = new CodeTypeDeclaration {

Name = "SimpleClass",

IsClass = true,

TypeAttributes = TypeAttributes.Public};

namespase.Types.Add(type);

// создадим элемент класса (поле Offset)

var field = new CodeMemberField {

Name = "Offset",

Type = new CodeTypeReference("System.Int32"),

Attributes = MemberAttributes.Public};

type.Members.Add(field);

// создадим исходник на языке C# и запишем его в файл

CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

using (var stream = new StreamWriter("SimpleClass.cs"))

{

provider.GenerateCodeFromCompileUnit(compileUnit, stream, null);

}

Деревья выражений описывают код в виде данных, которые хранятся в древовидной структуре. Каждый узел в дереве представляет выражение, например, вызов метода или бинарную операцию. Для хранения деревьев выражений служит класс System.Linq.Expressions.Expression<T>.

Простейший способ создать дерево выражений в C# – использовать возможности компилятора, генерирующего деревья для лямбда-выражений:

Func<int, bool> lambda = n => n < 5; // обычная лямбда

Expression<Func<int, bool>> tree = n => n < 5; // дерево

Пространство имён System.Linq.Expressions предоставляет программный интерфейс для создания деревьев выражений. Класс Expression содержит статические методы, которые создают узлы дерева выражений особых типов. Например, выражение ParameterExpression представляет именованный параметр, а выражение LoopExpression описывает цикл.

// построим вручную дерево выражений для лямбды n => n < 5

ParameterExpression n = Expression.Parameter(typeof(int), "n");

ConstantExpression five = Expression.Constant(5, typeof(int));

BinaryExpression nLessFive = Expression.LessThan(n, five);

Expression<Func<int, bool>> tree =

Expression.Lambda<Func<int, bool>>(nLessFive, 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));