У мові Сі-Шарп всі типи даних діляться на дві категорії - типи посилання, і типи значень. Вони відрізняються способом зберігання в пам'яті, продуктивністю і іншим. На цьому уроці ми поговоримо, що це все означає, а також про передачу параметрів в метод по посиланню (ключові слова ref і out).
Типи значень
Цю категорію також називають структурними типами. Типи значень зберігаються в стеці. Стек - це область пам'яті, яка використовується для передачі параметрів в методи і зберігання визначених у межах методів локальних змінних. Дані змінної типу значення зберігаються в самій змінної.
До типів значень відносяться:
- Цілочисельні типи (byte, sbyte, char, short, ushort, int, uint, long, ulong);
- Типи з плаваючою комою (float, double);
- Тип decimal;
- Тип bool;
- Призначені для користувача структури (struct);
- Перерахування (enum).
Код нижче показує, що при присвоєнні значення однієї змінної значимого типу інший, подальша зміна однієї з змінних не впливає на іншу. Так тому, що зберігання даних значимого типу відбувається в самій змінної:
static void Main(string[] args)
{
int a = 1;
int b = 2;
b = a;
a = 3;
Console.WriteLine(a); // 3
Console.WriteLine(b); // 1
}
Типи посилання
Змінна посилального типу містить не дані, а посилання на них. Самі дані в цьому випадку вже зберігаються в купі. Купа - це область пам'яті, в якій розміщуються керовані об'єкти, і працює збирач сміття. Складальник сміття звільняє всі ресурси і об'єкти, які вже не потрібні.
До посилальних типів відносяться:
- Класи (class);
- Інтерфейси (interface);
- Делегати (delegate);
- Тип object;
- Тип string.
У коді нижче був створений простий клас, в якому є одне поле типу int. Далі була пророблена така ж процедура, як і в випадку вище, тільки результат вже інший. Після присвоєння одного об'єкта іншому, вони стали вказувати на одну і ту ж область пам'яті (міняємо b - змінюється і a):
class Test
{
public int x;
}
class Program
{
static void Main(string[] args)
{
Test a = new Test();
Test b = new Test();
a.x = 1;
b.x = 2;
b = a; // присвоєння посилання
b.x = 3;
Console.WriteLine(a.x); // 3
Console.WriteLine(b.x); // 3
}
}
Передача параметрів в метод по посиланню. Оператори ref і out
У C # значення змінних за замовчуванням передаються за значенням (в метод передається локальна копія параметра, який використовується при виклику). Це означає, що ми не можемо всередині методу змінити параметр із зовні:
public static void ChangeValue(object a)
{
a = 2;
}
static void Main(string[] args)
{
int a = 1;
ChangeValue(a);
Console.WriteLine(a); // 1
Console.ReadLine();
}
Щоб передавати параметри по посиланню, і мати можливість впливати на зовнішню змінну, використовуються ключові слова ref і out.
Ключове слово ref
Щоб використовувати ref, це ключове слово варто вказати перед типом параметра в методі, і перед параметром при виклику методу:
public static void ChangeValue(ref int a)
{
a = 2;
}
static void Main(string[] args)
{
int a = 1;
ChangeValue(ref a);
Console.WriteLine(a); // 2
Console.ReadLine();
}
У цьому прикладі ми змінили значення зовнішньої змінної всередині методу.
Особливістю ref є те, що змінна, яку ми передаємо в метод, обов'язково повинна бути проініціалізувати значенням, інакше компілятор видасть помилку «Use of unassigned local variable 'a'». Це є головною відмінністю ref від out.
Ключове слово out
Out використовується точно таким же чином як і ref, за винятком того, що параметр не зобов'язаний бути ініціалізованим першим перед передачею, але при цьому в методі переданому параметру обов'язково повинно бути присвоєно нове значення:
public static void ChangeValue(out int a)
{
a = 2;
}
static void Main(string[] args)
{
int a;
ChangeValue(out a);
Console.WriteLine(a); // 2
Console.ReadLine();
}
Якщо не присвоїти нове значення параметру out, ми отримаємо помилку «The out parameter 'a' must be assigned to before control leaves the current method»
Продуктивність
З огляду на той факт, що за замовчуванням в метод передаються параметри за значенням і створюються їх копії в стеці, при використанні складних типів даних (призначені для користувача структури), або якщо метод викликається багато разів, це погано позначиться на продуктивності. В такому випадку також варто використовувати ключові слова ref і out.
Якщо говорити в цілому про посилальні типи і типи значень, то продуктивність програми впаде, якщо використовувати тільки посилальні типи. На створення змінної посилального типу в купі виділяється пам'ять під дані, а в стеку під посилання на ці дані. Для типів значень пам'ять виділяється тільки в стеку. Час на розміщення даних в стеку менше, ніж в купі, це також йде в плюс типам значень в плані продуктивності.