Вопрос о передаче параметров в c # - PullRequest
1 голос
/ 13 января 2011

Приведенный ниже пример взят из статьи Джона Скита " Передача параметра в C # ".

Мой вопрос: почему переменная y равна NOT null в первом примере, а мы видим, что она была изменена во втором примере:

1

void Foo (StringBuilder x)
{
    x = null;
}

...

StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (y);
Console.WriteLine (y==null);

2-

void Foo (StringBuilder x)
{
    x.Append (" world");
}

...

StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (y);
Console.WriteLine (y);

Спасибо

Ответы [ 9 ]

9 голосов
/ 13 января 2011

y равно , а не null в первом примере именно потому, что аргумент передается по значению. Выражение аргумента (просто y) оценивается, и его значение (ссылка на StringBuilder копируется в переменную параметра (x) в качестве начального значения.

Изменение значения x не не меняет значение y.

Второй пример не изменяет значение параметра (x) - он изменяет данные внутри объекта, на который ссылается x. Значение y до сих пор не изменилось: оно по-прежнему ссылается на тот же объект StringBuilder, просто этот объект теперь содержит другие данные.

Если я дам вам лист бумаги с моим домашним адресом, и вы перейдете по этому адресу и раскрасите дом в красный цвет, вы ничего не измените в этом листе бумаги, не так ли? И если бы вы вычеркнули мой адрес на листе бумаги (как в первом примере), это не изменило бы моего представления о моем адресе - так же, как изменило значение x не меняет значение y.

3 голосов
/ 13 января 2011

В обоих примерах ссылка на StringBuilder передается по значению.
Думайте о y как о держателе адреса фактического StringBuilder.
Сам адрес копируется в Foo как параметр стекапоэтому присвоение x = null в первом примере изменяет скопированный адрес, а не фактический адрес, сохраненный в y.
Во втором примере x.Append относится к тому же экземпляру, на который указывает y, и изменяет его, следовательно,изменение видно.

2 голосов
/ 13 января 2011

Как уже упоминал Итай "ссылка на StringBuilder передается по значению" .

Когда вы вызываете Foo(y), он копирует значение y, которое является адресом памяти, в которой находятся данные (значение), поэтому в обоих случаях в x у вас есть копия адреса, куда y переменная указывает. Обратите внимание, что x не ссылается на y, он просто получает копию значения переменной y ссылочного типа. Таким образом, присваивая null x, вы просто вырезали ссылку с x на некоторый адрес (который в нашем случае хранит текст, данные StringBuilder). И поскольку x просто получает копию переменной y и не ссылаясь на нее, y не изменяется. Во втором примере вы просто манипулируете данными в адресе, на который ссылается y, поэтому после обновления метода y обновляется.

Позвольте мне добавить комментарии к обоим случаям

1

void Foo (StringBuilder x) // x gets copy of the address to where y is referencing 
{
    // x now points to null 
    // Remember x and y variables are located in different memory addresses and x is 
    // not referencing y, thus only x is updated.
    x = null;
}
...
* * 2- тысяча двадцать восемь
void Foo (StringBuilder x) // x gets copy of the address to where y is referencing 
{
    // x updates the data located in the memory address to which y is referencing
    // x still points to the same address as y 
    x.Append (" world");
}
...

Попробуйте, это многое прояснит:

void Foo (ref StringBuilder x) // x points to y
{
    x = null;
}
2 голосов
/ 13 января 2011

Ах, это имеет больше смысла ... это потому, как работает указатель. В обоих примерах вы передаете копию указателя на функцию. Представьте, что y - это лист бумаги с надписью «вторая дверь слева», это комната, в которой мы хотим хранить строку.

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

Итак, теперь, когда мы вернулись и хотим напечатать содержимое y, мы следуем инструкциям, находим «вторую дверь слева», открываем ее и находим только «привет»

Во втором примере мы по-прежнему передаем копию бумаги, но теперь нам нужно взаимодействовать, поэтому мы следуем инструкциям, чтобы добраться до «второй двери слева», и здесь мы добавляем строку «мир». ». Итак, теперь, когда мы вернулись и хотим напечатать содержимое y, мы следуем инструкциям, написанным на нашем оригинальном листе бумаги, находим «вторую дверь слева», открываем ее и находим «привет мир».

Если бы мы использовали ключевое слово ref, мы бы не передали копию бумаги, но саму бумагу методу. Поэтому, когда метод стирает то, что написано на бумаге, мы теперь вернулись без указания направления, чтобы найти дверь, где хранится наша строка.

Я знаю, это немного детское объяснение, но обычно оно работает, когда пытаюсь объяснить указатели.

0 голосов
/ 13 января 2011

Чтобы (1) вывести значение true, код должен выглядеть следующим образом:

void Foo (ref StringBuilder x)
{
    x = null;
}

...

StringBuilder y = new StringBuilder();
y.Append ("hello");
Foo (ref y);
Console.WriteLine (y==null);

Mario

0 голосов
/ 13 января 2011

РЕДАКТИРОВАТЬ : Когда я ответил на вопрос, он заявил, что Y был нулевым. С тех пор вопрос был изменен.

Я думаю, что вы написали неправильный код. Я обновил ваш код - обратите внимание, что аргумент Foo теперь передается по ссылке, а не по значению.

class Program
{
    static void Foo(ref StringBuilder x)
    {
        x = null;
    }

    static void Main(string[] args)
    {
        StringBuilder y = new StringBuilder();
        y.Append("hello");
        Foo(ref y);
        Console.WriteLine(y == null); //Prints "true".
    }
}
0 голосов
/ 13 января 2011

Поскольку в первом примере он передает Y в Foo, который устанавливает Y в ноль:

Foo (y);
....


void Foo (StringBuilder x) 
{     
     x = null; 
}
0 голосов
/ 13 января 2011

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

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

Java сделал это так специально, чтобы былонет двусмысленности относительно того, как работает назначение переменных.Если вы хотите присвоить переменную объекту, определенному в методе, вы должны вернуть этот объект, который будет присвоен переменной (например, StringBuilder builder = createStringBuilder();)

0 голосов
/ 13 января 2011

Итак, я подумал, что исправлю свой ответ, чтобы никто не читал старый, поскольку он был неправильным.Я был немного смущен этим вопросом.Ответ таков:

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

public static void DoSomething(MyObject x)
{
    x.MyProperty = 2;
    x = new MyObject(); // now we lost our reference to original object
    x.MyProperty = 3; // The original object is NOT updated.
}

public static void Main(string[] args)
{
    var y = MyObject();
    y.MyProperty = 1;
    DoSomething(y);
    Console.WriteLine(y.MyProperty); // Will output "2"
}
...