$ sudo teach IT
Модуль 8 · Функции

Урок 8.2 — Параметры и аргументы

Передача данных в метод: ref, out, in, params и значения по умолчанию.

Представь, что метод — это автомат по приготовлению кофе. Ты кладёшь в него ингредиенты (вода, зёрна) — это аргументы. Автомат принимает их в специальные отсеки (чашка для воды, контейнер для зёрен) — это параметры. Сегодня мы поймём разницу, а также научимся делать параметры необязательными, передавать их по имени и даже менять исходные значения внутри метода.

🔤

Параметр vs аргумент — в чём разница?

Параметр (parameter)
Аргумент (argument)
Переменная в объявлении метода
Конкретное значение при вызове
«Отсек» в автомате
То, что кладёшь в отсек
Определён в сигнатуре метода
Передаётся в момент вызова
C# · Параметры и аргументы
// a и b — ПАРАМЕТРЫ (переменные внутри метода)
static int Add(int a, int b)
{
    return a + b;
}

// 5 и 3 — АРГУМЕНТЫ (конкретные значения при вызове)
int result = Add(5, 3);
//                ↑  ↑
//            аргументы
📐

Позиционные параметры

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

C# · Позиционные параметры
static void PrintInfo(string name, int age, string city)
{
    Console.WriteLine($"{name}, {age} год, {city}");
}

// Аргументы строго по порядку:
PrintInfo("Анна", 25, "Москва");
//           ↑      ↑     ↑
//         name   age   city

// ❌ Ошибка: несовпадение типов
// PrintInfo("Анна", "Москва", 25);  // порядок нарушен

// ❌ Ошибка: несовпадение количества
// PrintInfo("Анна", 25);  // не хватает city
💡 Позиционная передача — самый простой и распространённый способ. Но если параметров много (больше 3-4), легко запутаться в порядке. Тут на помощь приходят именованные аргументы.
🏷️

Значения по умолчанию (необязательные параметры)

Можно задать параметру значение по умолчанию. Тогда при вызове этот аргумент можно не передавать:

C# · Параметры по умолчанию
// Параметры с default значениями
static void Greet(string name, string greeting = "Привет")
{
    Console.WriteLine($"{greeting}, {name}!");
}

Greet("Анна");              // Привет, Анна!
Greet("Борис", "Здравствуй");  // Здравствуй, Борис!

// Несколько default-параметров
static double CalculatePrice(
    double basePrice,
    double taxRate = 0.20,
    double discount = 0.0)
{
    double afterTax = basePrice * (1 + taxRate);
    return afterTax * (1 - discount);
}

Console.WriteLine(CalculatePrice(1000));       // 1200
Console.WriteLine(CalculatePrice(1000, 0.10)); // 1100
Console.WriteLine(CalculatePrice(1000, 0.10, 0.5)); // 550

⚠️ Правило: параметры со значениями по умолчанию должны идти после всех обязательных параметров. void Foo(int a, int b = 0, int c = 1) — ок. void Foo(int a = 0, int b) — ошибка компиляции!

🔖

Именованные аргументы (Named Arguments)

Можно явно указывать, какому параметру какой аргумент передаётся, используя имя параметра:

C# · Named arguments
static void CreateUser(string name, int age, string city, bool isPremium)
{
    Console.WriteLine($"Создан пользователь: {name}, {age}, {city}");
    Console.WriteLine($"Премиум: {isPremium}");
}

// Позиционный вызов — легко ошибиться
CreateUser("Анна", 25, "Москва", true);

// Именованный вызов — порядок не важен!
CreateUser(age: 30, name: "Борис", isPremium: false, city: "Питер");

// Смешивать можно, но сначала позиционные, потом именованные
CreateUser("Вика", city: "Казань", age: 28, isPremium: true);

// ❌ Ошибка: позиционные после именованных
// CreateUser(name: "Олег", 35, "Тверь", false);
💡 Именованные аргументы особенно полезны, когда: (1) у метода много параметров, (2) много значений по умолчанию, (3) читаемость важнее краткости.
🔄

Передача по значению vs по ссылке

По умолчанию C# передаёт аргументы по значению — внутрь метода копируется значение переменной. Метод работает с копией, оригинал не меняется:

C# · По значению
static void TryChange(int x)
{
    x = 100;  // меняем копию
    Console.WriteLine($"Внутри: {x}");  // 100
}

int a = 5;
TryChange(a);
Console.WriteLine($"Снаружи: {a}");  // 5 — оригинал не изменился!

