Когда я передаю 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) понятие передачи по ссылке (и передачи ссылочного типа по ссылке в 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
ключевого слова.