Все способы получить более одного значения из функции в C#
15 августа 2026 г.
Вернуть значение из функции или все же из метода?
Внезапное пояснение терминологии.
В силу того, что C# — это объектно-ориентированный язык, в нем именованные блоки кода называются методами. Сам термин «метод» закреплен за функцией (или процедурой), которая является членом класса или структуры. Тем самым метод всегда «принадлежит» какому-то объекту.
Термины «процедура» и «функция» пришли из структурных языков программирования:
- Функция: блок кода, который обязательно возвращает значение вызывающему коду.
- Процедура: блок кода, который НЕ возвращает значение вызывающему коду.
Поскольку для C# это все «методы», то деление довольно условное:
- Метод, который возвращает значение — функция.
- Метод, который объявлен с ключевым словом
void— процедура.
Это небольшой нюанс, который необходимо знать, чтобы говорить на одном языке и не путаться в терминах.
Четыре основных способа вернуть более одного значения из функции
В C# мы объявляем функцию плюс-минус следующим образом.
int Sum(int a, int b)
{
return a + b;
}
Это пример обычной функции, которая принимает аргументы в виде двух целочисленных значений и возвращает их сумму.
На позиции возвращаемого значения указан тип int, который сигнализирует нам, что мы возвращаем только одно значение и это целое число.
Но бывают ситуации, когда из функции необходимо вернуть более одного значения. В C# есть четыре основных способа как это сделать. Каждый способ имеет свои особенности, которые влияют на выбор в зависимости от ситуации.
1. Вернуть экземпляр класса или структуры
Самый популярный способ вернуть несколько значений.
Для начала объявляем какой-нибудь класс, в котором при помощи полей и/или свойств перечисляем все его содержимое.
class Person
{
string Name { get; set; }
int Age { get; set; }
}
Далее в качестве возвращаемого значения функции мы можем указать наш новый тип, а в теле функции создавать объект этого типа и заполнить его.
Person GetKolya()
{
return new Person
{
Name = "Коля",
Age = 18
}
}
Это вариант, который обычно стоит выбрать, если необходимо вернуть набор данных разного типа, объединяемых по смыслу.
Типичные сценарии использования:
- Выполнить какую-то обработку над объектом и вернуть его новое состояние.
- Создать новый объект из набора входных параметров («Simple Factory»).
- Определить DTO, чтобы вернуть какое-то ограниченное представление об объекте (например, из базы данных).
2. Вернуть коллекцию
Представим, что мы хотим создать единичную матрицу заданного размера. Для этого мы можем использовать массивы.
int[,] CreateIdentityMatrix(int n)
{
var matrix = new int[n, n];
for (int i = 0; i < n; i++)
{
matrix[i, i] = 1;
}
return matrix;
}
Разумеется, ситуации не ограничиваются математическими задачами: на месте массива могла быть любая другая коллекция.
Это вариант, который обычно стоит выбрать, если необходимо вернуть набор однородных данных, где все элементы одного типа.
Типичные сценарии использования:
- Собрать структуру данных по заданному алгоритму (например, игровое поле для игры «Сапер» с расставленными минами).
- Вернуть список однородных элементов (собрать на месте, из другой коллекции или из базы данных).
3. Вернуть кортеж
Начиная с C# 7.0, появились удобные кортежи (System.ValueTuple).
Они мне очень нравятся, например, за возможность менять значения переменных местами в одну строку.
Практического смысла в этом мало, но это выглядит красиво в решениях на LeetCode.
(int A, int B) SpawValues(int a, int b)
{
return (b, a)
}
Это вариант, который обычно стоит выбрать, если мы не хотим создавать для наших значений новый класс, а результат целиком не переиспользуется в других местах.
Типичные сценарии использования:
- Вернуть несколько значений из функции, для которых создавать отдельную структуру слишком «громоздко».
4. Вернуть дополнительное значение с модификатором параметра out
Нередко возникает необходимость сначала проверить значение и, если оно проходит проверку, выполнить с ним различные манипуляции.
В этом плане моим любимым примером является функция TryGetComponent из Unity. Приведу пример ее упрощенной реализации.
bool TryGetComponent<T>(out T component)
{
// Ищем компонент нужного типа
// _components — список компонентов, который объявлен где-то в классе...
foreach (var component in _components)
{
if (component is T typedComponent)
{
component = typedComponent;
return true;
}
}
component = default;
return false;
}
В одной из моих игр (Cosmic Bustle) именно таким образом делаются проверки в компоненте, который отвечает за обработку столкновений игрока с объектами в игровом мире.
Проверки в подобном стиле хороши своей компактностью, потому что можно проверить, обладает ли некий объект, с которым столкнулся игрок, необходимым компонентом, который бы его однозначно определял
(например, все враги в мире имеют компонент Enemy), и сразу выполнить некую логику, если объект оказался нужного типа.
if (gameObject.TryGetComponent<Enemy>(out enemy))
{
// Логика взаимодействия с врагом
}
Иначе бы конструкция выглядела как-то так:
Enemy enemy = object.Components.FirstOrDefault(x => x is Enemy);
if (enemy != null)
{
// Логика взаимодействия с врагом
}
Выше уже разобран типичный сценарий использования, так что на этом и остановимся.