Метод выделяет вычисление или действие в именованную операцию с явной границей. Аргументы связываются с параметрами, каждый вызов создаёт собственное локальное состояние, а результат передаётся вызывающему коду через возвращаемое значение, именованный кортеж, out, изменение аргумента через ref либо наблюдаемый побочный эффект. Качество декомпозиции определяется не длиной тела, а однозначностью ответственности, контракта и зависимостей.

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

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

Опора — Разделы 13–14: методы и процедурная декомпозиция; рекурсия и стек вызовов. Выражения, ветвления, консольный разбор и циклы из Разделов 4–12 используются как уже изученные средства.

Каждая задача оформляется отдельной консольной программой полным шаблоном курса и компилируется с <Nullable>enable</Nullable>. Все пользовательские методы объявляются как static внутри класса Program. Локальные функции, массивы, коллекции, собственные struct, дополнительные классы, делегаты, лямбда-выражения, обобщённые методы, обработка исключений и глобальное изменяемое состояние не используются. Вычисляющие методы не обращаются к консоли: ввод, вывод и запуск контрольных примеров находятся в Main либо в отдельно обозначенном методе представления. Каждый контракт фиксирует предусловия, постусловия, способ передачи, побочные эффекты и допустимость отказа. Рекурсивный метод допускается только при явно сформулированных базовом случае, рекурсивном переходе и уменьшающейся мере; сравнение с итерацией выполняется на одинаковых входах. Намеренно некомпилируемые варианты и эксперименты со StackOverflowException запускаются в отдельных копиях проекта без try/catch.


База практических заданий

Уровень I. Базовый

Сигнатуры, состав методов, способы передачи, каналы результата и рекурсивное определение заданы условием. Требуется точно реализовать готовый API, вызвать его всеми указанными способами и проследить локальное состояние либо стек. Ученик не выбирает декомпозицию: сложность возрастает от одиночного чистого метода к композиции нескольких контрактов и рекурсивному вычислению.

1. Метод как именованное вычисление: Реализовать

static double EvaluateQuadratic(
    double x,
    double a,
    double b,
    double c)

для значения ax2+bx+cax^2+bx+c. Промежуточные square, quadraticPart, linearPart и result объявляются локально. В Main сохранить исходный x, вызвать метод дважды с разными аргументами и подтвердить, что изменение локальных переменных внутри вызова не изменяет переменные вызывающего кода. В комментарии разметить возвращаемый тип, имя, параметры, аргументы, тело и точку возврата.

2. Значение и действие: Реализовать чистый метод

static string FormatMeasurement(
    double value,
    string unit,
    int digits)

и метод действия

static void PrintMeasurement(
    double value,
    string unit,
    int digits)

Второй получает готовую строку вызовом первого и только выводит её. Показать, что результат FormatMeasurement можно сохранить, сравнить и использовать повторно, а вызов PrintMeasurement является самостоятельной инструкцией и не возвращает строку. Форматирование не дублировать.

3. Передача по значению: Реализовать

static decimal ApplyRate(
    decimal amount,
    decimal rate)

Метод локально выполняет amount *= 1 + rate и возвращает результат. В Main сохранить исходную сумму, вызвать метод и показать, что переменная вызывающего кода не изменилась. Затем передать в два последовательных вызова одно и то же исходное значение и подтвердить независимость локального состояния. Объяснить различие между изменением параметра-копии и возвратом нового результата.

4. Именованные и необязательные аргументы: Реализовать

static decimal CompoundAmount(
    decimal principal,
    decimal annualRate,
    int years,
    int compoundsPerYear = 1)

по формуле

A=P(1+rm)mt.A=P\left(1+\frac{r}{m}\right)^{mt}.

Из-за Math.Pow выполнить осознанные преобразования между decimal и double. Сделать вызовы: со всеми позиционными аргументами; с пропущенным compoundsPerYear; с именованными аргументами в порядке, отличном от объявления; с сочетанием позиционных и последующих именованных аргументов. Объяснить, почему значение по умолчанию должно быть константным выражением времени компиляции.

5. Перегрузки по числу и типам: Реализовать семейство

