# Ссылочные и значимые типы в C# Ключевые слова ref, out, in Что нужно знать новичку

## **Глава 1. Значимые и ссылочные типы в С#** <a href="#glava-1.-znachimye-i-ssylochnye-tipy-v-s" id="glava-1.-znachimye-i-ssylochnye-tipy-v-s"></a>

В современном мире программирования, где выбор типа данных влияет на производительность, понимание различий между значимыми и ссылочными типами в языке C# становится важным аспектом разработки. Давайте вместе разберемся, как эти типы взаимодействуют с памятью, а также какие инструменты предоставляет C# для оптимизации кода.

В программировании важно понимать, что тип данных - это способ организации и хранения информации в программе. В языке C#, выделяются два основных типа: *значимые* и *ссылочные*.

Мы проанализируем, как они хранят и передают данные, и какие у них особенности. Здесь вы поймете, почему выбор типа может повлиять на производительность и эффективность вашего кода.

***

### Почему у нас есть два типа памяти? <a href="#pochemu-u-nas-est-dva-tipa-pamyati" id="pochemu-u-nas-est-dva-tipa-pamyati"></a>

В C# **значимые типы** данных (*примитивные типы*), такие как *int, double, bool* и т.д., содержат одно значение. С другой стороны, **ссылочные типы** данных - они же *составные типы*. Ссылочный тип может иметь ссылки на другие объекты и другие примитивные типы данных. К примеру ссылочный тип может в один момент держать ссылку на объект, который занимает много памяти. А затем сменить ссылку на объект поменьше.

Ссылочные типы данных хранятся в *куче*, значимые - в *стеке*.

{% hint style="info" %}
*Куча* - это большая неупорядоченная область памяти, где всё хранится вразброс, и объекты могут расширяться и уменьшаться динамически, не мешая друг другу.\
\
\&#xNAN;*Стек* - непрерывная упорядоченная память, в которой всё хранится друг за другом, и он не рассчитан на хранение значений, которые могут динамически менять свой объем по памяти.
{% endhint %}

В *стеке* хранятся данные, которые не могут внезапно изменить занимаемую ими память. Здесь могут храниться **значимые типы**, которые в процессе жизни не изменят свой объем занимаемой памяти. Например: `int = 2;` - здесь `int` как занимал *4 байта памяти*, так и будет столько занимать, какое бы мы число в него не записали (в пределах его границ, до 2^32)\
Также в *стеке* хранятся все ссылки. Ведь ссылка не может изменять свой размер, это просто адрес, указывающий на значение.&#x20;

**Ссылочный тип** содержит ссылки на несколько значений, и каждое из них должно храниться в памяти. Если объекты внезапно начнут увеличиваться или уменьшаться - занимать больше или меньше памяти, они могут залезть на область, где хранятся совершенно другие данные.

По этой причине ссылочным типам требуется динамическая память (*куча*), в то время как примитивным типам данных требуется статическая память (*стек*). Для лучшего понимания посмотрите на следующую картинку:

<figure><img src="https://2743800200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwxT0U6r7Plf4kYjtMJzB%2Fuploads%2Fp0qRC0I84QOLojGuMFXB%2Fimage.png?alt=media&#x26;token=0399e7eb-40f1-49d5-8160-461a2c5f313b" alt=""><figcaption><p>Хранение значимых типов</p></figcaption></figure>

Давайте разберемся с типом значения на примере. Как вы можете видеть на изображении, сначала мы создаем целочисленную переменную с именем `x`, а затем присваиваем это целочисленное значение `x` другой целочисленной переменной с именем `y`. В этом случае выделение памяти для этих двух переменных будет производиться внутри стековой памяти.

В .NET, когда мы присваиваем значение из одной переменной в другую переменную, это создает совершенно другую копию в памяти *стека*. Это то, что вы можете видеть на рисунке выше. Таким образом, если вы измените значение одной переменной, это не повлияет на другую переменную. В .NET эти типы данных называются типами значений. Примерами типов значений являются *bool, byte, char, decimal, double, enum, float, long, sbyte, int, short, ulong , struct, uint ushort*.

Теперь разберемся с ссылочным типом на примере. Пожалуйста, взгляните на следующее изображение:

<figure><img src="https://2743800200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwxT0U6r7Plf4kYjtMJzB%2Fuploads%2FtuFPGSC9PqnDWuh7a5az%2Fimage.png?alt=media&#x26;token=e788c303-a50e-44cd-ae2a-a8051571c95a" alt=""><figcaption><p>Хранение ссылочных типов</p></figcaption></figure>

