Вопрос по function, .net, string, parameter-passing – Как строки передаются в .NET?

108

Когда я прохожуstring является ли указатель на переданное содержимое строки или вся строка, переданная функции в стеке, какstruct было бы?

Ваш Ответ

3   ответа
245

Чтобы ответить на ваш вопрос, рассмотрите следующий код:

void Main()
{
    string strMain = "main";
    DoSomething(strMain);
    Console.Write(strMain); // What gets printed?
}
void DoSomething(string strLocal)
{
    strLocal = "local";
}

Есть три вещи, которые вы должны знать, чтобы предсказать, что здесь произойдет, и понять, почему это происходит.

Strings are reference types in C#. But this is only part of the picture. They are also immutable, so any time you do something that looks like you're changing the string, you aren't. A completely new string gets created, the reference is pointed at it, and the old one gets thrown away. Even though strings are reference types, strMain isn't passed by reference. It's a reference type, but the reference is being passed by value. This is a tricky distinction, but it's a crucial one. Any time you pass a parameter without the ref keyword (not counting out parameters), you've passed something by value.

Но что это значит?

Passing reference types by value: You're already doing it

В C # есть две группы типов данных:reference types а такжеvalue types, Есть также два способа передачи параметров в C #:by reference а такжеby value, Они звучат одинаково и их легко спутать. Они НЕ одно и то же!

Если вы передаете параметр ЛЮБОГО типа, и вы не используетеref ключевое слово, то вы передали его по значению. Если вы передали его по значению, то вы действительно передали его копию. Ноif the parameter was a reference type, then the thing you copied was the reference, не то, на что это указывало.

Вот первая строка нашегоMain метод:

string strMain = "main";

На самом деле в этой строке мы создали две вещи: строку со значениемmain где-то хранится в памяти, а ссылочная переменная называетсяstrMain указывая на это.

DoSomething(strMain);

Теперь мы передаем эту ссылкуDoSomething, Мы передали его по значению, что означает, что мы сделали копию. Но это ссылочный тип, так что это означает, что мы скопировали ссылку, а не саму строку. Теперь у нас есть две ссылки, каждая из которых указывает на одно и то же значение в памяти.

Inside the callee

Вот вершинаDoSomething метод:

void DoSomething(string strLocal)

нетref Ключевое слово, как обычно. ТакstrLocal ISN & APOS; тstrMain, но они оба указывают на одно и то же место. Если мы "изменим"strLocal, как это...

strLocal = "local";   

... у нас нетchanged сохраненное значение, как таковое. Мы повторно указали ссылку. Мы взяли ссылку под названием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" 
Immutability is important

Давайте изменим сценарий на секунду. Представьте, что мы работаем не со строками, а с некоторым изменяемым ссылочным типом, например, с классом, который вы создали.

class MutableThing
{
    public int ChangeMe { get; set; }
}

если тыfollow ссылкаobjLocal к объекту, на который он указывает, вы можете изменить его свойства:

void DoSomething(MutableThing objLocal)
{
     objLocal.ChangeMe = 0;
} 

Там еще только одинMutableThing в памяти, и скопированная ссылка и исходная ссылка все еще указывают на это.СвойстваMutableThing itself have changed:

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   
}
Ah, but...

... строки неизменны! Там нетChangeMe свойство для установки. Вы не можете сделатьstrLocal[3] = 'H'; как вы могли бы с массивом символов в стиле C; вместо этого вы должны создать целую новую строку. Единственный способ изменитьstrLocal это указать ссылку на другую строку, и это ничего не значит, что вы делаетеstrLocal может повлиять наstrMain, Значение является неизменным, а ссылка является копией.

Таким образом, даже если строки являются ссылочными типами, передача их по значению означает, что все, что происходит в вызываемом объекте, не повлияет на строку в вызывающем объекте. Но так как ониare ссылочные типы, вам не нужно копировать всю строку в памяти, когда вы хотите передать ее.