Но для ссылочных типов (массивы, списки, объекты) передаётся ссылка, поэтому содержимое можно изменить:

C# · Ссылочный тип
static void ChangeFirst(int[] arr)
{
    arr[0] = 999;  // меняем элемент массива (оригинал!)
}

int[] numbers = { 1, 2, 3 };
ChangeFirst(numbers);
Console.WriteLine(numbers[0]);  // 999 — массив изменился!

// Но если попытаться пересоздать массив:
static void TryReassign(int[] arr)
{
    arr = new int[] { 100, 200, 300 };  // меняем ссылку (копию)
    Console.WriteLine(arr[0]);  // 100
}

TryReassign(numbers);
Console.WriteLine(numbers[0]);  // 999 — а не 100!
// Ссылка скопирована, оригинал не изменился
🔧

ref, out и in — передача по ссылке

Иногда нужно, чтобы метод изменил оригинальную переменную. Для этого используются модификаторы ref, out и in:

ref — передача по ссылке (с инициализацией)

Переменная должна быть инициализирована до передачи. Метод может читать и изменять её:

C# · ref
static void Double(ref int x)
{
    x = x * 2;  // меняем оригинал
}

int value = 5;
Double(ref value);   // ← указываем ref при вызове!
Console.WriteLine(value);  // 10 — оригинал изменился

out — для возврата нескольких значений

Похож на ref, но переменную не нужно инициализировать до вызова. Метод обязан присвоить ей значение:

C# · out
// Классика: TryParse
static bool TryParseInt(string text, out int result)
{
    if (int.TryParse(text, out result))
        return true;
    result = 0;
    return false;
}

// Использование:
if (TryParseInt("123", out int number))
{
    Console.WriteLine($"Успех: {number}");  // Успех: 123
}

// Классический пример из .NET:
string input = "42";
if (int.TryParse(input, out int parsed))
{
    Console.WriteLine(parsed + 10);  // 52
}

// Несколько out-параметров:
static void Split(string fullName,
    out string firstName, out string lastName)
{
    string[] parts = fullName.Split(' ');
    firstName = parts[0];
    lastName = parts.Length > 1 ? parts[1] : "";
}

Split("Иван Петров", out var first, out var last);
Console.WriteLine($"Имя: {first}, Фамилия: {last}");

in — только чтение (для больших структур)

Передаётся по ссылке, но метод не может изменить значение. Используется для производительности с большими структурами:

C# · in
struct BigStruct
{
    public int X, Y, Z, W;
}

static void Process(in BigStruct data)
{
    // data.X = 10;  // ❌ Ошибка! in — только для чтения
    Console.WriteLine(data.X + data.Y + data.Z + data.W);
}

var big = new BigStruct { X = 1, Y = 2, Z = 3, W = 4 };
Process(in big);
ref
out
Инициализация до вызова
Обязательна
Не нужна
Метод может читать
Да
Да (но обычно не читают)
Метод обязан присвоить
Нет
Да
Типичное применение
Изменить оригинал
Вернуть несколько значений
🔢

params — переменное количество аргументов

Ключевое слово params позволяет передавать любое количество аргументов одного типа. Они собираются в массив внутри метода:

C# · 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
Console.WriteLine(Sum(1, 2, 3, 4, 5));    // 15
Console.WriteLine(Sum());                  // 0 (пустой массив)
Console.WriteLine(Sum(42));                // 42

// Также можно явно передать массив:
Console.WriteLine(Sum(new int[] { 10, 20, 30 }));  // 60

// params + обычные параметры:
static void PrintReport(string title, params string[] items)
{
    Console.WriteLine($"=== {title} ===");
    foreach (var item in items)
        Console.WriteLine($"- {item}");
}

PrintReport("Список покупок", "Хлеб", "Молоко", "Яйца");
// Вывод:
// === Список покупок ===
// - Хлеб
// - Молоко
// - Яйца

⚠️ Правила params: (1) может быть только один params-параметр, (2) он должен быть последним в списке, (3) массив должен быть одномерным.

🧩

Практический пример: всё вместе

C# · Пример с разными параметрами
using System;

class Program
{
    // Позиционные + default + params
    static void ProcessOrder(
        string customer,
        string product,
        int quantity = 1,
        double discount = 0,
        params string[] notes)
    {
        Console.WriteLine($"Заказ для {customer}: {product} x{quantity}");
        if (discount > 0)
            Console.WriteLine($"Скидка: {discount * 100}%");
        if (notes.Length > 0)
        {
            Console.WriteLine("Примечания:");
            foreach (var n in notes)
                Console.WriteLine($"  - {n}");
        }
    }