Здесь сначала мы создаем объект, т.е. `arrayA`, а затем назначаем этот объект другому объекту, т.е. `arrayB`. В этом случае обе ссылочные переменные (`arrayA` и `arrayB`) будут указывать на одну и ту же ячейку памяти.

Теперь, когда вы изменяете одну из них, это также влияет на другой объект. Такие типы данных называются ссылочными типами в .NET. *Класс*, *интерфейс*, *объект*, *строка* и *делегат* являются примерами ссылочных типов.

***

## Пример работы со Значимыми типами <a href="#primer-raboty-so-znachimymi-tipami" id="primer-raboty-so-znachimymi-tipami"></a>

```csharp
int a = 0;
int b = 10;

Console.WriteLine(a); // 0
Console.WriteLine(b); // 10

b = a;

Console.WriteLine(a); // 0
Console.WriteLine(b); // 0

a++;

Console.WriteLine(a); // 1
Console.WriteLine(b); // 0
```

Здесь мы объявили две переменные значимого типа `a` и `b`.

```csharp
int a = 0;
int b = 10;
```

Затем провели операцию *присваивания*, и переменной `b` присвоили значение переменной `a`. Переменная `b` получила *копию* значения от `a`.

```csharp
b = a;

Console.WriteLine(a); // 0
Console.WriteLine(b); // 0
```

И наконец, меняя значение переменной `a`, на переменную `b` это не возымеет эффекта. При присваивании `b = a;` мы не получили ссылку на ячейку памяти `a`, а получили *копию*. По этой причине, при операции `a++` изменилось лишь значение, хранящееся в `a`.

```csharp
a++;

Console.WriteLine(a); // 1
Console.WriteLine(b); // 0
```

***

## Пример работы с Ссылочными типами <a href="#primer-raboty-s-ssylochnymi-tipami" id="primer-raboty-s-ssylochnymi-tipami"></a>

Со значимыми типами разобрались, они при операции *присваивания* ( = ) отдают копию своего значения. Теперь поговорим о ссылочных типах, как ведут себя они? Ниже пример кода, демонстрирующий все возможные ситуации, о которых нужно знать для начала:

```csharp
int[] arrayA = [0, 0, 0];
int[] arrayB = [1, 2, 3, 4];

Print(arrayA);           // 0 0 0
Print(arrayB);           // 1 2 3 4

arrayB = arrayA;

Print(arrayA);           // 0 0 0
Print(arrayB);           // 0 0 0

arrayB[0] = 10;

Print(arrayA);          // 10 0 0
Print(arrayB);          // 10 0 0

arrayB = null;

Print(arrayA);          // 10 0 0
Print(arrayB);          // null
```

> Определение метода Print:

```csharp
void Print(int[] array)
{
    if(array == null)
    {
        Console.WriteLine("null");
        return;
    }

    foreach (var number in array)
    {
        Console.Write($"{number} ");
    }

    Console.WriteLine();
}
```

Для начала мы объявили два массива - переменные с ссылочным типом данных:

```csharp
int[] arrayA = [0, 0, 0];
int[] arrayB = [1, 2, 3, 4];

Print(arrayA);           // 0 0 0
Print(arrayB);           // 1 2 3 4
```

Следом мы смотрим на поведение: при *присваивании* одного массива другому, пока ничего необычного не заметим, просто в `arrayB` лежат те же данные, что и в `arrayA`.

```csharp
arrayB = arrayA;

Print(arrayA);           // 0 0 0
Print(arrayB);           // 0 0 0
```

Самое интересное начинается, когда мы начинаем менять состояние одной из переменных ссылочного типа. После операции `arrayB[0] = 10;` данные изменятся и в `arrayA`. Происходит это потому, что при операции `arrayB = arrayA;` - мы в переменную `arrayB` скопировали ссылку на адрес переменной `arrayA`. Теперь они указывают на один и тот же массив.

```csharp
arrayB[0] = 10;

Print(arrayA);          // 10 0 0
Print(arrayB);          // 10 0 0
```

{% hint style="info" %}
Важно отметить, что это не одна и та же ссылка на `arrayA` - это две разные ссылки, но ведут они к одной области памяти. Почему это важно, будет видно в следующем примере.
{% endhint %}

