---
course: ОАиП
lecture: 4
title: "Лекция №4. Функции: модульность и повторное использование кода."
---

# Лекция №4. Функции: модульность и повторное использование кода.

## §4.1. Понятие функции и принципы модульности

**Функция**

— это именованный блок кода, предназначенный для выполнения конкретной задачи. Вместо того чтобы писать один и тот же код несколько раз, его можно оформить в виде **функции** и вызывать по имени при необходимости.

Программы, состоящие из множества небольших **функций**, проще понимать, тестировать и поддерживать. Это и есть **принцип модульности**: сложная задача разбивается на более простые, независимые части — модули (в данном случае, функции).

**Функции** помогают:

- **Избегать дублирования кода** — один раз написали, много раз используем.
- **Упрощать отладку** — если ошибка, можно проверить каждую функцию отдельно.
- **Повышать читаемость** — код становится понятнее, особенно если функции имеют осмысленные имена (например, *CalculateArea, PrintMenu*).

В языках C++ и C# **функции** являются основным способом организации кода.

В C++ функции могут быть глобальными или частью классов. В C# все функции (методы) обязательно входят в состав классов.

Небольшое напутствие

в С++ программисты, именованный блок кода для многократного вызова, называют «функции», а в С# — «методы».

## §4.2 Синтаксис определения функций

В дополнение к определению функции из параграфа **§4.1**. можно сказать, что **функция** (*function*) — это подпрограмма, которая может получать параметры на вход и возвращать выходное значение. Чтобы выполнить свою задачу, **функция** должна быть вызвана. Она является важнейшим элементом процедурного программирования, так как позволяет группировать и обобщать программный код, который может позднее использоваться произвольное число раз.

Общий синтаксис определения функции в языке C++ имеет следующий вид:

text

```text
ТИП_ВОЗВРАТА ИМЯ_ФУНКЦИИ(СПИСОК_ПАРАМЕТРОВ) 
{ 
   //тело функции
}
```

Пример создания и вызова функции на языке С++:

cpp

```cpp
void SayHello()
{
    std::cout << " Привет второй курс :)" << std::endl;
}

SayHello(); // Вызов функции
```

При определении функции C# (функции в данном языке программирования называются методами) используется следующая форма:

text

```text
[модификаторы] ТИП_ВОЗВРАТА ИМЯ_МЕТОДА (СПИСОК_ПАРАМЕТРОВ)
{
   //тело метода
}
```

Пример создания и вызова функции на языке С#:

csharp

```csharp
static void SayHello()
{
    Console.WriteLine("Привет второй курс :)");
}

SayHello(); // Вызов метода 
```

Здесь **ТИП\_ВОЗВРАТА** определяет тип данного, возвращаемых функцией. Если **функция** не возвращает никакого значения, то используется тип **void**.

Имя **функции** определяется элементом **ИМЯ\_ФУНКЦИИ**. В качестве имени допускается использовать **любой идентификатор**, если он еще не занят.

**СПИСОК\_ПАРАМЕТРОВ** представляет собой последовательность пар типов и идентификаторов, разделяемых запятыми.

**Параметры**

– это переменные, которые получают значения аргументов, передаваемых функции при ее вызове.

**Аргументы**

– это значения, которые передаются из caller-а в вызываемую функцию. Если функции не требует параметров, то список параметров будет пуст.

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

## §4.3 Вызов функции и аргументы

После того как **функция** определена, её можно вызвать — то есть **запустить на выполнение**. Для этого используется **имя функции**, за которым следуют круглые скобки с аргументами (данными, которые передаются в функцию).

Формат вызова:

text

```text
ИмяФункции(аргументы);
```

Если функция возвращает значение, его можно сохранить в переменную или использовать в выражении:

text

```text
тип переменная = ИмяФункции(аргументы);
```

Вновь повторимся, как было сказано в параграфе **§4.2**, **аргументы** — это конкретные значения, которые передаются в функцию при её вызове. Они соответствуют параметрам, указанным при **определении функции**. Обратите внимание на код на языке программирования С#:

csharp

```csharp
int Add(int a, int b)  // a и b — параметры
{
    return a + b;
}
 
// Вызов функции:
int result = Add(3, 5); // 3 и 5 — аргументы
```

