Московский государственный технический университет им. Н.Э. Баумана
Опубликован: 28.06.2006 | Доступ: свободный | Студентов: 12463 / 344 | Оценка: 4.54 / 3.83 | Длительность: 22:03:00
ISBN: 978-5-9556-0055-0
Лекция 10:

Динамическая генерация кода

Обобщенный алгоритм интегрирования

Для интегрирования функций нам потребуется некое представление функции, которое бы не зависело от конкретного способа вычисления значения функции. Идеальным вариантом такого представления является абстрактный класс Function:

public abstract class Function
{
 public abstract double Eval(double x);
}

Объявив такой класс, мы предполагаем, что от него будут наследоваться другие классы, реализующие в методе Eval конкретный способ вычисления значения функции в заданной точке.

Имея класс Function, мы можем записать обобщенный алгоритм интегрирования методом прямоугольников. В качестве параметров этот алгоритм принимает объект f, представляющий интегрируемую функцию, пределы интегрирования a и b, а также количество разбиений n:

static double Integrate(Function f, double a, double b, int n)
{
  double h = (b-a)/n, sum = 0.0;
  for (int i = 0; i < n; i++)
    sum += h*f.Eval((i+0.5)*h);
  return sum;
}

Для проверки работоспособности алгоритма можно объявить тестовый класс TestFunction, реализующий вычисление функции f(x) = x * sin(x):

public class TestFunction: Function
{
 public override double Eval(double x)
  {
     return x * Math.Sin(x);
  }
}

Представление выражений

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

Детали синтаксического анализа и представления выражений мы рассматривать не будем: интересующиеся могут обратиться к полным исходным текстам примера. Скажем лишь, что анализ осуществляется методом рекурсивного спуска и транслирует выражение в дерево, в узлах которого расположены объекты, представляющие арифметические операции и их операнды. Каждый из этих объектов является экземпляром одного из четырех классов, обозначающих числовые константы, переменные, унарные и бинарные операции. Причем все эти классы наследуют от абстрактного класса Expression:

public abstract class Expression
{
 public abstract string GenerateCS();
 public abstract void GenerateCIL(ILGenerator il);
 public abstract double Evaluate(double x);
}

В классе Expression объявлены три абстрактных метода, которые каждый класс-наследник реализует по-своему. Метод Evaluate выполняет непосредственное вычисление значения выражения, метод GenerateCS транслирует выражение в фрагмент программы на C#, а метод GenerateCIL транслирует выражение в CIL-код.

Вопросы генерации кода будут обсуждаться в следующих разделах, поэтому сейчас мы только приведем пример CIL-кода, генерируемого методом GenerateCIL для дерева объектов, которые представляют выражение "2*x*x*x+3*x*x+4*x+5":

ldc.r8   2.0
ldarg.1
mul
ldarg.1
mul
ldarg.1
mul
ldc.r8   3.0
ldarg.1
mul
ldarg.1
mul
add
ldc.r8   4.0
ldarg.1
mul
add
ldc.r8   5.0
add

Метод GenerateCS фактически восстанавливает из дерева строковое представление выражения и в особых комментариях не нуждается.

Анастасия Булинкова
Анастасия Булинкова
Рабочим названием платформы .NET было