В следующем коде демонстрируется поведение, когда мы *обрубаем* ( = `null` ) ссылку у переменной `arrayB` на область памяти, где хранится значение. Заметьте, мы *обрубили* ссылку для одной переменной, но вторая всё ещё указывает на наш массив. Поэтому было важно знать, что передается именно **копия ссылки**. Одну ссылку *обрубили*, вторая ещё указывает на массив. Ещё это называют “занулить”.

```csharp
arrayB = null;

Print(arrayA);          // 10 0 0
Print(arrayB);          // null
```

Вот небольшая демонстрация того, что же произошло в последнем примере кода:

<figure><img src="https://2743800200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwxT0U6r7Plf4kYjtMJzB%2Fuploads%2FhvzHqHf6tgDTn0iqTR1J%2Fimage.png?alt=media&#x26;token=12daf936-c983-4132-ad4b-2d998dd46030" alt=""><figcaption><p>Логика ссылочных операций</p></figcaption></figure>

На этом всё, теперь вы знаете базовые принципы работы с ссылочными и значимыми типами. Теперь вы готовы изучить принцип работы `ref`, `out` и `in`.

***

## **Глава 2. Применение ref к Значимым и Ссылочным Типам** <a href="#glava-2.-primenenie-ref-k-znachimym-i-ssylochnym-tipam" id="glava-2.-primenenie-ref-k-znachimym-i-ssylochnym-tipam"></a>

В этой главе мы исследуем, как ключевое слово `ref` влияет на передачу значимых и ссылочных типов в методы. Я предоставлю примеры использования и объясню, почему это важно для эффективной работы с данными.

***

## Применение ref к значимым типам (struct) <a href="#primenenie-ref-k-znachimym-tipam-struct" id="primenenie-ref-k-znachimym-tipam-struct"></a>

Мы уже знаем, как ведут себя простые значимые типы в C# (int, float, bool и т.д.). Для следующего примера создадим свой значимый тип `struct Dog`. Обычные присвоения мы разобрали, теперь рассмотрим ещё и передачу переменных в метод.

{% hint style="info" %}
Переменные, при передаче в качестве аргумента метода - ведут себя так же, как и при обычном присваивании. Значимые типы копируются, ссылочные - передают копию ссылки на себя.
{% endhint %}

Ниже приведён код, демонстрирующий работу со значимыми типами, у которых мы можем изменять состояние, то есть поля:

```csharp
Dog dog = new Dog() { Name = "Doggy" };
Console.WriteLine(dog.Name);         // Doggy

ChangeField(dog);
Console.WriteLine(dog.Name);         // Doggy
```

```csharp
void ChangeField(Dog dog)
{
    dog.Name = "Changed Name";
}
```

```csharp
struct Dog
{
    public string Name;
}
```

Здесь происходит копирование переменной `dog`. Снаружи метода данные не сохранятся, просто потому что мы работали с копией, но не с оригиналом. Внутри метода теперь новый объект, занимающий точно столько же памяти.

{% hint style="warning" %}
Да, внутри метода мы заметим изменения в объекте `dog` и сможем этим воспользоваться. Например, сможем вывести их в теле метода через команду -`Console.WriteLine(dog.Name);` и увидим вывод “ChangedName”.
{% endhint %}

Теперь немножко изменим наш пример, чтобы разобраться с модификатором `ref`.

```csharp
Dog dog = new Dog() { Name = "Doggy" };
Console.WriteLine(dog.Name);         // Doggy

ChangeField(ref dog);
Console.WriteLine(dog.Name);         // ChangedName
```

```csharp
void ChangeField(ref Dog dog)
{
    dog.Name = "Changed Name";
}
```

{% hint style="warning" %}
Для работы с `ref` мы должны модификатор `ref` к параметру `Dog dog` при **объявлении** метода. Также мы обязаны указывать этот модификатор и при **вызове** этого метода.
{% endhint %}

В чём разница? Всё просто, `ref` говорит, что будет передана ссылка на ту ячейку памяти, где находится оригинальный объект.

{% hint style="info" %}
В случае со значимыми типами, мы так избегаем копирования целой переменной. Это играет на руку производительности, так как мы не тратим ресурсы на выделение памяти для копий. В главе “Примеры работы с in” это будет продемонстрировано.
{% endhint %}