В данном случае, **a** и **b** – **параметры** (*места – заполнители в определени*), а **3** и **5** – **аргументы** (*реальные значения при вызове*).

При вызове **функции** необходимо учитывать следующие ограничения:

- Количество аргументов должно совпадать с количеством параметров (если не используются параметры по умолчанию).
- Типы аргументов должны быть совместимы с типами параметров.
- Порядок аргументов имеет значение — первый аргумент подставляется в первый параметр и т.д.

text

```text
double Power(double base, double exp)
{
    return pow(base, exp);
}
 
double res = Power(2.0, 3.0); // base = 2.0,exp = 3.0
// double res = Power(“abc”, 3.0); 
// ❌ Ошибка — тип string не подходит под double.
```

Если функция **не возвращает значение** (void), её вызов используется как отдельный оператор. Пример на языке программирования С#:

csharp

```csharp
void PrintHello()
{
    Console.WriteLine("Привет!");
}
 
PrintHello(); // вызов — просто имя и скобки
```

Если функция **возвращает значение**, его можно:

- Присвоить переменной.
- Использовать в выражении.
- Передать как аргумент в другую функцию.

csharp

```csharp
int Square(int x)
{
    return x * x;
}
 
int a = Square(4);             // a = 16
int b = Square(3) + Square(4); // b = 9 + 16 = 25

Console.WriteLine(Square(5));  // выводит 25 
```

То же самое — в C++ (с заменой *Console.WriteLine* на *std::cout*).

Несмотря на то, что **функции / методы** удобная конструкция для работы с программой, в С++ есть **ограничения**, которые придется учитывать. Первое, **функция** не может быть объявлена внутри другой **функции**. Исключениями являются лишь **лямбда – функции**.

Пример создания и вызова лямбда – функции в С++:

csharp

```csharp
#include <iostream>
 
void myFunction() {
    // Лямбда-функция, которая просто выводит сообщение
    auto greet = []() {
        std::cout << "Привет из лямбды!" << std::endl;
    };
 
    // Вызов лямбды
    greet();
}
 
int main() {
    myFunction();  // Выведет: Привет из лямбды!
    return 0;
}
```

Но стоит понимать, что код:

cpp

```cpp
int main() {
   void myFunction() { }
   return 0;
}  
```

выдаст ошибку компиляции: **«myFunction () was not declared in this scope»**. Это связано с тем, что определение **функции** не существует внутри текущей функции.

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

cpp

```cpp
int main() {
   Sum_numbers(1, 2);
   return 0;
}

int Sum_numbers(int a, int b) {
   cout << a + b;
}
```

Так, при вызове функции **Sum\_numbers()** внутри функции **main()** компилятор не знает ее полное имя.

Конечно, компилятор C++ мог просмотреть весь код и определить полное имя функции, но этого он делать не умеет и нам приходится с этим считаться.

Поэтому мы обязаны проинформировать компилятор о полном имени функции. Для этого мы будем использовать **прототип функции**.

**Прототип функции**

— это функция, в которой отсутствует блок кода (тело функции).

В прототипе функции находятся:

- Полное имя функции.
- Тип возвращаемого значения функции.

Вот как правильно должна была выглядеть программа, написанная выше:

cpp

```cpp
int Sum_numbers(int a, int b); // прототип функции

int main() {
   Sum_numbers(1, 2);
   return 0;
}

int Sum_numbers(int a, int b) { // сама функция
   cout << a + b;
}
```

Так, с помощью прототипа мы объясняем компилятору, что полным именем функции является **Sum\_numbers(int a, int b)**, и кроме этого мы говорим компилятору о типе возвращаемого значения, у нас им является тип **int**.

## §4.4 Возвращаемые значения и оператор return

**Функция** может выполнять вычисления и возвращать результат для дальнейшего использования. Значение, которое функция передаёт обратно в место вызова, называется возвращаемым значением. Для возврата значения используется оператор **return**.

Оператор **return** выполняет две задачи:

- Завершает выполнение функции.
- Возвращает указанное значение вызывающему коду.

