Коли я передаю функцію string
a, чи передається вказівник на вміст рядка, або вся передана рядок до функції в стеку, як би це struct
було?
Коли я передаю функцію string
a, чи передається вказівник на вміст рядка, або вся передана рядок до функції в стеку, як би це struct
було?
Відповіді:
Посилання передається; однак це технічно не передається шляхом посилання. Це тонка, але дуже важлива відмінність. Розглянемо наступний код:
void DoSomething(string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.WriteLine(strMain); // What gets printed?
}
Щоб зрозуміти, що тут відбувається, потрібно знати три речі:
strMain
вони не передаються посиланням. Це тип посилання, але сама посилання передається за значенням . Кожен раз , коли ви передаєте параметр без ref
ключового слова (не рахуючи out
параметрів), ви передаєте щось за значенням.Отже, це означає, що ви ... передаєте посилання за значенням. Оскільки це тип посилання, на стек було скопійовано лише посилання. Але що це означає?
Змінні C # є або еталонними типами, або типовими значеннями . Параметри C # або передаються за посиланням, або передаються за значенням . Термінологія тут є проблемою; ці звуки схожі на те саме, але їх немає.
Якщо ви ref
передаєте параметр БУДЬ-якого типу, і ви не використовуєте ключове слово, ви передали його за значенням. Якщо ви передали це за вартістю, те, що ви насправді пройшли, було копією. Але якщо параметр був еталонним типом, то ви скопіювали те, що було скопійовано , а не те, на що він вказував.
Ось перший рядок Main
методу:
string strMain = "main";
У цьому рядку ми створили дві речі: рядок зі значенням, main
збереженим десь у пам'яті, та посилання на змінну, що називається, що strMain
вказує на неї.
DoSomething(strMain);
Тепер ми передаємо це посилання на DoSomething
. Ми передали це за значенням, це означає, що ми зробили копію. Це тип посилання, тож це означає, що ми скопіювали посилання, а не сам рядок. Тепер у нас є дві посилання, які кожне вказують на однакове значення в пам'яті.
Ось верх DoSomething
методу:
void DoSomething(string strLocal)
Немає ref
ключового слова, тому є strLocal
і strMain
дві різні посилання, що вказують на одне значення. Якщо ми перепризначимо strLocal
...
strLocal = "local";
... ми не змінили збережене значення; ми взяли посилання, що називається, strLocal
і націлили його на абсолютно новий рядок. Що відбувається, strMain
коли ми робимо це? Нічого. Він все ще вказує на стару рядок.
string strMain = "main"; // Store a string, create a reference to it
DoSomething(strMain); // Reference gets copied, copy gets re-pointed
Console.WriteLine(strMain); // The original string is still "main"
Давайте змінимо сценарій на секунду. Уявіть, що ми працюємо не з рядками, а з деяким змінним посилальним типом, як-от створений вами клас.
class MutableThing
{
public int ChangeMe { get; set; }
}
Якщо слідувати посиланням objLocal
на об'єкт, на який вказує, ви можете змінити його властивості:
void DoSomething(MutableThing objLocal)
{
objLocal.ChangeMe = 0;
}
У MutableThing
пам'яті залишається лише одна , і скопійована посилання, і оригінальна посилання все ще вказують на неї. Властивості самого MutableThing
себе змінилися :
void Main()
{
var objMain = new MutableThing();
objMain.ChangeMe = 5;
Console.WriteLine(objMain.ChangeMe); // it's 5 on objMain
DoSomething(objMain); // now it's 0 on objLocal
Console.WriteLine(objMain.ChangeMe); // it's also 0 on objMain
}
Ах, але струни незмінні! Немає ChangeMe
властивості встановлювати. Ви не можете робити strLocal[3] = 'H'
в C #, як ви могли, з char
масивом у стилі C ; ви повинні створити зовсім новий рядок. Єдиний спосіб зміни strLocal
- це наведення посилання на інший рядок, а це означає, що нічого, що ви робите, не strLocal
може вплинутиstrMain
. Значення незмінне, а посилання - копія.
Щоб довести свою різницю, ось що відбувається при передачі посилання за посиланням:
void DoSomethingByReference(ref string strLocal)
{
strLocal = "local";
}
void Main()
{
string strMain = "main";
DoSomethingByReference(ref strMain);
Console.WriteLine(strMain); // Prints "local"
}
Цього разу рядок у Main
дійсно змінюється, тому що ви передали посилання, не копіюючи її в стек.
Отже, навіть якщо рядки є еталонними типами, передача їх за значенням означає, що все, що відбувається в абоненті, не вплине на рядок у виклику. Але оскільки вони є еталонними типами, вам не доведеться копіювати весь рядок у пам'ять, коли ви хочете передати його навколо.
ref
ключового слова. Щоб довести, що перехід за посиланням має значення, дивіться це демонстраційне повідомлення: rextester.com/WKBG5978
ref
ключове слово є корисним, я просто намагався пояснити, чому можна подумати про передачу посилального типу за значенням у C #, схоже, на "традиційне" (тобто С) поняття проходження посилання (і передача посилального типу) посилання в C # здається більше схожим на передачу посилання на посилання за значенням).
Foo(string bar)
можна було б розглядати як Foo(char* bar)
тоді Foo(ref string bar)
б Foo(char** bar)
(або , Foo(char*& bar)
або Foo(string& bar)
в C ++). Звичайно, це не так, як ви повинні думати про це щодня, але насправді це допомогло мені нарешті зрозуміти, що відбувається під капотом.
Рядки в C # є незмінними опорними об'єктами. Це означає, що посилання на них передаються навколо (за значенням), і щойно створена рядок, ви не можете її змінити. Методи, що створюють модифіковані версії рядка (підрядки, обрізані версії тощо), створюють модифіковані копії вихідного рядка.
Струни - це особливі випадки. Кожен екземпляр незмінний. Коли ви змінюєте значення рядка, ви виділяєте нову рядок у пам'яті.
Таким чином, лише посилання передається на вашу функцію, але коли рядок редагується, вона стає новим екземпляром і не змінює старий екземпляр.
Uri
(клас) та Guid
(структура) також є особливими випадками. Я не бачу, як System.String
діє на зразок "типу значення" більше, ніж інші непорушні типи ... будь-якого класу чи структури структури.
Uri
& Guid
- ви можете просто призначити рядково -буквальне значення рядкової змінної. Рядок здається мутабельним, як int
переназначений, але створює об'єкт неявно - немає new
ключового слова.