{% hint style="danger" %}
В программировании мы не пользуемся `ref` ради оптимизации в каждом методе, просто потому что становится тяжелее следить за долгоживущими переменными, которые могут меняться множество раз. Хотя где-то такой оптимизации обязательно найдется место. Но это бьёт по читаемости кода.
{% endhint %}

Пару слов про изменение значения переменной, если она передана без `ref`. Думаю ответ уже очевиден, но вот пример для наглядности:

```csharp
void ChangeReference(Dog dog)
{
    dog = new Dog() { Name = "new Dog name" }; // значение new Dog() присваивается копии
    dog = default;                             // Тоже присваивается копии
}
```

{% hint style="danger" %}
Никакое из этих изменений снаружи метода не применится, так как мы работаем с копией.
{% endhint %}

{% hint style="info" %}
Ключевое слово `default` - назначает переменной значение по умолчанию. Значение по умолчанию зависит от типа переменной. Для ссылочных это `null`, для значимых - зависит от конкретного типа.\
Для `int` это `0`, для `bool` это `false` и т.д. Для более объемных объектов - объект с полями, которые установлены в значение по умолчанию.\
Подробнее о ключевых словах и значениях по умолчанию найдёте в конце статьи.
{% endhint %}

***

## Применение ref к ссылочным типам (class) <a href="#primenenie-ref-k-ssylochnym-tipam-class" id="primenenie-ref-k-ssylochnym-tipam-class"></a>

Пример ниже продемонстрирует работу с `ref` с ссылочными типами данных. Здесь мы попробуем поменять состояние объекта (`cat.Name = "Changed Name";`), и попробуем поменять ему ссылку (`cat = null;`).

```csharp
Cat cat = new Cat() { Name = "Kitty" };
Console.WriteLine(cat.Name);           // Kitty

ChangeField(cat);
Console.WriteLine(cat.Name);           // Changed Name

ChangeReference(cat);
Console.WriteLine(cat.Name);           // Changed Name
Console.WriteLine(cat == null);        // False
```

```csharp
void ChangeField(Cat cat)
{
    cat.Name = "Changed Name";
}
```

```csharp
private static void ChangeReference(Cat cat)
{
    cat = new Cat() { Name = "new Cat name" };
    cat = null;
}
```

```csharp
class Cat
{
    public string Name;
}
```

В этот раз ситуация немножко меняется, ведь после срабатывания вызова `ChangeField(cat);` - поле успешно будет изменено. А после вызова `ChangeReference(cat);` изменений никаких не будет.

{% hint style="info" %}
Но почему так? Тип ведь “ссылочный”, и передается по ссылке, разве нет?

В таком определении есть одна недосказанность - передается здесь лишь копия ссылки.\
В стек кладётся ещё одна копия ссылки, которая тоже указывает на наш объект, их теперь две. Да, хоть это и копия ссылки, ведёт-то она на один и тот же класс, верно? Поэтому мы без проблем может менять его состояние.

Но при этом, так как в руках у нас лишь копия ссылки, меняя ее таким образом: `cat = new Cat()` или `cat = null;` - мы изменим значение конкретно для этой скопированной ссылки. Оригинальная ссылка всё ещё никак не тронута, и ничем не заменена.
{% endhint %}

{% hint style="warning" %}
Похожая ситуация была продемонстрирована в главе “Ссылочные типы”.
{% endhint %}

Осталось понять, что мы получим, если передать ссылочный тип с помощью `ref`.\
Следующий код описывает метод, который заменит оригинальную ссылку на объект, и по итогу мы получим абсолютно нового кота, с именем “Banana”.\
Вместе с `ref`, ссылка не копируется, а передается *ссылка на ссылку*.

```csharp
void ChangeReference(ref Cat cat)
{
    cat = new Cat() { Name = "Banana" };
}
```

{% hint style="danger" %}
При работе с `ref` - если внутри метода написать `cat = null;` Мы оборвём оригинальную ссылку на кота. И в следующий раз, при попытке доступа к `cat` - получим *NullReferenceException*.
{% endhint %}

***

## **Глава 3: Частые Ошибки при Работе с ref** <a href="#glava-3-chastye-oshibki-pri-rabote-s-ref" id="glava-3-chastye-oshibki-pri-rabote-s-ref"></a>

Код ниже описывает ручное расширение массива, и добавление ещё одной буквы в конец.

{% code lineNumbers="true" %}