После выполнения **return** управление немедленно возвращается туда, откуда функция была вызвана.

Тип значения, которое возвращает функция, должен совпадать с её возвращаемым типом, указанным при определении. Если функция объявлена как **int**, она должна вернуть целое число, если **double** — число с плавающей точкой, если **string** — строку и т. д.

text

```text
int GetNumber()
{
    return 42; // корректно: int → int
} 
```

Если **функция** не возвращает значения, её тип — **void**. В таких функциях return можно использовать без значения, чтобы завершить выполнение досрочно.

csharp

```csharp
void PrintMessage(string message)
{
    if (message == null)
    {
        return; // выход из функции без возврата значения
    }

    Console.WriteLine(message);
}
```

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

text

```text
int Max(int a, int b)
{
    if (a >= b)
        return a; 
    else
        return b;
    // если a >= b, то второй return не выполняется
}
```

Как было сказано выше, одна из задач оператора **return** – завершение работы функции. Поэтому, после оператора **return** любой код в теле функции игнорируется.

csharp

```csharp
int BadFunction()
{
    return 10;
    Console.WriteLine("Этот код никогда не выполнится"); // ОШИБКА: недостижимый код 
}
```

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

text

```text
double Square(double x)
{
    return x * x;
}
```

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

Пример ошибки при работе с функцией:

text

```text
int CheckValue(int x)
{
    if (x > 0)
        return 1;

    // А если x <= 0? Куда идти? Ошибка компиляции!
}
```

Для того, чтобы исправить ошибку компиляции, необходимо добавить блок **else** и оператор **return**, который вернет необходимое значение в случае, если **x <= 0**.

## §4.5 Функции с несколькими параметрами

**Функция** может принимать не один, а несколько входных данных — для этого в её определении указывается несколько параметров. Такие \*\*функции \*\*позволяют обрабатывать сложные зависимости и строить более гибкие алгоритмы.

Параметры перечисляются в списке параметров через запятую при определении функции.

Общий вид подобной записи:

text

```text
возвращаемыйТип ИмяФункции(тип1 имя1, тип2 имя2, ..., типN имяN)
{
    // тело функции
}
```

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

wireframe

```
Площадь прямоугольника = Длина прямоугольника × Ширина прямоугольника
```

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

text

```text
double CalculateArea(double length, double width)
{
    return length * width;
}
```

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

csharp

```csharp
double area = CalculateArea(5.0, 3.0);
Console.WriteLine("Площадь: " + area); // Вывод: 15
```

При использовании нескольких параметров, ограничения остаются те же, что и при работе с одним параметров:

- Все параметры должны иметь указанный тип.
- Имена параметров должны быть уникальными в пределах списка.
- Типы аргументов при вызове должны быть совместимы с типами параметров (например, *int* можно передать в *double*, но не наоборот без приведения).

text

```text
CalculateArea("длина", "ширина"); 
// ОШИБКА: тип string не подходит под double
```

Параметры функций похожи на локальные переменные. Они допустимы только в пределах функции. Так, параметры *length* и *width* **функции** *CalculateArea()* допустимы и пригодны для использования только внутри функции *CalculateArea()*, но не вне нее.

Технически ограничений почти нет, но на практике более **3–4 параметров** — *признак, что функция делает слишком много*.

## §4.6 Параметры функций со значениями по умолчанию

В некоторых случаях удобно, чтобы параметр **функции** имел заранее заданное значение, если при вызове аргумент не указан. Такие параметры называются **параметрами со значением по умолчанию**.

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

**Значение по умолчанию**

– это значение, которое автоматически подставляется в параметр, если при вызове функции соответствующий аргумент не передан.

В C++ значение по умолчанию указывается при объявлении функции (*чаще всего в прототипе или определении*):

text

```text
возвращаемыйТип ИмяФункции(тип параметр = значение, ...)
{
    // тело функции
}
```

Для примера можно рассмотреть следующий код:

cpp

```cpp
void PrintMessage(std::string message, int count = 1)
{
    for (int i = 0; i < count; i++)
    {
        std::cout << message << std::endl;
    }
}
```

Теперь функцию можно вызывать по-разному:

cpp

