🔷 C#

Cadenas y trabajo con texto en C# (.NET 6): guía detallada

Методы в 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