    // ref + out
    static void ApplyBonus(ref double salary, out string message)
    {
        if (salary > 100000)
        {
            message = "Премия не положена (слишком высокая ЗП)";
            return;
        }
        salary *= 1.2;
        message = $"Премия начислена! Новая ЗП: {salary}";
    }

    static void Main()
    {
        // Именованные + default + params
        ProcessOrder(
            customer: "Анна",
            product: "Ноутбук",
            quantity: 2,
            discount: 0.1,
            notes: "Подарочная упаковка", "Срочно");

        Console.WriteLine();

        // ref + out
        double salary = 80000;
        ApplyBonus(ref salary, out string msg);
        Console.WriteLine(msg);  // Премия начислена! Новая ЗП: 96000
        Console.WriteLine($"Исходная переменная: {salary}");  // 96000
    }
}
🧪

Мини-тест

Вопрос 1

В чём разница между параметром и аргументом?

Ответ: Параметр — переменная в объявлении метода. Аргумент — конкретное значение при вызове.

Вопрос 2

Что нужно сделать, чтобы метод мог изменить оригинальную переменную типа int?

Ответ: Использовать ref: static void Foo(ref int x) и передавать Foo(ref a).

Вопрос 3

Чем out отличается от ref?

Ответ: out не требует инициализации до вызова, но метод обязан присвоить значение. ref — переменная должна быть инициализирована.

Вопрос 4

Сколько может быть params-параметров в методе и где он должен располагаться?

Ответ: Только один, и он должен быть последним параметром в списке.

🧩

Задание для тренировки

Напиши программу «Управление заказами», которая:

  1. Содержит метод с default-параметрами: static double CalculateTotal(double price, int qty = 1, double tax = 0.2, double discount = 0)
  2. Содержит метод с out: static bool ApplyPromo(ref double price, out string promoMessage) — если цена > 5000, даёт скидку 15%
  3. Содержит метод с params: static void PrintReceipt(string customer, params string[] items)
  4. В Main демонстрирует все варианты вызова: позиционный, именованный, с ref, с params
C# · Решение
using System;

class Program
{
    static double CalculateTotal(
        double price,
        int qty = 1,
        double tax = 0.20,
        double discount = 0)
    {
        double subtotal = price * qty;
        double afterTax = subtotal * (1 + tax);
        return afterTax * (1 - discount);
    }

    static bool ApplyPromo(ref double price, out string message)
    {
        if (price > 5000)
        {
            price *= 0.85;
            message = $"Промо применено! Новая цена: {price:F2}";
            return true;
        }
        message = "Цена ниже 5000, промо недоступно";
        return false;
    }

    static void PrintReceipt(string customer, params string[] items)
    {
        Console.WriteLine($"Чек для {customer}:");
        if (items.Length == 0)
        {
            Console.WriteLine("  (пусто)");
            return;
        }
        for (int i = 0; i < items.Length; i++)
            Console.WriteLine($"  {i + 1}. {items[i]}");
    }

    static void Main()
    {
        // Позиционные + default
        double total1 = CalculateTotal(1000, 2);
        Console.WriteLine($"Итого 1: {total1}");

        // Именованные
        double total2 = CalculateTotal(
            price: 2000, discount: 0.1, qty: 3);
        Console.WriteLine($"Итого 2: {total2}");

        // ref + out
        double price = 8000;
        if (ApplyPromo(ref price, out string msg))
            Console.WriteLine(msg);
        Console.WriteLine($"Цена после промо: {price}");

        // params
        PrintReceipt("Анна", "Мышь", "Клавиатура", "Монитор");
        PrintReceipt("Борис");  // без товаров
    }
}
📌

Что важно запомнить

1️⃣
Параметры — это «дырки» в объявлении, аргументы — то, чем ты их заполняешь при вызове. По умолчанию передача по значению.
2️⃣
Значения по умолчанию делают параметры необязательными. Они должны располагаться после обязательных параметров.
3️⃣
Именованные аргументы Method(param: value) — улучшают читаемость и позволяют менять порядок. Сначала позиционные, потом именованные.
4️⃣
ref — чтение и запись, out — только запись (для возврата), in — только чтение. ref требует инициализации, out — нет.
5️⃣
params позволяет передавать переменное количество аргументов. Только один, всегда последний. Собирает аргументы в массив.

Тест: 8.2: Параметры и аргументы

3 вопроса

Параметры

Premium