← Назад к вопросам

В чем разница между in и out?

2.0 Middle🔥 122 комментариев
#Основы C# и .NET

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между in и out в C# (модификаторы параметров и дженериков)

В C# ключевые слова in и out имеют два основных контекста использования: как модификаторы параметров методов (для передачи по ссылке) и как модификаторы параметров универсальных типов (для дженериков). Эти два использования имеют разные назначения и семантику.


1. Модификаторы параметров методов (аргументов)

Модификатор in

  • Передача параметра по ссылке только для чтения (read-only reference). Параметр передается как ссылка, но метод не может его изменять.
  • Позволяет избежать копирования больших структур при передаче в метод, что улучшает производительность.
  • Введен в C# 7.2 для структур (value types).
  • Может использоваться с методами, свойствами, индексаторами, делегатами.
public struct Point
{
    public double X;
    public double Y;
}

// Метод с параметром in - не может изменять point
public double CalculateDistance(in Point p1, in Point p2)
{
    // p1.X = 10; // Ошибка компиляции: нельзя изменить in-параметр
    return Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
}

// Вызов
Point point1 = new Point { X = 1, Y = 2 };
Point point2 = new Point { X = 4, Y = 6 };
double distance = CalculateDistance(in point1, in point2);

Модификатор out

  • Передача параметра по ссылке с обязательным присвоением (output parameter). Метод обязан присвоить значение параметру перед возвратом.
  • Используется для возврата нескольких значений из метода.
  • Параметр не требует инициализации перед передачей в метод.
// Метод с параметром out - должен присвоить значение
public bool TryParseInt(string input, out int result)
{
    if (int.TryParse(input, out int parsedValue))
    {
        result = parsedValue; // Обязательное присвоение
        return true;
    }
    result = 0; // Обязательное присвоение даже в случае неудачи
    return false;
}

// Вызов
if (TryParseInt("123", out int value))
{
    Console.WriteLine($"Parsed value: {value}");
}

Сравнение in, out и ref как модификаторов параметров:

  • ref — передача по ссылке с возможностью чтения и записи
  • in — передача по ссылке только для чтения
  • out — передача по ссылке только для записи (с обязательным присвоением)

2. Модификаторы параметров универсальных типов (дженерики)

Модификатор out (ковариантность)

  • Ковариантность позволяет использовать более производный тип, чем заданный.
  • Применяется только к возвращаемым значениям методов интерфейса/делегата.
  • Используется с интерфейсами и делегатами.
// Интерфейс с ковариантным параметром
public interface IProducer<out T>
{
    T Produce();
}

public class Animal { }
public class Dog : Animal { }

public class DogProducer : IProducer<Dog>
{
    public Dog Produce() => new Dog();
}

// Ковариантность позволяет присвоить IProducer<Dog> переменной IProducer<Animal>
IProducer<Animal> producer = new DogProducer(); // Без out было бы ошибкой

Модификатор in (контравариантность)

  • Контравариантность позволяет использовать более базовый тип, чем заданный.
  • Применяется только к входным параметрам методов интерфейса/делегата.
// Интерфейс с контравариантным параметром
public interface IConsumer<in T>
{
    void Consume(T item);
}

public class AnimalConsumer : IConsumer<Animal>
{
    public void Consume(Animal animal) { }
}

// Контравариантность позволяет присвоить IConsumer<Animal> переменной IConsumer<Dog>
IConsumer<Dog> consumer = new AnimalConsumer(); // Без in было бы ошибкой
consumer.Consume(new Dog());

Ключевое ограничение: модификаторы in и out для дженериков можно использовать только с делегатами и интерфейсами, но не с классами.


Сводная таблица различий

Критерийin (параметр)out (параметр)in (дженерик)out (дженерик)
НазначениеПараметр только для чтенияВыходной параметрКонтравариантностьКовариантность
Изменение значенияЗапрещеноОбязательноНе применимоНе применимо
Инициализация перед вызовомОбязательнаНе требуетсяНе применимоНе применимо
Область примененияПараметры методовПараметры методовПараметры интерфейсов/делегатовПараметры интерфейсов/делегатов
Введено в версии C#7.21.04.04.0
Типы данныхВ основном структурыЛюбые типыУниверсальные типыУниверсальные типы

Практические рекомендации

  1. Используйте in для больших структур, чтобы избежать накладных расходов на копирование
  2. Используйте out для методов типа "Try...", возвращающих результат операции и значение
  3. Ковариантность (out) и контравариантность (in) делают API более гибкими при работе с наследованием в дженериках
  4. Избегайте out для асинхронных методов — они не поддерживают out-параметры
  5. Соблюдайте принцип минимального обязательства: используйте самый строгий модификатор, который подходит для вашего сценария
// Пример совместного использования
public interface IDataProcessor<in TInput, out TResult>
{
    TResult Process(in TInput input);
}

public class StringToIntProcessor : IDataProcessor<string, int>
{
    public int Process(in string input)
    {
        // input нельзя изменить
        return int.TryParse(input, out int result) ? result : 0;
    }
}

Понимание различий между in и out критически важно для написания эффективного и типобезопасного кода на C#, особенно при работе с производительными приложениями и сложными системами типов.

В чем разница между in и out? | PrepBro