Как строки передаются в.NET?
Когда я прохожу string
на функцию, является указателем на переданное содержимое строки или вся строка передана функции в стеке, как struct
было бы?
3 ответа
Чтобы ответить на ваш вопрос, рассмотрите следующий код:
void Main()
{
string strMain = "main";
DoSomething(strMain);
Console.Write(strMain); // What gets printed?
}
void DoSomething(string strLocal)
{
strLocal = "local";
}
Есть три вещи, которые вы должны знать, чтобы предсказать, что здесь произойдет, и понять, почему это происходит.
- Строки являются ссылочными типами в C#. Но это только часть картины.
- Они также неизменны, поэтому каждый раз, когда вы делаете что-то, что похоже на изменение строки, это не так. Создается совершенно новая строка, на нее указывает ссылка, а старая отбрасывается.
- Даже если строки являются ссылочными типами,
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.Write(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.Write(objMain.ChangeMe); //it's 5 on objMain
DoSomething(objMain); //now it's 0 on objLocal
Console.Write(objMain.ChangeMe); //it's also 0 on objMain
}
Ах, но...
... строки неизменны! Нет никаких ChangeMe
свойство для установки. Ты не можешь сделать strLocal[3] = 'H';
как вы могли бы с массивом символов в стиле C; вместо этого вы должны создать совершенно новую строку. Единственный способ изменить strLocal
это указать ссылку на другую строку, и это ничего не значит, что вы делаете strLocal
может повлиять на strMain
, Значение является неизменным, а ссылка является копией.
Таким образом, даже если строки являются ссылочными типами, передача их по значению означает, что все, что происходит в вызываемом объекте, не повлияет на строку в вызывающем объекте. Но так как они являются ссылочными типами, вам не нужно копировать всю строку в памяти, когда вы хотите передать ее.
Другие ресурсы:
- Вот лучшая статья, которую я читал о разнице между ссылочными типами и типами значений в C# и о том, почему ссылочный тип не совпадает с передаваемым по ссылкам параметром.
- Как обычно, Эрик Липперт также имеет несколько отличных постов в блоге на эту тему.
- У него тоже есть кое-что о неизменности.
Строки в C# являются неизменяемыми ссылочными объектами. Это означает, что ссылки на них передаются (по значению), и после создания строки вы не можете ее изменить. Методы, которые создают измененные версии строки (подстроки, усеченные версии и т. Д.), Создают измененные копии исходной строки.
Строки - это особые случаи. Каждый экземпляр неизменен. Когда вы изменяете значение строки, вы выделяете новую строку в памяти.
Таким образом, только ссылка передается вашей функции, но когда строка редактируется, она становится новым экземпляром и не изменяет старый экземпляр.