```csharp
private static void Main(string[] args)
{
    char[] letters = new char[] { 'A', 'B', 'C' };
    
    Print(letters);             // A B C
    Add(letters, 'D');
    Print(letters);             // A B C
}

private static void Add(char[] array, char letter)
{
    char[] tempArray = new char[array.Length + 1];

    for (int i = 0; i < array.Length; i++)
    {
        tempArray[i] = array[i];
    }

    int lastIndex = tempArray.Length - 1;
    tempArray[lastIndex] = letter;

    array = tempArray;
}

private static void Print(char[] array)
{
    foreach (var item in array)
    {
        Console.Write($"{item} ");
    }

    Console.WriteLine();
}
```

{% endcode %}

{% hint style="danger" %}
Ошибка здесь - считать, что на `строке 22` `array = tempArray;`ссылка на массив всё таки изменилась.
{% endhint %}

Мы уже знаем как ведут себя ссылочные типы, при передаче в методы - они передают туда копию своей ссылки. Поэтому - изменения на 22 строке произошли лишь с копией ссылки array, оригинальная ссылка изменений не получила.

Решения два:

* использовать модификатор `ref`, чтобы изменять не копию ссылки, а оригинальную ссылку. Тогда изменения сохранятся. Вывод будет `A B C D`, вместо `A B C`. Решение этим способом будет предоставлено в главе “Упражнения”.
* использовать `return`. И возвращать мы будем ссылку на измененный массив `char[] tempArray`. А снаружи метода эти возвращаемые данные используем, чтобы изменить ссылку на массив вот так:

```csharp
private static void Main(string[] args)
{
    char[] letters = new char[] { 'A', 'B', 'C' };
    
    Print(letters);             // A B C
    letters = Add(letters, 'D');
    Print(letters);             // A B C D
}

private static char[] Add(char[] array, char letter)
{
    char[] tempArray = new char[array.Length + 1];

    for (int i = 0; i < array.Length; i++)
    {
        tempArray[i] = array[i];
    }

    int lastIndex = tempArray.Length - 1;
    tempArray[lastIndex] = letter;

    return tempArray;
}
```

Заметьте, изменения мы внесли в трёх местах:

* `строка 6` - `letters = Add(letters, 'D');`\
  Присваиваем ссылку, которую возвращает метод.
* `строка 10` - `private static char[] Add(char[] array, char letter)`\
  Изменяем возвращаемый тип с `void` на `char[]`.
* `строка 22` - `return tempArray;`\
  Возвращаем ссылку на расширенный массив.

***

## **Глава 4: Ключевые Слова out и in: Глубже в понимание передачи параметров** <a href="#glava-4-klyuchevye-slova-out-i-in-glubzhe-v-ponimanie-peredachi-parametrov" id="glava-4-klyuchevye-slova-out-i-in-glubzhe-v-ponimanie-peredachi-parametrov"></a>

Немного расширим понимание передачи параметров с использованием ключевых слов `out` и `in`. Объясним, как эти инструменты делают код более гибким и удобным для использования.

Фактически, эти два ключевых слова передают переменную по ссылке, но у них есть ещё задачи. К примеру `out` будет требовать, чтобы мы в обязательном порядке инициализировали переменную с этим модификатором внутри нашего метода. Иначе возникнет ошибка компиляции, требующая ее инициализировать.

Ключевое слово `in` - наоборот, не разрешит нам менять эту переменную внутри метода. Позже в язык добавили синтаксис, делающий ту же самую работу - `ref readonly`. У него есть свои особенности, но в основном они делают одну и ту же работу.

***

### Пример применения out <a href="#primer-primeneniya-out" id="primer-primeneniya-out"></a>

Частый пример использования `out`:

```csharp
class Program
{
    private static void Main(string[] args)
    {
        Database database = new Database();
        string nameToFind = "Иван";

        if (database.TryFind(nameToFind, out string result))
        {
            Console.WriteLine($"{nameToFind} найден. Результат: {result}");
            // Результат: Иван
        }
        else
        {
            Console.WriteLine($"{nameToFind} не оказалось в коллекции. Результат: {result}");
            // Результат:  (здесь будет просто пустая строка, так как мы присваивали default)
        }
    }

}

class Database
{
    private string[] _names;

    public Database()
    {
        _names = ["Павел", "Иван", "Николай"];
    }

    public bool TryFind(string nameToFind, out string result)
    {
        foreach (var name in _names)
        {
            if (nameToFind == name)
            {
                result = name;
                return true;
            }
        }

        result = default;
        return false;
    }
}
```

