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

Какие знаешь способы передачи значимых типов по ссылке?

2.0 Middle🔥 62 комментариев
#C# и ООП#Управление памятью

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

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

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

Способы передачи значимых типов по ссылке в C# и Unity

В C# для передачи значимых типов (Value Types) по ссылке существует несколько механизмов, которые позволяют избежать копирования данных и работать с исходной переменной, изменяя её непосредственно. Это критически важно для оптимизации в Unity-разработке, где часто требуется эффективно модифицировать структуры (как Vector3, Quaternion, Bounds) без накладных расходов на копирование.

1. Ключевое слово ref

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

void ModifyValue(ref int number) {
    number *= 2; // Изменение затронет исходную переменную
}

void Example() {
    int value = 5;
    ModifyValue(ref value);
    Debug.Log(value); // Выведет: 10
}

В Unity можно использовать с кастомными структурами:

struct TransformData {
    public Vector3 Position;
    public Quaternion Rotation;
}

void ApplyOffset(ref TransformData data, Vector3 offset) {
    data.Position += offset; // Изменяется оригинальная структура
}

2. Ключевое слово out

out аналогичен ref, но гарантирует, что метод присвоит значение переменной. Исходная инициализация не требуется, идеально для возврата нескольких значений.

bool TryParseInput(string input, out Vector3 result) {
    // Парсинг строки в Vector3
    if (parsingSuccessful) {
        result = parsedVector;
        return true;
    }
    result = Vector3.zero; // Должно быть присвоено значение
    return false;
}

void Example() {
    if (TryParseInput("1,2,3", out var position)) {
        transform.position = position;
    }
}

3. Ключевое слово in

in передаёт значимый тип по ссылке только для чтения (readonly reference), предотвращая копирование, но запрещая модификацию. Полезно для крупных структур, которые не должны изменяться в методе.

float CalculateDistance(in Vector3 pointA, in Vector3 pointB) {
    // pointA и pointB доступны по ссылке, но изменить их нельзя
    return Vector3.Distance(pointA, pointB);
}

void Example() {
    Vector3 start = Vector3.zero;
    Vector3 end = new Vector3(10, 0, 0);
    float distance = CalculateDistance(in start, in end);
}

4. Использование упаковки (Boxing) — менее эффективный способ

Хотя это не рекомендуется для оптимизации, значимый тип можно передать как object, что вызовет упаковку (boxing) — обёртывание в ссылочный тип.

void ProcessObject(object data) {
    // Распаковка (unboxing) необходима для обратного преобразования
    if (data is Vector3 vector) {
        Debug.Log(vector.magnitude);
    }
}

void Example() {
    Vector3 direction = new Vector3(1, 0, 0);
    ProcessObject(direction); // Boxing происходит здесь
}

Важно: Упаковка создаёт аллокацию в куче (heap allocation), что может привести к сборке мусора (garbage collection), негативно влияя на производительность, особенно в цикле Update() Unity.

5. Ссылочные типы, обёртки над значимыми

Создание класса-обёртки вокруг значимого типа — ещё один опосредованный способ.

class Vector3Wrapper {
    public Vector3 Value;
}

void ModifyWrapper(Vector3Wrapper wrapper) {
    wrapper.Value = Vector3.one; // Изменения отражаются на исходном объекте
}

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

  • Оптимизация структур: Для частого изменения Vector3, RaycastHit, Bounds в методах используйте ref или out, чтобы минимизировать копирование.
  • Read-only для больших данных: Если структура большая (например, кастомная структура с несколькими полями Matrix4x4), а метод только читает её — применяйте in.
  • Избегайте boxing: Упаковка вызывает аллокации — критично для производительности в VR, мобильных приложениях или при работе с тысячами объектов.
  • Использование с нативным кодом: При взаимодействии с Native Plugins или Burst Compiler в DOTS, ref и out обеспечивают прямой доступ к памяти, что ускоряет выполнение.

Пример оптимизации в Unity

Допустим, у вас есть система обработки тысяч точек. Без ref каждая передача Vector3 в метод копирует 12 байт (3 float), что при массовом вызове создаёт нагрузку.

// Неоптимально
void ProcessPoint(Vector3 point) {
    // point — копия
}

// Оптимально
void ProcessPoint(ref Vector3 point) {
    // Работа с оригиналом, без копирования
}

Итак, выбор способа зависит от задачи: ref для изменения, out для возврата, in для защиты от изменений, а упаковку и обёртки стоит избегать в высокопроизводительном коде. В Unity эти приёмы особенно актуальны для снижения аллокаций и увеличения fps.