Further resources: Here is the best article I've read on the difference between reference types and value types in C#, and why a reference type isn't the same as a reference-passed parameter. As usual, Eric Lippert also has several excellent blog posts on the subject. He has some great stuff on immutability, too.
прочитай это:pooyakhamooshi.blogspot.co.uk/search?q=string
@JustinMorgan Не создавать мертвую ветку комментариев, но я думаю, что комментарий TheLight имеет смысл, если вы думаете на C. В C данные - это просто блок памяти. Ссылка - это указатель на этот блок памяти. Если вы передаете весь блок памяти функции, которая называется "передача по значению". Если вы передаете указатель, он называется «передача по ссылке». В C # отсутствует понятие передачи во всем блоке памяти, поэтому они переопределяют «передачу по значению» означать передачу указателя внутрь. Это кажется неправильным, но указатель тоже просто блок памяти! Для меня терминология довольно произвольна
Вы правы, но я думаю, что @roliu ссылался на такую функцию, какFoo(string bar) можно рассматривать какFoo(char* bar) в то время какFoo(ref string bar) было быFoo(char** bar) (или жеFoo(char*& bar) или жеFoo(string& bar) в C ++). Конечно, это не то, как вы должны думать об этом каждый день, но на самом деле это помогло мне, наконец, понять, что происходит под капотом. Cole Johnson
@TheLight - Извините, но вы здесь не правы, когда говорите: "Тип ссылки передается по ссылке по умолчанию." По умолчанию все параметры передаются по значению, но со ссылочными типами это означает, чтоthe reference is passed by value. Вы связываете ссылочные типы со ссылочными параметрами, что понятно, потому что это очень запутанное различие. УвидетьPassing Reference Types by Value section here. Ваша связанная статья совершенно правильна, но на самом деле она поддерживает мою точку зрения.
@JustinMorgan Я согласен с тем, что смешивать терминологию C и C # плохо, но, хотя мне понравился пост Липперта, я не согласен с тем, что представление о ссылках как указателях, в частности, затрудняет здесь что-либо. В блоге рассказывается о том, как представление ссылки в качестве указателя придает ей слишком много силы. Я знаю, чтоref Ключевое слово имеет полезность, я просто пытался объяснить, почему одинmight мысль о передаче ссылочного типа по значению в C # выглядит как «традиционная» (то есть C) понятие передачи по ссылке (и передача ссылочного типа по ссылке в C # больше похоже на передачу ссылки на ссылку по значению).
10

Строки - это особые случаи. Каждый экземпляр неизменен. Когда вы изменяете значение строки, вы выделяете новую строку в памяти.

Таким образом, только ссылка передается вашей функции, но когда строка редактируется, она становится новым экземпляром и не изменяет старый экземпляр.

Строкиnot особый случай в этом аспекте. Очень легко создавать неизменяемые объекты, которые могут иметь одинаковую семантику. (То есть экземпляр типа, который не предоставляет метод для его изменения ...)
Так вот почему StringBuilder лучше при построении строк? (Таким образом, строковые символы помещаются вокруг памяти). Cole Johnson
@ Энигмативность По этой логике тогдаUri (класс) иGuid (структура) также являются частными случаями. Я не вижу какSystem.String действует как «тип значения» больше, чем другие неизменные типы ... класса или структуры.
Строка - это особый случай, но это не имеет отношения к этому вопросу. Тип значения, ссылочный тип, любой тип в этом вопросе будет действовать одинаково.
@pst - Строки имеют особую семантику создания - в отличие отUri & Амп;Guid - Вы можете просто присвоить строковое литеральное значение строковой переменной. Строка выглядит изменчивой, какint переназначается, но неявно создает объект - нетnew ключевое слово.
23

Строки в C # являются неизменяемыми ссылочными объектами. Это означает, что ссылки на них передаются (по значению), и после создания строки ее нельзя изменить. Методы, которые создают измененные версии строки (подстроки, усеченные версии и т. Д.), Создают измененныеcopies оригинальной строки.

Похожие вопросы