Voici la traduction de l'article du russe vers le français, en respectant toutes les règles :
Методы в C#: параметры, лямбды и делегаты для начинающих
Представь, что ты повар, который каждый день готовит одно и то же блюдо. Вместо того чтобы каждый раз вспоминать весь рецепт заново, ты записываешь его один раз и используешь по необходимости. Методы в программировании работают так же — они позволяют один раз написать блок кода и использовать его столько раз, сколько нужно.
Что такое метод (функция)
Метод — это именованный блок кода, выполняющий определённую задачу. В C# принято называть их «методами» (в других языках — «функциями»), но суть одна.
// Простой метод без параметров и возвращаемого значения
static void SayHello()
{
Console.WriteLine("Привет!");
}
// Вызов метода
SayHello(); // Привет!
SayHello(); // Привет! (можно вызывать сколько угодно раз)
SayHello(); // Привет!
// Метод с возвращаемым значением
static int Add(int a, int b)
{
return a + b; // return возвращает результат
}
int result = Add(3, 5);
Console.WriteLine(result); // 8
Console.WriteLine(Add(10, 20)); // 30
Анатомия метода
// [модификаторы] [тип возврата] [Имя]([параметры])
static int Multiply(int x, int y)
{
int product = x * y;
return product;
}
// static — метод принадлежит классу, не объекту (в Main() нужно static)
// int — тип возвращаемого значения
// Multiply — имя метода (PascalCase по соглашению C#)
// int x, int y — параметры: тип и имя
// void — если метод ничего не возвращает
static void PrintLine(string text)
{
Console.WriteLine(text);
// Нет return (или можно написать return; без значения)
}
Параметры: обычные, ref, out, in
Обычные параметры (по значению)
// По умолчанию параметры передаются ПО ЗНАЧЕНИЮ — копируются
static void TryToChange(int number)
{
number = 999; // Меняем КОПИЮ, оригинал не затронут
Console.WriteLine($"Внутри метода: {number}"); // 999
}
int original = 42;
TryToChange(original);
Console.WriteLine($"После вызова: {original}"); // 42 — не изменилось!
Параметр ref: передача по ссылке
// ref — передаём ссылку на оригинальную переменную
static void DoubleValue(ref int number)
{
number *= 2; // Меняем ОРИГИНАЛ
}
int value = 10;
DoubleValue(ref value); // Нужно указать ref при вызове
Console.WriteLine(value); // 20 — изменилось!
// Пример: обмен двух переменных
static void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 5, y = 10;
Console.WriteLine($"До: x={x}, y={y}"); // До: x=5, y=10
Swap(ref x, ref y);
Console.WriteLine($"После: x={x}, y={y}"); // После: x=10, y=5
Параметр out: возврат нескольких значений
// out — как ref, но переменная не должна быть инициализирована
// Метод ОБЯЗАН присвоить значение out-параметру
static bool TryDivide(int dividend, int divisor, out int quotient, out int remainder)
{
if (divisor == 0)
{
quotient = 0;
remainder = 0;
return false;
}
quotient = dividend / divisor;
remainder = dividend % divisor;
return true;
}
// Вызов:
if (TryDivide(17, 5, out int q, out int r))
{
Console.WriteLine($"17 / 5 = {q} остаток {r}"); // 17 / 5 = 3 остаток 2
}
// Уже знакомый int.TryParse использует out!
if (int.TryParse("42", out int number))
{
Console.WriteLine($"Распарсили: {number}"); // Распарсили: 42
}
// Если out-значение не нужно — используй discard _
int.TryParse("abc", out _); // Результат парсинга нас не интересует
Параметр in: только для чтения
// in — передаём по ссылке, но изменить нельзя (оптимизация для больших структур)
static double CalculateDistance(in Point p1, in Point p2)
{
// p1 = new Point(); // ОШИБКА! in-параметр нельзя изменить
double dx = p2.X - p1.X;
double dy = p2.Y - p1.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
Перегрузка методов: один метод — разные варианты
Перегрузка (overloading) — когда несколько методов имеют одинаковое имя, но разные параметры. Как универсальный нож с разными насадками.
// Три метода Print с разными параметрами
static void Print(string text)
{
Console.WriteLine(text);
}
static void Print(int number)
{
Console.WriteLine($"Число: {number}");
}
static void Print(string text, int times)
{
for (int i = 0; i < times; i++)
{
Console.WriteLine(text);
}
}
// C# выбирает нужный вариант по типу аргументов
Print("Привет"); // → Print(string)
Print(42); // → Print(int)
Print("Привет", 3); // → Print(string, int)
// Ещё пример: вычисление площади
static double Area(double radius) => Math.PI * radius * radius;
static double Area(double width, double height) => width * height;
static double Area(double a, double b, double c) // Треугольник по формуле Герона
{
double s = (a + b + c) / 2;
return Math.Sqrt(s * (s-a) * (s-b) * (s-c));
}
Необязательные параметры и именованные аргументы
// Необязательные параметры (optional parameters)
static void Greet(string name, string greeting = "Привет", bool exclaim = false)
{
string message = $"{greeting}, {name}!";
if (exclaim) message += "!!!";
Console.WriteLine(message);
}
// Варианты вызова
Greet("Саша"); // Привет, Саша!
Greet("Борис", "Здравствуй"); // Здравствуй, Борис!
Greet("Вика", "Хей", true); // Хей, Вика!!!!!!
// Именованные аргументы — порядок не важен!
Greet(name: "Дима", exclaim: true); // Привет, Дима!!!
Greet(greeting: "Хэй", name: "Нина"); // Хэй, Нина!
// Params — переменное количество аргументов
static int Sum(params int[] numbers)
{
int total = 0;
foreach (int n in numbers) total += n;
return total;
}
Console.WriteLine(Sum(1, 2, 3)); // 6
Console.WriteLine(Sum(1, 2, 3, 4, 5)); // 15
Console.WriteLine(Sum(10, 20)); // 30
Лямбда-выражения: компактный код
Лямбда — это анонимная (безымянная) функция. Как стикер с заметкой вместо записной книжки. Очень удобно для коротких операций!
// Обычный метод
static int Double(int x)
{
return x * 2;
}
// То же самое как лямбда
var double1 = (int x) => x * 2;
// Ещё короче (тип вывести из контекста)
var double2 = (int x) => x * 2;
// Примеры лямбд
var square = (int x) => x * x;
var greet = (string name) => $"Привет, {name}!";
var isEven = (int n) => n % 2 == 0;
Console.WriteLine(square(5)); // 25
Console.WriteLine(greet("Вася")); // Привет, Вася!
Console.WriteLine(isEven(4)); // True
Console.WriteLine(isEven(7)); // False
// Лямбда с несколькими операторами
var printInfo = (string name, int age) =>
{
Console.WriteLine($"Имя: {name}");
Console.WriteLine($"Возраст: {age}");
};
Лямбды с LINQ
var numbers = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Лямбды используются внутри LINQ!
var evens = numbers.Where(n => n % 2 == 0); // n => n % 2 == 0 — лямбда
var squared = numbers.Select(n => n * n); // n => n * n — лямбда
var bigEvens = numbers.Where(n => n > 5 && n % 2 == 0);
Console.WriteLine(string.Join(", ", evens)); // 2, 4, 6, 8, 10
Console.WriteLine(string.Join(", ", squared)); // 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
Делегаты: переменные для методов
Делегат — это тип переменной, которая хранит ссылку на метод. Как записная книжка с телефонами: ты не звонишь напрямую — ты достаёшь нужный номер и звонишь.
Func: делегат с возвращаемым значением
// Func — делегат, возвращающий значение
// Последний тип — тип возврата, остальные — типы параметров
Func square = x => x * x;
Func add = (a, b) => a + b;
Func isLong = s => s.Length > 5;
Console.WriteLine(square(4)); // 16
Console.WriteLine(add(3, 7)); // 10
Console.WriteLine(isLong("Привет")); // True
Console.WriteLine(isLong("Да")); // False
// Func в методах
static List Transform(List numbers, Func transform)
{
return numbers.Select(transform).ToList();
}
var nums = new List { 1, 2, 3, 4, 5 };
var doubled = Transform(nums, x => x * 2); // [2, 4, 6, 8, 10]
var squares = Transform(nums, x => x * x); // [1, 4, 9, 16, 25]
Action: делегат без возвращаемого значения
// Action — как void-метод
Action greet = () => Console.WriteLine("Привет!");
Action greetNamed = name => Console.WriteLine($"Привет, {name}!");
Action printInfo = (name, age) =>
Console.WriteLine($"{name}: {age} лет");
greet(); // Привет!
greetNamed("Маша"); // Привет, Маша!
printInfo("Коля", 12); // Коля: 12 лет
// Практический пример: логирование
static void ProcessItems(List items, Action processor)
{
foreach (var item in items)
{
processor(item);
}
}
var fruits = new List { "Яблоко", "Банан", "Вишня" };
ProcessItems(fruits, f => Console.WriteLine($"Обрабатываем: {f}"));
ProcessItems(fruits, f => Console.WriteLine(f.ToUpper()));
Локальные функции и expression-bodied методы
// Expression-bodied метод (стрелочная запись для коротких методов)
static int Add(int a, int b) => a + b;
static string Greet(string name) => $"Привет, {name}!";
static bool IsAdult(int age) => age >= 18;
// Локальные функции — функции внутри функций
static void ProcessData()
{
var numbers = new List { 5, 2, 8, 1, 9, 3 };
// Локальная функция — видна только внутри ProcessData
void PrintSorted(List list)
{
var sorted = list.OrderBy(x => x);
Console.WriteLine(string.Join(", ", sorted));
}
int FindMax(List list) => list.Max();
PrintSorted(numbers); // 1, 2, 3, 5, 8, 9
Console.WriteLine($"Макс: {FindMax(numbers)}"); // Макс: 9
}
Типичные ошибки
Ошибка 1: Забытый return
// ОШИБКА! Не все пути возвращают значение
static int AbsoluteValue(int n)
{
if (n >= 0)
{
return n;
}
// Ошибка компиляции: не все ветки кода возвращают значение
}
// ПРАВИЛЬНО:
static int AbsoluteValue2(int n)
{
if (n >= 0) return n;
return -n;
}
Ошибка 2: Неправильное использование ref/out
// ОШИБКА! Передаём без ключевого слова ref
static void Double(ref int n) { n *= 2; }
int x = 5;
// Double(x); // Ошибка компиляции!
Double(ref x); // Правильно — обязательно указываем ref
Итого
- ✅ Методы — именованные блоки кода для повторного использования
- ✅ ref — передача по ссылке (изменения видны снаружи)
- ✅ out — вывод нескольких значений из метода
- ✅ Перегрузка — несколько методов с одним именем, разными параметрами
- ✅ Необязательные параметры и именованные аргументы — гибкость вызова
- ✅ Лямбды
x => x * 2— компактные анонимные функции - ✅ Func<T> — делегат с возвращаемым значением
- ✅ Action — делегат без возвращаемого значения (как void)
Практика: калькулятор с методами
// Класс-калькулятор с разными методами
static class Calculator
{
// Статические методы
public static double Add(double a, double b) => a + b;
public static double Sub(double a, double b) => a - b;
public static double Mul(double a, double b) => a * b;
public static double Div(double a, double b)
{
if (b == 0) throw new DivideByZeroException("Деление на ноль!");
return a / b;
}
// Метод с несколькими операциями
public static (double result, string formula) Calculate(
double a, string op, double b)
{
double result = op switch
{
"+" => Add(a, b),
"-" => Sub(a, b),
"*" => Mul(a, b),
"/" => Div(a, b),
"^" => Math.Pow(a, b),
_ => throw new ArgumentException($"Неизвестная операция: {op}")
};
return (result, $"{a} {op} {b} = {result}");
}
// Среднее значение (variadic)
public static double Average(params double[] nums)
{
if (nums.Length == 0) throw new ArgumentException("Нет чисел!");
return nums.Sum() / nums.Length;
}
}
// Использование
var (result, formula) = Calculator.Calculate(5, "^", 3);
Console.WriteLine(formula); // 5 ^ 3 = 125
Console.WriteLine(Calculator.Average(85.0, 92.0, 78.0, 91.0)); // 86.5
// Lambda как стратегия
Func[] operations = {
(a, b) => a + b,
(a, b) => a - b,
(a, b) => a * b
};
string[] names = { "+", "-", "*" };
double x = 10, y = 3;
for (int i = 0; i < operations.Length; i++)
Console.WriteLine($"{x} {names[i]} {y} = {operations[i](x, y)}");
Extension methods: расширение существующих типов
// Extension methods — добавляем методы к существующим типам
// (должны быть в статическом классе)
static class StringExtensions
{
// Добавляем метод к string
public static bool IsPalindrome(this string s)
{
s = s.ToLower().Replace(" ", "");
return s == new string(s.Reverse().ToArray());
}
public static string Truncate(this string s, int maxLen, string suffix = "...")
{
if (s.Length <= maxLen) return s;
return s[..(maxLen - suffix.Length)] + suffix;
}
public static string ToTitleCase(this string s)
{
return string.Join(" ", s.Split(' ')
.Select(w => char.ToUpper(w[0]) + w[1..].ToLower()));
}
}
static class IntExtensions
{
public static bool IsEven(this int n) => n % 2 == 0;
public static bool IsPrime(this int n)
{
if (n < 2) return false;
for (int i = 2; i <= Math.Sqrt(n); i++)
if (n % i == 0) return false;
return true;
}
public static IEnumerable To(this int start, int end)
{
for (int i = start; i <= end; i++) yield return i;
}
}
// Теперь используем как обычные методы!
Console.WriteLine("Анна".IsPalindrome()); // True
Console.WriteLine("Кот".IsPalindrome()); // False
Console.WriteLine("Привет мир".ToTitleCase()); // Привет Мир
Console.WriteLine("Длинный текст".Truncate(8)); // Длинный...
Console.WriteLine(7.IsPrime()); // True
Console.WriteLine(4.IsEven()); // True
var primes = 1.To(20).Where(n => n.IsPrime());
Console.WriteLine(string.Join(", ", primes)); // 2, 3, 5, 7, 11, 13, 17, 19