Методы со строковыми параметрами и ref в .Net - PullRequest
0 голосов
/ 01 февраля 2011

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

class DoWork
{
    public static void Beer(string s1)  {
        s1 = s1 + "beer";
    }
    public static void Vodka(ref string s2) {
        s2 = s2 + "vodka";
    }
}

string sss = "I Like ";
DoWork.Beer(sss);
DoWork.Vodka(ref sss);

, поэтому sss будет иметь значение "Мне нравится водка", но почему?

PS или таким образом

DoWork.Vodka(ref sss);
DoWork.Beer(sss);

PPS некоторый код IL из примера

DoWork.Beer:
IL_0000:  ldarg.0     
IL_0001:  ldstr       "beer"
IL_0006:  call        System.String.Concat
IL_000B:  starg.s     00 
IL_000D:  ret

DoWork.Vodka:
IL_0000:  ldarg.0     
IL_0001:  ldarg.0     
IL_0002:  ldind.ref   
IL_0003:  ldstr       "vodka"
IL_0008:  call        System.String.Concat
IL_000D:  stind.ref   
IL_000E:  ret   

Ответы [ 3 ]

3 голосов
/ 01 февраля 2011

Проще говоря, ключевое слово ref позволяет вызываемому методу манипулировать ссылкой на сайте вызова. Это полная цель ref.

Что происходит, когда Vodka возвращается, sss заменяется новым экземпляром строки . Это не обновляет оригинальный экземпляр. Без модификатора ref это невозможно.

Одна вещь, которую я часто нахожу в замешательстве, когда говорят об этом, это когда вы рассматриваете концепции передачи параметров по значению и по ссылке , когда вы имеете дело с ссылочные типы . Когда вы передаете строку (ссылочный тип) по значению , что на самом деле вы передаете? Вы передаете значение переменной. В этом случае значение является ссылкой на строку. Таким образом, вы передаете это значение, и вызывающий метод может использовать это значение для доступа к строке. Однако, поскольку параметр передается по значению, передается значение как таковое (по существу, копия ссылки), а не указатель на область памяти, в которой хранится значение. Вот почему вызывающий метод не может заменить экземпляр на сайте вызова.

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

Обновление
Давайте разберем пример из вашего комментария (имена были изменены, чтобы защитить невинных):

class SomeClass
{
    public int Value { get; set; }
}

class DoWork
{
    public static void DoOne(SomeClass c) { c.Value = c.Value + 1; }
    public static void DoTwo(ref SomeClass c) { c.Value = c.Value + 2; }
}

SomeClass является ссылочным типом. Это означает, что любой фрагмент кода, которому вы передаете такой экземпляр (и, чтобы было ясно, мы никогда не передаем фактический экземпляр ссылочного типа, мы передаем ссылку в экземпляр), может манипулировать данные в этом случае *. Это означает, что он может вызывать методы, устанавливать значения свойств и т. Д., И любое изменение состояния, к которому это приводит, также видно на сайте вызовов. Ключевое слово ref не влияет на это. Так что в вашем примере ключевое слово ref не имеет значения. Однако учтите это изменение:

class DoWork
{
    public static void DoOne(SomeClass c) 
    { 
        c = new SomeClass(); 
        c.Value = 1; 
    }
    public static void DoTwo(ref SomeClass c) 
    {
        c = new SomeClass(); 
        c.Value = 2; 
    }
}

Теперь давайте назовем это:

var temp = new SomeClass() { Value = 42 };
DoWork.DoOne(temp);
Console.WriteLine(temp.Value); // prints 42

DoWork.DoTwo(ref temp);
Console.WriteLine(temp.Value); // prints 2

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

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

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

Коди Грей опубликовал ссылку на параметр Джона Скита, передающий C # , если вы этого еще не сделали, прочитайте его. Он очень хорошо объясняет эти вещи (и даже больше; он также имеет дело с типами значений).

* (если тип не является неизменяемым, как строка, но это совсем другая история ...)

2 голосов
/ 01 февраля 2011
2 голосов
/ 01 февраля 2011

в Beer указатель на строку передается по значению, поэтому при выполнении s1 = s1 + "beer"; копия указателя изменяется, и она не видна вне метода.
однако в Vodka указатель на строку передается по ссылке, поэтому при выполнении s1 = s1 + "vodka"; изменяется тот же указатель, и он виден вне метода.

...