```cpp
// выведет 1 раз (count = 1 по умолчанию)
PrintMessage("Привет");           
PrintMessage("Пока", 3); // выведет 3 раза
```

При работе со значениями по умолчанию, необходимо следовать двум правилам, чтобы не было ошибки компиляции:

- Параметры со значениями по умолчанию должны идти в конце списка.
- Нельзя пропускать аргументы посередине.

cpp

```cpp
// ✅ Так можно
void Func(int a, int b = 10, int c = 20);

// ❌ Так нельзя, так как ошибка: a — не в конце
void Func(int a = 5, int b, int c = 10);
```

Однако, на практике в C# чаще используют перегрузку методов, а не значения по умолчанию. Но если используется — синтаксис значений по умолчанию такой же:

csharp

```csharp
void PrintMessage(string message, int count = 1)
{
    for (int i = 0; i < count; i++)
    {
        Console.WriteLine(message);
    }
}
```

## §4.7 Перегрузка функций (function overloading)

**Перегрузка функций**

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

Это удобно, когда одна и та же операция выполняется с разными типами данных или разным числом параметров.

Представьте, что вы хотите написать функцию *Print*, которая выводит на экран разные типы данных: число, строку, массив.

Можно дать им разные имена: *PrintInt, PrintString, PrintArray* — но это неудобно и неочевидно.

С перегрузкой можно просто написать:

text

```text
Print(42);
Print("Hello");
Print(arr);
```

— и всё будет работать, если есть соответствующие версии функции *Print*.

Компилятор различает функции по их сигнатуре — комбинации: имени функции, типов параметров, количества параметров, порядка параметров.

Так же важно понимать, что **возвращаемый тип** не участвует в различении **перегруженных функций**.

text

```text
// ✅ Так можно
int Add(int a, int b);
double Add(double a, double b);

// ❌ Так нельзя
int Get();
double Get(); 
// ОШИБКА: одинаковое имя и параметры — не перегрузка!
```

Для примера перегруженной функции рассмотрим следующий код:

text

```text
// Версия 1: два целых числа
int Sum(int a, int b)
{
    return a + b;
}
 
// Версия 2: три целых числа
int Sum(int a, int b, int c)
{
    return a + b + c;
}
 
// Версия 3: два числа с плавающей точкой
double Sum(double a, double b)
{
    return a + b;
}

// Вызов
Sum(2, 3);        // вызовет Sum(int, int)
Sum(1, 2, 3);     // вызовет Sum(int, int, int)
Sum(2.5, 3.7);    // вызовет Sum(double, double)
```

В C# **перегрузка** работает точно так же, как и в С++, но только для методов внутри классов.

## §4.8 Рекурсивные функции

В некоторых случаях функция может фактически вызывать сама себя. Такая функция называется **рекурсивной** (*recursive function*).

Любая корректная рекурсивная функция состоит из двух ключевых частей:

- **Базовый случай (условие остановки)** — условие, при котором функция не вызывает себя, а возвращает конкретное значение.

Без **базового случая** рекурсия будет продолжаться **бесконечно** — приведёт к переполнению стека вызовов.

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

Главное правило **рекурсий** заключается в том, что **рекурсия** должна приближаться **к базовому случаю** с каждым шагом.

Обратите внимание, что **у рекурсивной функции** должно быть четко определенное условие выхода, когда она завершает работу и больше себя не вызывает.

Давайте рассмотрим принцип работы **рекурсивных функций** на примере нахождения факториала числа. Но прежде давайте вспомним, что собой представляет факториал и как он вычисляется:

factorial(N)=1×2×3×⋯×(N−1)×N\text{factorial}(N) = 1 \times 2 \times 3 \times \dots \times (N-1) \times Nfactorial(N)=1×2×3×⋯×(N−1)×N

Проще говоря, **факториал числа** — это просто произведение всех чисел от 1 до N.

Чтобы перемножить всю заданную последовательность чисел, можно использовать цикл **for**.

Но если приглядеться, можно заметить, что для вычисления **факториала** можно применить **рекурсивную структуру**.

factorial(N)=N×factorial(N−1)\text{factorial}(N) = N \times \text{factorial}(N-1)factorial(N)=N×factorial(N−1)

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

