Коли я передаю функцію stringa, чи передається вказівник на вміст рядка, або вся передана рядок до функції в стеку, як би це structбуло?
Коли я передаю функцію stringa, чи передається вказівник на вміст рядка, або вся передана рядок до функції в стеку, як би це 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ключового слова.