Здесь мы создали класс `Database`, который будет хранить массив имён. В нём объявлен метод `TryFind`, который как раз и использует модификатор `out`.

Похожие примеры реализации вы можете найти в различных типах данных, по типу статического метода TryParse у `int`. Вот некоторые из них:

* У целочисленного типа `int` - `int.TryParse(string value, out int result);`
* У типа перечисления `Enum` - `Enum.TryParse(string value, out TEnum result);`
* У словаря `Dictionary` - `TryGetValue(T key, out T value);`
* У очереди `Queue` - `TryPeek(out T result)` и `TryDequeue(out T result);`

{% hint style="info" %}

Конкретно в нашем случае, есть одна весомая причина использовать `out` - Мы не хотим возвращать `null` напрямую в `return`.\
\
В такой ситуации, нам бы пришлось каждый раз проверять результат выполнения метода на `null`. К примеру будь он таким: `public string TryFind(string name)`. Проверка на `null` будет дублироваться везде, где мы вызовем этот метод.

В нашем коде, в `return` возвращается `bool`, и мы смогли просто вызвать этот метод в внутри условной конструкции `if`. И сразу можем принимать решения, основываясь на значении аргумента с `out`.

* Если вернулось `true`, значит объект найден, и внутри `if` мы им спокойно пользуемся.
* Если вернулось `false`, значит объект равен `null`, и эту ситуацию мы обрабатываем в `else`.
  {% endhint %}

***

### Пример применения in <a href="#primer-primeneniya-in" id="primer-primeneniya-in"></a>

Что по поводу `in`, вы его вряд ли скоро примените. У него есть свои нюансы, но в основном, это просто передача значения по ссылке, с поправкой на *только для чтения*.\
Менять внутри метода эту переменную не получится. Хотя в случае со значимыми типами - мы сэкономим память на копировании, так как передали ссылку, а не копию.

Ниже показаны результаты кода, в котором была создана структура с полем `int[]` на `100_000` элементов. Выполним с массивом простую операцию, будем считать сумму всех элементов от `0` до `100_000`.

На изображении видно, что сначала структура передавалась в метод обычным способом, то есть копировалась. Затем она передавалась по ссылке, через `in`. Заметьте, что второй способ занял на выполнение операции меньше тиков процессора.

<figure><img src="https://2743800200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwxT0U6r7Plf4kYjtMJzB%2Fuploads%2FufFFyghEccW0lInn6Tfb%2Fimage.png?alt=media&#x26;token=81253fcb-7d2a-48b7-8b1e-40e81955a3b5" alt=""><figcaption><p>Результат работы с массивом на <code>100000</code> элементов типа <code>int</code></p></figcaption></figure>

Стоит отметить, что разница в скорости между передачей по ссылке и передачей копии - не сильно зависит от размеров массива. По сути, при работе с маленькими массивами, выигрыш в производительности намного больше. Для наглядности, взгляните на результат работы с массивом на `10` элементов типа `int`:

<figure><img src="https://2743800200-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FwxT0U6r7Plf4kYjtMJzB%2Fuploads%2FFyxkMBmlJaA1zemvKIfe%2Fimage.png?alt=media&#x26;token=e86412a7-9de0-4c70-80ca-55431c33457a" alt=""><figcaption><p>Результат работы с массивом на <code>10</code> элементов типа <code>int</code></p></figcaption></figure>

{% hint style="danger" %}
На разных машинах время может быть разным. Также оно зависит от того, в первый раз был запущен код или нет. Главное, что нужно понять из примера - передача по ссылке работает гораздо быстрее, чем копирование. Ощутимая разница в скорости выполнения будет при малом размере структуры.

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

От размера значимого типа зависит насколько сильно “мусорится” память. Сначала удваивая занимаемое пространство копией, а затем эту копию очищая.
{% endhint %}