![Рисунок 1 – Шаги рекурсивной функции для вычисления факториала](/images/lectures/oaip/04/image-01.webp)

![Рисунок 2 – Проверка правильности вычислений](/images/lectures/oaip/04/image-02.webp)

Как видно по рисункам, наша рекуррентная формула вычисления факториала верна. Формализуем аналитическое представление рекуррентной формулы в виде программного кода:

text

```text
int Factorial(int n)
{
    if (n == 0)           // базовый случай
        return 1;
    else                  // рекурсивный случай
        return n * Factorial(n - 1);
}
```

Рекурсивные функции могут пригодиться при вычислении чисел прогрессии Фибоначчи.

0,1,1,2,3,5,8,13,21,34,55,89,144,233,…0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, \dots0,1,1,2,3,5,8,13,21,34,55,89,144,233,…

В последовательности Фибоначчи каждое число представляет собой сумму двух предыдущих чисел, а первые два числа — это 0 и 1.

Общая формула для вычисления n – го числа Фибоначчи выглядит следующим образом:

F(n)=F(n−1)+F(n−2),где F(1)=F(2)=1F(n) = F(n-1) + F(n-2), \quad \text{где } F(1) = F(2) = 1F(n)=F(n−1)+F(n−2),где F(1)=F(2)=1

Определение последовательности Фибоначчи рекурсивно по своей природе, поскольку n-е число зависит от двух предыдущих чисел. Это означает, что задачу можно разделить на меньшие подзадачи, а это рекурсия.

text

```text
int GetFibNumber(int n)
{
    if (n == 0)
        return 0;
    if (n == 1)
        return 1;
    return Fibonacci(n - 1) + Fibonacci(n - 2);
}
```

Каждая **рекурсивная задача** имеет две необходимые вещи:

- Рекуррентное соотношение, определяющее состояния задачи и то, как основная задача может быть разделена на меньшие подзадачи. Сюда также входит базовый случай для прекращения рекурсии.
- Дерево рекурсии, демонстрирующее несколько первых вызовов рассматриваемой функции (если не все). Взгляните на дерево рекурсии для рекурсивного соотношения последовательности Фибоначчи:

![Рисунок 3 – Дерево рекурсии](/images/lectures/oaip/04/image-03.webp)

## §4.9 Делегаты и функциональные объекты

**Делегат**

— это тип, который представляет ссылку на метод с определённой сигнатурой (возвращаемым типом и списком параметров).

**Делегат** позволяет работать с методами как с объектами: сохранять их в переменные, передавать в другие методы, возвращать из методов.

**Делегаты** являются основой для таких возможностей C#, как **события**, **обратные вызовы** (*callbacks*) и **лямбда-выражения**.

**Делегат** объявляется с помощью ключевого слова `delegate`, за которым следует сигнатура метода.

csharp

```csharp
delegate int Operation(int x, int y);
```

Этот **делегат** может ссылаться на любой метод, который принимает два параметра типа **int**, возвращает значение типа **int**.

После объявления **делегат** можно использовать как тип переменной:

csharp

```csharp
1. Operation op = Add;  // op ссылается на метод Add
2. int result = op(3, 5); // вызов метода через делегат
```

## §4.10 Добавление методов в делегат

**Делегат** в C# может ссылаться не только на один метод, но и на несколько методов одновременно. Такой **делегат** называется **мультикаст-делегатом** (*multicast delegate*).

Когда такой делегат вызывается, все присоединённые методы выполняются по очереди — один за другим.

Для добавления метода в делегат используется оператор +=:

text

```text
делегат += имя_метода;
```

Это подписывает метод на выполнение при вызове делегата. Но важно понимать, что тип делегата и его сигнатура должны совпадать с методом.

csharp

```csharp
void SayHello() => Console.WriteLine("Привет!");
void SayGoodbye() => Console.WriteLine("Пока!");
 
Action message = null;
 
message += SayHello;   // добавили первый метод
message += SayGoodbye; // добавили второй
 
message(); // сначала SayHello(), потом SayGoodbye()
```

Если метод больше не должен вызываться, его можно отписать с помощью оператора -=:

csharp

```csharp
message -= SayGoodbye; // метод больше не будет вызываться
```

После этого вызов *message()* выполнит только *SayHello()*.

## §4.11 Анонимные методы

**Анонимный метод**

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

Анонимные методы позволяют писать код более компактно и без «размазывания» логики по разным частям программы.

В C# анонимный метод создаётся с помощью ключевого слова **delegate**, за которым (опционально) указываются параметры и тело в фигурных скобках.

text

```text
delegate(параметры) { тело метода }
```

Для примера можно рассмотреть следующий код:

csharp

```csharp
Action sayHi = delegate 
{
    Console.WriteLine("Привет!");
};
 
sayHi(); // вызов анонимного метода
```

Так же можно передавать параметры, как и в обычном методе:

csharp

```csharp
Action<string> greet = delegate(string name)
{
    Console.WriteLine("Привет, " + name);
};
 
greet("Анна"); // вывод: Привет, Анна
```

Зачем же нужны **анонимные методы**? Иногда требуется выполнить простое действие — например, обработать событие, передать логику в метод, отфильтровать данные. Писать для этого отдельный именованный метод — избыточно.

csharp

```csharp
Timer timer = new Timer(delegate(object obj)
{
    Console.WriteLine("Таймер сработал!");
}, null, 1000, 1000);
```

Здесь **анонимный метод** вызывается каждую секунду. Создавать отдельный метод ради одной строки — неудобно.

Так же стоит ввести понятие **захват переменных**. **Анонимный метод** может использовать переменные из внешнего кода — это называется **захват переменных**.

csharp

```csharp
int count = 0;
Action increment = delegate
{
    count++; // использует переменную извне
    Console.WriteLine(count);
};
 
increment(); // 1
increment(); // 2  
```

Переменная **count** захватывается, и её значение сохраняется между вызовами.

## §4.12 Лямбда – выражения и их использование

**Лямбда – выражения** представляют упрощенную запись анонимных методов. **Лямбда – выражения** позволяют создать емкие лаконичные методы, которые могут возвращать некоторое значение и которые можно передать в качестве параметров в другие методы.

Лямбда – выражения имеют следующий синтаксис: слева от лямбда-оператора => определяется список параметров, а справа блок выражений, использующий эти параметры:

text

```text
(список_параметров) => выражение.
```

Например:

csharp

```csharp
class Program
{
   delegate int Operation(int x, int y);

   static void Main(string[] args)
   {
      Operation operation = (x, y) => x + y;

      Console.WriteLine(operation(10, 20)); // 30
      Console.WriteLine(operation(40, 20)); // 60

      Console.Read();
   }
}
```

Здесь код

text

```text
(x, y) => x + y;
```

представляет **лямбда – выражение**, где **x** и **y** – это **параметры**, а **x + y** – **выражение**. При этом нам не надо указывать тип параметров, а при возвращении результата не надо использовать оператор **return**.

При этом надо учитывать, что каждый **параметр** в **лямбда – выражении** неявно преобразуется в соответствующий параметр **делегата**, поэтому типы параметров должны быть одинаковыми. Кроме того, количество **параметров** должно быть таким же, как и у **делегата**. И возвращаемое значение **лямбда-выражений** должно быть тем же, что и у **делегата**. То есть в данном случае использованное лямбда - выражение соответствует делегату Operation как по типу возвращаемого значения, так и по типу и количеству параметров.

Если лямбда-выражение принимает один параметр, то скобки вокруг параметра можно опустить.

csharp

```csharp
delegate void Printer(string message);

Printer PrintMessage = text => Console.WriteLine("Сообщение: " + text);
```

Если **параметры** в **лямбда-выражении** не требуется, то вместо параметра в выражении используются пустые скобки. Также бывает, что лямбда-выражение не возвращает никакого значения.

csharp

```csharp
using System;
 
// Объявляем делегат без параметров и с void-возвратом
delegate void Action();
 
// Лямбда-выражение: нет параметров → (), нет возврата → просто выводим
Action SayHello = () => Console.WriteLine("Привет, мир!");
 
// Вызов
SayHello();  // Вывод: Привет, мир!
```