static int Abs(int value)
static double Abs(double value)
static double Distance(double x1, double x2)
static double Distance(
    double x1,
    double y1,
    double x2,
    double y2)

без Math.Abs внутри собственных Abs. Для каждого контрольного вызова заранее указать выбранную перегрузку и основание выбора: точное совпадение статического типа либо число аргументов. Показать, что результат Abs(-7) имеет тип int, а Abs(-7.0)double.

6. Контракт Try... и параметры out: Реализовать

static bool TryDivMod(
    int dividend,
    int divisor,
    out int quotient,
    out int remainder)

При ненулевом делителе вернуть true, частное и остаток; при нуле вернуть false, присвоив обоим выходным параметрам согласованные значения по умолчанию. В Main использовать результаты только после проверки логического ответа. Для успеха подтвердить

dividend=quotientdivisor+remainder.dividend=quotient\cdot divisor+remainder.

Объяснить, почему out не требует предварительной инициализации аргумента, но требует присваивания параметру на каждом пути возврата.

7. Изменение аргумента через ref: Реализовать

static void Swap(ref int left, ref int right)

и

static void OrderAscending(
    ref int first,
    ref int second)

OrderAscending вызывает Swap только при нарушении порядка. Сделать вызовы для возрастающей, убывающей и равной пары. Показать отдельным некомпилируемым фрагментом, что аргумент ref обязан быть переменной, предварительно получившей значение, и что модификатор требуется как в объявлении, так и в вызове.

8. Чтение через in: Реализовать

static long SquaredDistance(
    in int x1,
    in int y1,
    in int x2,
    in int y2)

Разности преобразовать к long до умножения. Вызвать метод с явными in и без модификатора в месте вызова, сравнить результаты. В отдельном некомпилируемом фрагменте попытаться присвоить новое значение параметру in. Объяснить, что in выражает контракт чтения и не делает метод чистым автоматически, если внутри существуют другие побочные эффекты.

9. Именованный кортеж и деконструкция: Реализовать

static (long quotient, long remainder) DivMod(
    long dividend,
    long divisor)

для ненулевого делителя. Получить результат через имена элементов, Item1 и Item2, полную деконструкцию и деконструкцию с _. Подтвердить тождество деления. Объяснить, почему два связанных результата естественно вычисляются одним вызовом и чем кортеж отличается от изменения двух заранее существующих переменных через ref.

10. Композиция и единая ответственность: Реализовать чистые методы

static double Discriminant(
    double a,
    double b,
    double c)

static int RootCount(
    double discriminant,
    double tolerance)

static double Residual(
    double a,
    double b,
    double c,
    double x)

RootCount возвращает 2, 1 или 0. Main организует вызовы, вычисляет корень только в допустимой ветви и выводит результат. Для каждого метода записать предусловие, постусловие и отсутствие либо наличие побочного эффекта. Объяснить, почему выделение операции оправдано смысловой границей, а не желанием уменьшить число строк в Main.

11. Прямая рекурсия и порядок возврата: Реализовать

static void TraceDownUp(int n)

для n0n\ge0. Метод выводит enter n, при n>0n>0 вызывает TraceDownUp(n - 1), затем выводит leave n. До запуска записать точную последовательность строк для n = 3, число вызовов и максимальное число одновременно активных кадров. Базовый случай — n == 0, уменьшающаяся мера — n.

12. Рекурсивный факториал: Реализовать

static long Factorial(int n)

для 0n200\le n\le20:

n!={1,n=0,n(n1)!,n>0.n!= \begin{cases} 1,&n=0,\\ n\cdot(n-1)!,&n>0. \end{cases}

До запуска построить таблицу кадров для Factorial(4): параметр, незавершённое умножение и возвращаемое значение. Максимальная глубина с базовым кадром равна n+1n+1. Сравнить результат с итеративным факториалом из Модуля 3 и объяснить различие дополнительной памяти.

13. Рекурсивный алгоритм Евклида: Реализовать

static long Gcd(
    long a,
    long b,
    ref int callCount)

для a>0a>0, b0b\ge0:

gcd(a,b)={a,b=0,gcd(b,amodb),b>0.\gcd(a,b)= \begin{cases} a,&b=0,\\ \gcd(b,a\bmod b),&b>0. \end{cases}

До запуска выписать цепочку аргументов для (1071,462)(1071,462). Доказать сохранение НОД и уменьшение второго аргумента. Сравнить число рекурсивных вызовов с числом итераций циклической версии.


Уровень II. Продвинутый

Условие задаёт вычислительную задачу и требования к результату, но не сигнатуры, границы методов и каналы передачи данных. Требуется самостоятельно спроектировать API: распределить ответственности, выбрать возвращаемое значение, кортеж, out, ref или in, исключить скрытые зависимости и сформулировать контракты. В рекурсивных задачах дополнительно выводятся база, переход, уменьшающаяся мера, число вызовов и глубина.

1. Статистика четырёх наблюдений: Для четырёх double спроектировать чистый API, возвращающий минимум, максимум, среднее, дисперсию и стандартное отклонение. Основной метод должен возвращать именованный кортеж. Допускаются перегрузки собственных Min и Max для двух и четырёх аргументов, но более сложные версии обязаны использовать простые, а не дублировать все сравнения. В Main выполнить полную деконструкцию и отдельную деконструкцию с пропуском ненужных элементов.

2. Нормализация рационального представления: Спроектировать метод, получающий числитель и знаменатель типа long через ref и приводящий дробь к каноническому виду:

  • НОД модулей равен 1;
  • знаменатель положителен;
  • нулевой числитель представлен как 0/10/1.

Нулевой знаменатель должен давать false и оставлять обе исходные переменные неизменными. НОД вычисляется отдельным чистым методом. Для безопасности исключить входы long.MinValue явным предусловием. Доказать идемпотентность: повторный успешный вызов не изменяет пару.

3. Анализ квадратного уравнения: Спроектировать набор чистых методов для коэффициентов double a, b, c и допуска. Модель должна различать вырожденное линейное уравнение, тождество, противоречие, отсутствие вещественных корней, один и два корня. Разрешается объявить enum EquationKind. Вычислительное ядро возвращает именованный кортеж со статусом, числом корней, двумя местами для корней и максимальной подстановочной невязкой. Представление результата находится вне ядра. Обосновать, какие вспомогательные методы имеют самостоятельный контракт, а какие были бы бессодержательным дроблением формулы.

4. Половинное деление как Try...-операция: Для f(x)=x3x2f(x)=x^3-x-2 спроектировать чистый метод половинного деления. Он получает границы, абсолютный допуск и MaxIterations, возвращает bool, а через out сообщает приближение, остаток, число итераций и конечную ширину интервала. true означает, что требуемая точность подтверждена; false — что результат не подтверждён из-за недопустимых параметров, отсутствия смены знака или исчерпания предела. Все out присваиваются на каждом пути. Вычисление f(x)f(x) вынести в отдельный чистый метод; в Main до вызова отдельно фиксировать, какое предусловие проверяется каждым контрольным набором.

5. Семейство степеней: Спроектировать однозначные перегрузки одного имени для основания long и double с неотрицательным показателем int. Обе версии используют быстрое возведение в степень, но имеют разные числовые контракты: целая версия принимает только входы, для которых результат представим в long, вещественная проверяется сравнением с Math.Pow. Добавить необязательный параметр только там, где он имеет один естественный смысл, например допуск проверки вещественного результата. Для каждого контрольного вызова обосновать выбор перегрузки по статическим типам.

6. Каналы состояния метода Ньютона: Спроектировать чистый метод одного шага Ньютона для a\sqrt a и отдельный метод полного процесса. Итог процесса должен включать converged, приближение, невязку, число итераций и факт остановки из-за отсутствия прогресса. Самостоятельно выбрать между кортежем и out, обосновать решение и не использовать ref только ради возврата нескольких значений. Начальное приближение, абсолютный и относительный допуски и предел являются параметрами; значения по умолчанию допустимы только для параметров с общепринятой политикой модуля.

7. Рекурсивная сумма и число цифр: Для неотрицательного long спроектировать рекурсивный метод, возвращающий количество десятичных цифр и их сумму. Число 0 содержит одну цифру 0. Циклы и строки внутри рекурсивного вычисления запрещены. До реализации вывести базовый случай, переход по делению на 10, уменьшающуюся меру и максимальную глубину.

8. Рекурсивный профиль числа: Расширить предыдущую модель: вернуть именованный кортеж (digits, sum, maxDigit, reversed). Предусловие: развёрнутое значение представимо в long. Допускается отдельный вспомогательный метод для степени 10, если его ответственность сформулирована явно. Разворот сохраняет числовое значение без ведущих нулей. Доказать, какие поля кортежа относятся к обработанному префиксу или суффиксу при обратном разворачивании вызовов.

9. Рекурсивное быстрое возведение в степень: Спроектировать рекурсивный метод для ana^n, где double a, n0n\ge0, использующий деление показателя пополам. Вместе со значением вернуть число умножений, число вызовов и максимальную глубину. Сравнить с линейной рекурсией an=aan1a^n=a\cdot a^{n-1} и итеративным бинарным алгоритмом. Доказать логарифмическую глубину быстрой версии.

10. Косвенная рекурсия для чётности: Спроектировать взаимно рекурсивные предикаты IsEven(n) и IsOdd(n) для неотрицательного int. Каждый метод при ненулевом аргументе вызывает другой с n - 1. Указать две базы, построить цепочку для n = 5 и доказать, что ровно один предикат истинен. Сравнить с % 2 по времени, глубине и дополнительной памяти и объяснить, почему математическая естественность определения не гарантирует практической целесообразности.

11. Деление диапазона пополам: Спроектировать рекурсивный метод суммы замкнутого диапазона [left,right][left,right], где left <= right и математическая сумма представима в long. Единичный диапазон является базой, иначе область делится около середины на две непересекающиеся части; середину вычислять без переполнения выражением left + (right - left) / 2 при заранее гарантированном представимом right - left. Вернуть сумму, число вызовов и максимальную глубину. Сравнить с циклом и замкнутой формулой: рекурсивное дерево имеет логарифмическую глубину, но линейное число листьев. Доказать отсутствие пропусков и двойного счёта.

12. Расширенный алгоритм Евклида: Спроектировать рекурсивный метод, возвращающий именованный кортеж (gcd, x, y) для положительных int a и b, где gcd имеет тип int, а коэффициенты — long, такой что

ax+by=gcd(a,b).ax+by=\gcd(a,b).

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


Уровень III. Экспертный

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

1. Сигнатуры и разрешение перегрузок: В отдельных копиях проекта исследовать:

static string Select(int value)
static string Select(long value)
static string Select(double value)

для литералов 1, 1L, 1.0, переменных byte, short, float и decimal; затем семейство

static string Distance(double x1, double x2)
static string Distance(
    double x1,
    double y1,
    double x2,
    double y2)

и неоднозначную пару

static string Mix(long left, float right)
static string Mix(float left, long right)

для Mix(1, 1). Предсказать выбор или неоднозначность, затем устранить её явным типом одного аргумента. Отдельно попытаться объявить методы, различающиеся только возвращаемым типом, именами параметров и модификаторами ref/out; записать диагностики. Показать допустимое различие между параметром по значению и параметром ref.

2. Семантика value, ref, out и in: Реализовать четыре минимальных метода для одного int и до запуска составить таблицу:

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

Проверить таблицу рабочими и отдельными некомпилируемыми фрагментами: неинициализированный аргумент для ref и in, чтение out до присваивания, присваивание in. Дополнительно вызвать Swap(ref x, ref x) и объяснить результат через совпадение двух параметров с одной переменной.

3. Именованные и необязательные аргументы: Реализовать

static string Configure(
    int width,
    int height = 1,
    bool normalize = false)

и исследовать полные позиционные вызовы, пропуск хвостовых аргументов, именованные аргументы в другом порядке и смешанный вызов с последующими именованными аргументами. В отдельных некомпилируемых версиях использовать неизвестное имя параметра, передать один аргумент дважды и задать значением по умолчанию результат Math.Sqrt. Объяснить, что имена параметров не образуют новую сигнатуру, но участвуют в связывании аргументов конкретного вызова.

4. Порядок вычисления аргументов и побочные эффекты: Реализовать

static int Next(ref int state)
static int Combine(int first, int second, int third)

по явно записанному контракту Next. До запуска предсказать аргументы и результат

Combine(
    Next(ref state),
    Next(ref state),
    Next(ref state))

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

5. Кадры, разворачивание и нарушение меры: Реализовать Trace(n) с выводом до и после рекурсивного вызова. Для n = 4 предсказать полную трассу, число вызовов и максимальную глубину. Затем исследовать в отдельных запусках версию без базы и версию с переходом n + 1 при условии n > 0; объяснить отсутствие уменьшающейся меры. Для корректной линейной рекурсии увеличивать глубину отдельными запусками до StackOverflowException, не пытаясь его перехватывать, и записать приблизительную границу данной среды. Сравнить с циклом при постоянной дополнительной памяти.

6. Три способа возведения в степень: Реализовать линейную рекурсию, рекурсивное деление показателя пополам и итеративное быстрое возведение. Для каждого считать умножения, вызовы или итерации и максимальную глубину. До запуска вывести прогноз для n=0n=0, 11, 22, 1313 и 10241024, затем сопоставить с наблюдением. Объяснить линейную и логарифмическую глубину и то, почему итеративная версия сохраняет логарифмическое число шагов без роста стека.

7. Наивная рекурсия Фибоначчи и повторная работа: Реализовать

static long Fibonacci(
    int n,
    ref long callCount)

с базами F0=0F_0=0, F1=1F_1=1 и двумя рекурсивными вызовами для n2n\ge2. До запуска построить дерево для n = 5, отметить повторные вычисления одинаковых аргументов и вывести рекуррентность числа вызовов

T(n)=T(n1)+T(n2)+1,T(0)=T(1)=1.T(n)=T(n-1)+T(n-2)+1, \qquad T(0)=T(1)=1.

Подтвердить формулу

T(n)=2Fn+11T(n)=2F_{n+1}-1

на малых nn и сравнить с итеративной версией из Модуля 3. Объяснить, почему глубина остаётся O(n)O(n), а число вызовов растёт экспоненциально, и почему отсутствие массивов и запоминания результатов в текущих границах не позволяет устранить повторную работу внутри этой реализации.


Обязательные контрольные наборы

ЗадачаИсходные данные
I.1(x,a,b,c)=(2,3,4,5)(x,a,b,c)=(2,3,-4,5) и (1,5,2,0,5,7)(-1{,}5,2,0{,}5,-7)
I.2(12.3456, "V", 2) и (-0.0049, "A", 4)
I.3amount = 1000m, rate = 0.075m; два последовательных вызова
I.4principal = 1000m, annualRate = 0.12m, years = 3, compoundsPerYear = 12; затем значение по умолчанию
I.5Abs(-7), Abs(-7.0), Distance(-2,5), Distance(0,0,3,4)
I.6(17,5), (-17,5), (17,0)
I.7(9,-4), (5,5), (-3,11)
I.8(0,0)(0,0) и (30000,40000)(30000,40000); затем одинаковые точки
I.9(17,5), (-17,5)
I.10(a,b,c)=(1,2,1)(a,b,c)=(1,-2,1), (1,0,1)(1,0,1), (0,2,6)(0,2,-6)
I.11n{0,1,3,5}n\in\{0,1,3,5\}
I.12n{0,1,4,10,20}n\in\{0,1,4,10,20\}
I.13(1071,462)(1071,462), (48,18)(48,18), (13,0)(13,0)
II.1(1,2,3,4)(1,2,3,4), (5,5,5,5)(5,5,5,5), (109+1,109+2,109+3,109+4)(10^9+1,10^9+2,10^9+3,10^9+4)
II.2(42,56)(42,56), (42,56)(-42,56), (42,56)(42,-56), (0,9)(0,-9), знаменатель 0
II.3(1,3,2)(1,-3,2), (1,2,1)(1,2,1), (1,0,1)(1,0,1), (0,2,6)(0,2,-6), (0,0,5)(0,0,5), (0,0,0)(0,0,0)
II.4[1,2][1,2], допуски 10310^{-3}, 10910^{-9}; неверный интервал [2,3][2,3]
II.5(2L, 0), (2L, 13), (-2L, 15), (1.5, 13)
II.6(a,x0)=(2,1)(a,x_0)=(2,1), (1012,1)(10^{12},1), (1012,1)(10^{-12},1)
II.70, 7, 1002003, 9_223_372_036_854_775_807L
II.80, 7, 1002003, 1200300
II.9(a,n)=(2,0)(a,n)=(2,0), (2,13)(2,13), (1,5,11)(-1{,}5,11)
II.10n{0,1,5,20,1000}n\in\{0,1,5,20,1000\}
II.11[1,1][1,1], [1,10][1,10], [5,5][-5,5], [1,1024][1,1024]
II.12(240,46)(240,46), (1071,462)(1071,462), (99,78)(99,78)
III.1все вызовы и объявления из условия
III.2начальные значения 10 и 20; отдельный вызов Swap(ref x, ref x)
III.3Configure(10), Configure(10,20), Configure(width:10, normalize:true), Configure(normalize:true, width:10, height:20)
III.4начальное state = 0; контракт Next увеличивает состояние и возвращает новое значение
III.5n=4n=4; затем возрастающие глубины отдельными запусками
III.6n{0,1,2,13,1024}n\in\{0,1,2,13,1024\}
III.7n{0,1,2,5,10,20,40}n\in\{0,1,2,5,10,20,40\}; большие значения запускать с учётом времени

Итог модуля

Ученик проектирует static-методы как API с одной ответственностью, явными предусловиями, постусловиями и зависимостями, отделяет вычисление от ввода-вывода и различает возвращаемое значение и побочный эффект. Перегрузка разрешается по имени, числу и статическим типам параметров, способам передачи и допустимым преобразованиям; именованные и необязательные аргументы используются без нарушения однозначности. Передача по значению, out, ref и in выбирается по тому, кто создаёт, читает и изменяет данные, а несколько связанных результатов возвращаются кортежем или выходными параметрами и принимаются через имена, ItemN, деконструкцию и _. Рекурсивное решение содержит базовый случай и уменьшающуюся меру, прослеживается по кадрам стека и сравнивается с итерацией по числу операций, глубине и дополнительной памяти.

Покрытие опоры и границы

  • Объявление, вызов, параметры, аргументы, return, void и локальное состояние отдельного вызова: I.1–I.3.
  • Чистое вычисление, побочный эффект, разделение вычисления и представления, одна ответственность и композиция: I.2, I.10, II.1–II.6.
  • Сигнатуры, перегрузки, статические типы, допустимые преобразования и неоднозначность: I.5, II.5, III.1.
  • Именованные и необязательные аргументы: I.4, II.5–II.6, III.3.
  • Передача по значению, out, ref и in: I.3, I.6–I.8, II.2, II.4, II.6, III.2 и III.4.
  • Кортеж, именованные элементы, ItemN, возврат, деконструкция и _: I.9, II.1, II.3, II.6–II.9, II.11–II.12.
  • Контракт Try..., определённость выходных параметров и отказ без исключения: I.6, II.2 и II.4.
  • Прямая рекурсия, базовый случай, переход, мера и порядок разворачивания: I.11–I.13, II.7–II.9, II.11–II.12, III.5–III.7.
  • Косвенная рекурсия: II.10.
  • Кадр стека, глубина, переполнение стека и сравнение с циклом: I.11–I.13, II.9–II.11, III.5–III.7.
  • Число вызовов, повторная работа и различие линейной, логарифмической и экспоненциальной стоимости: II.9, II.11–II.12, III.6–III.7.

За границей: массивы и params, собственные структуры и классы, свойства, экземплярные методы, обобщения, делегаты, лямбда-выражения, локальные функции, исключения и функции как значения рассматриваются позднее. Оптимизация хвостовой рекурсии не предполагается и не используется как основание корректности или оценки памяти. Рекурсивный алгоритм не считается предпочтительным только потому, что допускает рекурсивное определение: выбор подтверждается структурой задачи, глубиной и ценой повторных вычислений.