Урок 8.2 — Параметры и аргументы
Передача данных в метод: ref, out, in, params и значения по умолчанию.
Представь, что метод — это автомат по приготовлению кофе. Ты кладёшь в него ингредиенты (вода, зёрна) — это аргументы. Автомат принимает их в специальные отсеки (чашка для воды, контейнер для зёрен) — это параметры. Сегодня мы поймём разницу, а также научимся делать параметры необязательными, передавать их по имени и даже менять исходные значения внутри метода.
Параметр vs аргумент — в чём разница?
// a и b — ПАРАМЕТРЫ (переменные внутри метода)
static int Add(int a, int b)
{
return a + b;
}
// 5 и 3 — АРГУМЕНТЫ (конкретные значения при вызове)
int result = Add(5, 3);
// ↑ ↑
// аргументы
Позиционные параметры
По умолчанию аргументы передаются по позиции: первый аргумент — первому параметру, второй — второму и так далее. Количество и типы должны совпадать:
static void PrintInfo(string name, int age, string city)
{
Console.WriteLine($"{name}, {age} год, {city}");
}
// Аргументы строго по порядку:
PrintInfo("Анна", 25, "Москва");
// ↑ ↑ ↑
// name age city
// ❌ Ошибка: несовпадение типов
// PrintInfo("Анна", "Москва", 25); // порядок нарушен
// ❌ Ошибка: несовпадение количества
// PrintInfo("Анна", 25); // не хватает city
Значения по умолчанию (необязательные параметры)
Можно задать параметру значение по умолчанию. Тогда при вызове этот аргумент можно не передавать:
// Параметры с 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)
Можно явно указывать, какому параметру какой аргумент передаётся, используя имя параметра:
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);
Передача по значению vs по ссылке
По умолчанию C# передаёт аргументы по значению — внутрь метода копируется значение переменной. Метод работает с копией, оригинал не меняется:
static void TryChange(int x)
{
x = 100; // меняем копию
Console.WriteLine($"Внутри: {x}"); // 100
}
int a = 5;
TryChange(a);
Console.WriteLine($"Снаружи: {a}"); // 5 — оригинал не изменился!
Но для ссылочных типов (массивы, списки, объекты) передаётся ссылка, поэтому содержимое можно изменить:
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 — передача по ссылке (с инициализацией)
Переменная должна быть инициализирована до передачи. Метод может читать и изменять её:
static void Double(ref int x)
{
x = x * 2; // меняем оригинал
}
int value = 5;
Double(ref value); // ← указываем ref при вызове!
Console.WriteLine(value); // 10 — оригинал изменился
out — для возврата нескольких значений
Похож на ref, но переменную не нужно инициализировать до вызова. Метод обязан присвоить ей значение:
// Классика: 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 — только чтение (для больших структур)
Передаётся по ссылке, но метод не может изменить значение. Используется для производительности с большими структурами:
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);
params — переменное количество аргументов
Ключевое слово 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) массив должен быть одномерным.
Практический пример: всё вместе
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-параметров в методе и где он должен располагаться?
Ответ: Только один, и он должен быть последним параметром в списке.
Задание для тренировки
Напиши программу «Управление заказами», которая:
- Содержит метод с default-параметрами:
static double CalculateTotal(double price, int qty = 1, double tax = 0.2, double discount = 0) - Содержит метод с out:
static bool ApplyPromo(ref double price, out string promoMessage)— если цена > 5000, даёт скидку 15% - Содержит метод с params:
static void PrintReceipt(string customer, params string[] items) - В Main демонстрирует все варианты вызова: позиционный, именованный, с ref, с params
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("Борис"); // без товаров
}
}
Что важно запомнить
Method(param: value) — улучшают читаемость и позволяют менять порядок. Сначала позиционные, потом именованные.Тест: 8.2: Параметры и аргументы
3 вопроса