Действительно ли аргумент передается по значению?
Я читаю книгу Джона Скита "C# in Depth"
, На странице 74 он сказал, что все предполагают, что аргументы, передаваемые в функцию по ссылке, в то же время передают по значению, и в качестве примера он показывает этот код, который должен доказать StringBuilder в вызывающем коде, не был изменен. Между тем внутри нашей функции экземпляр StringBuilder изменился.
private static void SayHello(StringBuilder s)
{
s.AppendLine("Hello");
}
Но мой эксперимент показал, что объект StringBuilder получил изменения - мы увидим "Hello" в консоли. Что здесь не так? Или что не так с моим пониманием этого примера?
private static void Main(string[] args)
{
var s = new StringBuilder();
Console.WriteLine(s.ToString());
SayHello(s);
Console.WriteLine(s.ToString());
Console.ReadLine();
}
private static void SayHello(StringBuilder s)
{
s.AppendLine("Hello");
}
2 ответа
После прочтения страницы он говорит следующее.
Теперь запомните, что значением переменной ссылочного типа является ссылка, а не сам объект. Вы можете изменить содержимое объекта, на который ссылается параметр, без передачи самого параметра по ссылке.
public static void Main()
{
var s = new StringBuilder();
Console.WriteLine(s.ToString());
SayHello(s);
Console.WriteLine(s.ToString());
Console.ReadLine();
}
private static void SayHello(StringBuilder s)
{
s.AppendLine("Hello");
s = null;
}
//output will be Hello
Когда этот метод вызывается, значение параметра (ссылка на StringBuilder) передается по значению. Если бы я изменил значение переменной построителя внутри метода - например, с помощью оператора builder = null; - это изменение не было бы замечено вызывающей стороной, вопреки мифу.
Он так много объясняет, что вы не можете уничтожить объект StringBuilder
в SayHello вы можете просто изменить содержимое этого объекта. Я тоже этого не знал.
РЕДАКТИРОВАТЬ: О вашем комментарии
Так много, когда вы проходите s
в SayHello, вы создаете новую ссылку и обе ссылки в Main
метод и в SayHello
ссылаются на тот же объект. Но они существуют как две разные ссылки.
Если ты пишешь в
private static void SayHello(StringBuilder s)
{
s.AppendLine("Hello");
s = new StringBuilder();
}
Ссылка на в SayHello
будет указывать на другой объект, но это ничего не изменит в методе Main, поскольку ссылка указывает на предыдущий объект, в котором вы написали Hello. Итак, снова вывод будет Hello.
Если вы хотите передать точную ссылку, вы должны использовать ref
Так что если вы напишите
private static void SayHello(ref StringBuilder s)
{
s.AppendLine("Hello");
s = null;
//you will receive an exception in Main method, because now the value in the reference is null.
}
Рассмотрим следующую аналогию:
- Вы покупаете новую квартиру (
new StringBuilder()
) и вы получите ключ (s
), который предоставляет вам доступ. - Вы нанимаете уборщика (
s.AppendLine
) и вы даете ему копию своего ключа, чтобы он мог получить доступ к вашей квартире и очистить ее. - Уборщик использует копию вашего ключа, убирает вашу квартиру и, поскольку ему так хочется, перепрограммирует ключ, чтобы открыть какую-то другую квартиру.
- Вы заканчиваете свою ежедневную работу и возвращаетесь домой. Ваш ключ просто открывает дверь в вашу квартиру, и вы находите ее чистой.
Это в основном то, что происходит, когда вы передаете ссылочный тип по значению.
Теперь рассмотрим следующее:
- Вы покупаете новую квартиру (
new StringBuilder()
) и вы получите ключ (s
), который предоставляет вам доступ. - Вы нанимаете уборщика (
s.AppendLine
) и вы даете ему свой ключ, чтобы он мог убрать вашу квартиру. - Уборщик убирает вашу комнату и, потому что ему так хочется, перепрограммирует ключ, который вы дали ему, чтобы открыть какую-то другую квартиру. Он оставляет твой ключ под ковриком.
- Вы заканчиваете свою ежедневную работу и возвращаетесь домой. Вы пытаетесь использовать свой ключ, но дверь не открывается. Через окно видно, что ваша квартира чистая.
Вот что происходит, когда вы передаете ссылочный тип по ссылке.