Код можно посмотреть по ссылке:\
[![](https://gist.github.com/favicon.ico)Example copy-ref.cs](https://gist.github.com/GigaOrts/91d90bbaa0b06f0cdd6b6a90cfe697e5)

***

## **Упражнения** <a href="#uprazhneniya" id="uprazhneniya"></a>

Вы можете себя проверить, насколько хорошо вы усвоили материал, решив упражнения. Не стесняйтесь заглядывать обратно в статью, если не уверены в ответе. Она как раз нужна для того, чтобы дать вам ответы на эти вопросы.

Надеемся, что статья была вам полезна, и вы вынесли для себя что-то новое или закрепили уже имеющиеся знания. Спасибо тебе, читатель, за внимание!

{% hint style="warning" %}
1 - Что будет выведено в консоль?

Примечание: В этой задаче кроется ещё одна тема - *boxind-unboxing*, но для ее решения достаточно знать материал статьи. Здесь важно понимание того, что передастся в ссылочный тип `object b` - копия значения `a`, или ссылка на переменную `a`?
{% endhint %}

```csharp
int a = 0;
object b = a;

a++;

Console.WriteLine(a);
Console.WriteLine(b);
```

{% hint style="warning" %}
2 - Что будет выведено в консоль?
{% endhint %}

```csharp
private static void Main(string[] args)
{
    Human human = new Human() { Age = 18 };

    Example(human);
    Console.WriteLine(human.Age);
}

private static void Example(Human human)
{
    human.Age = 0;
    human = null;
    human = new Human() { Age = 1 };
    human.Age = 2;
}

class Human
{
    public int Age;
}
```

{% hint style="success" %}
3 - Как исправить код, показанный в главе “Частые ошибки при работе с ref”, применив модификатор `ref`? Измените код, и убедитесь, что всё работает правильно.
{% endhint %}

{% hint style="warning" %}
4 - Закончите фразу: “При передаче в метод переменной **ссылочного** типа, она…”:
{% endhint %}

* передается по ссылке, и ссылка на переменную может быть изменена. Изменение ссылки отразится на оригинальной переменной;
* передается по ссылке, но передается лишь копия ссылки. Поэтому снаружи сохранятся только изменения состояния переменной. Изменение ссылки никак не отразится на оригинале;
* передается копия переменной, и изменения внутри метода никак не отразятся на оригинале;
* никак не может быть изменена, без `ref` мы не сможем изменить ни состояние, ни ссылку;

{% hint style="warning" %}
5 - Для чего мы можем применить модификатор out?
{% endhint %}

* Чтобы не дублировать проверки на `null`, если метод может его вернуть;
* Чтобы передать переменную по ссылке, и изменить ее внутри метода;
* Он ничем не отличается от `in` или `ref`, это просто передача по ссылке;

{% hint style="warning" %}
6 - Какой будет результат вывода в консоль?
{% endhint %}

```csharp
private static void Main(string[] args)
{
    int number = 0;

    number = MethodRef(ref number);
    Console.WriteLine(number);
}

private static int MethodRef(ref int number)
{
    number = 2;
    return 10;
}
```

{% hint style="warning" %}
7 - Почему так вышло, что ссылочные типы обязательно надо хранить в куче, а значимые в стеке?
{% endhint %}

* В куче хранятся именно ссылки, поэтому там хранятся ссылочные типы. А в стеке хранятся значения, поэтому там и лежат значимые типы;
* Куча это большая неупорядоченная область памяти, где всё хранится вразброс, и объекты могут расширяться и уменьшаться динамически.\
  В стеке хранятся данные, которые не могут внезапно изменить занимаемую ими память. Стек представлен как непрерывная упорядоченная память, в которой всё хранится друг за другом. Здесь могут храниться значимые типы, которые в процессе жизни не изменят свой объем занимаемой памяти;

## **Ссылки для дальнейшего изучения** <a href="#ssylki-dlya-dalneishego-izucheniya" id="ssylki-dlya-dalneishego-izucheniya"></a>

Чтобы глубже погрузиться в тему, рекомендую для начала ознакомиться с информацией по ссылкам ниже:

[Система типов C# | Microsoft Learn](https://learn.microsoft.com/ru-ru/dotnet/csharp/fundamentals/types/)

[default — справочник по C# - C# | Microsoft Learn](https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/keywords/default)

[Значения по умолчанию типов C# — справка по C# - C# | Microsoft Learn](https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/builtin-types/default-values)

[C# и .NET | Типы значений и ссылочные типы (metanit.com)](https://metanit.com/sharp/tutorial/2.16.php?ysclid=lt8twp1tmc519545878)

[Справочник по C#. Типы значений - C# | Microsoft Learn](https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/builtin-types/value-types)

[Ссылочные типы. Справочник по C# - C# | Microsoft Learn](https://learn.microsoft.com/ru-ru/dotnet/csharp/language-reference/keywords/reference-types)
