Упоминание ссылки в параметрах функции C # - PullRequest
1 голос
/ 19 марта 2012

Я сделал функцию и должен был передать в нее массив строк, например string [] aa = null
В определении функции я произвел некоторое обновление в ней, а затем обнаружил, что ее значение не обновлялось, когда функция была выполнена / возвращена. Я не могу понять, какие вещи в C # мы должны упомянуть ключевое слово ref. Разве это не всегда передает параметры по ссылке, зачем упоминать ref? какие объекты необходимо упомянуть с помощью ref для передачи по ссылке?

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

string[] temp = null 
foo(ref temp); 
//function definition
void foo (ref string[] temp) 
{
temp = {"Hello World ","You must be updated now"}
}
foreach(string s in temp)
System.Console.WriteLine(s)

Ответы [ 7 ]

4 голосов
/ 19 марта 2012

Вы немного ошиблись в тонкостях. Если вы передаете ссылочный объект, то он передает ссылку по значению (продолжайте читать, и, надеюсь, это должно стать более понятным). Тем не менее, это не означает, что это мимо. Фактически параметры C # передаются по умолчанию по умолчанию.

Вот хорошая статья, которая очень хорошо объясняет это

И это фрагмент, который объясняет, что я имею в виду, когда говорю, что вы передаете ссылку по значению

В C # параметры (по умолчанию) передаются по значению, что означает, что они неявно копируются при передаче методу. Для значения типа параметры, это означает физическое копирование экземпляра (в том же способ p2 был скопирован), в то время как для ссылочных типов это означает копирование ссылка (аналогично копированию f2)

. Однако в вашем случае вы передаете свой объект, а затем создаете новый объект с новой ссылкой в ​​вашем методе. Таким образом, исходная ссылка остается неизменной, вы можете увидеть это, выполнив обновление вместо совершенно нового создания. Когда вы явно говорите ref, тогда вы передаете ссылку на ссылку, поэтому она работает, потому что вы работаете только с указателем на ссылку, и когда вы создаете новый объект, он будет помещен в это местоположение ссылки.

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

Подведем итог:

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

UPDATE

Вот статья MSDN, объясняющая, что именно вы спрашиваете:)

2 голосов
/ 19 марта 2012

Это потому, что вы на самом деле возвращаете совершенно новый объект, а не изменяете запись в существующем. Если вы назначаете новый объект, вы должны использовать out.

Вот пример, показывающий, как out, ref и обычная передача будут работать с массивом arg. Как видите, массив передается по ссылке независимо от того, указан ref; однако, если вы возвращаете совершенно новый объект, вам нужно указать out:

class Program
{
    static void Main(string[] args)
    {
        string[] val;
        foo(out val);
        Console.WriteLine(string.Join(",", val));
        // Output: 1, 2

        bar(ref val);
        Console.WriteLine(string.Join(",", val));
        // Output: modified, 2

        bar2(val);
        Console.WriteLine(string.Join(",", val));
        // Output: modified again, 2

        Console.Read();
    }

    static void foo(out string[] temp)
    {
        temp = new string[]{"1", "2"};
    }

    static void bar(ref string[] temp)
    {
        temp[0] = "modified";
    }

    static void bar2(string[] temp)
    {
        temp[0] = "modified again";
    }
}
2 голосов
/ 19 марта 2012

C # всегда передает параметры по значению , если вы не используете модификаторы ref или out.

string[] temp = { "zero", "one", "two" };
MutateByVal(temp);
MutateByRef(ref temp);    

void MutateByVal(string[] arr)
{
    // arr and temp are separate references to the same array
    // the value of arr (a reference) is a copy of the value of temp

    // mutate the array referenced by arr
    arr[1] = "mutated!";
    // arr and temp still point at the same array
    // so both arr and temp now contain { "zero", "mutated!", "two" }

    // re-assign arr
    arr = new[] { "blah", "blah", "blah" };
    // arr and temp now point at different arrays
    // arr now contains { "blah", "blah", "blah" }
    // temp still contains { "zero", "mutated!", "two" }
}

void MutateByRef(ref string[] arr)
{
    // arr is an alias for temp
    // that is, they are two different names for the same reference

    // mutate the array referenced by arr
    arr[1] = "mutated!";
    // arr and temp are the same reference
    // so both arr and temp now contain { "zero", "mutated!", "two" }

    // re-assign arr
    arr = new[] { "blah", "blah", "blah" };
    // arr and temp are the same reference
    // so both arr and temp now contain { "blah", "blah", "blah" }
}
1 голос
/ 19 марта 2012

Во-первых, вы псевдокод должен работать.Но прежде чем мы перейдем к этому, здесь есть три вещи: типы значений, ссылочные типы и ключевое слово «ref».

Типы значений - это, как правило, простые базовые типы, такие как int, double и т. Д.Строка странная, так как она считается типом значения.

Более сложные типы, такие как массивы и классы, являются ссылочными типами.

Когда вы передаете типы значений, такие как int и double, вы получаетеПередача копии значения, так что если вы передадите int x = 10 в метод, изменение x в методе не будет отражено после выхода из метода.С другой стороны, если вы передадите MyClass class1, любые изменения свойств в class1 будут отражены за пределами функции.Только не пытайтесь создать новый class1 внутри вашего метода, потому что он не изменится вне вызывающей стороны.

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

Еще одна вещь: это не то, что черно-белое между использованием out и ref.Вы бы использовали, только если дизайн метода всегда создавал ваш класс или массив только внутри метода.Вы бы использовали ref для ссылочного типа, если хотите разрешить возможность создания нового объекта.Например,

    //function definition 
    void foo (ref string[] temp)  
    {
        if(temp == null)
        {
            temp = new string[] { "Hello World ", "You must be updated now" };
        }
        else
        { 
             // do something with the existing temp
        }
    } 

Наконец, если это ваш настоящий код:

        string[] temp = null;
        foo(ref temp);

        foreach (string s in temp)
            System.Console.WriteLine(s);

Позже:

    //function definition 
    void foo (ref string[] temp)  
    {
        temp = new string[] { "Hello World ", "You must be updated now" };
    } 

Тогда это должно действительно сработать.

1 голос
/ 19 марта 2012

Думайте о ссылке на класс как об «идентификаторе объекта». Если у вас есть переменная MyCar типа класса Car, переменная на самом деле не содержит автомобиль. Вместо этого он содержит «идентификатор объекта» автомобиля, который фактически хранится в другом месте. Если MyCar содержит "# 1543", оператор MyCar.Color = CarColors.Purple; фактически не изменит переменную MyCar. Вместо этого он скажет системе "Объект # 1543 должен быть Car. Установите его свойство Color равным CarColors.Purple." In many cases, a routine which passes a variable of type Автомобиль will simply want the called code to do something with the Автомобиль identified by that variable. In a few cases, however, one may be necessary to let the called code change the object ID stored within MyCar itself, so that it points to an entirely different instance of Автомобиль`.

В вашем конкретном случае рассматриваемый объект является массивом. Вызываемая подпрограмма создает целый новый массив, но вызывающая сторона начинает с temp, равным нулю Единственный способ, которым вызывающая сторона увидит новый массив, - это сохранение ссылки на него в temp. В противном случае вызывающая сторона продолжит видеть, какой массив temp имел раньше (или null, если там не было ссылки на массив).

1 голос
/ 19 марта 2012

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

Проблема с вашим кодом заключается в том, что вы создаете новую переменную (с неверным синтаксисом).Чтобы это исправить, вы просто используете индексатор массива напрямую, то есть:

void foo (string[] temp) // create a copy of a reference to the string array
{
    temp[0] = "Boom"; // temp still points to the same object
}
-------------
string[] temp = new [] {"one", "two", "three"}; //outer variable
foo(temp); // behind the scene we have two variables pointing to the same array
foreach (string s in temp)
    System.Console.WriteLine(s);
1 голос
/ 19 марта 2012

Без ref ссылка на массив просто копируется и передается методу (по значению - поскольку значение ссылочного типа является ссылкой на объект); разрешение методу доступа к тому же массиву, но он не позволяет ему изменять собственную ссылку вызывающего.

Для этого и используется ключевое слово ref (или даже out).

Однако - вам не нужно использовать ref, если вы просто хотите, чтобы метод изменил объект , на который ссылается (в зависимости от того, может ли тип быть изменен после построения - т.е. если он неизменен или нет).

Таким образом, этот метод перезапишет первый элемент переданного массива:

public static void foo(string[] ss)
{
  if(ss!=null && ss.Length > 0)
    ss[0] = "Overwritten";
}

public static void main()
{
   var strings = new[] { String.Empty, "Original" };
   foo(strings);
   Console.WriteLine(strings[0]); //will print 'Overwritten'.
}

То, что foo не может сделать в приведенном выше коде, однако, это new параметр ss и ожидается, что он изменит значение ссылки strings, переданной в качестве аргумента из main.

Для этого нам нужно передать ссылку в strings local, куда входит ref. Если мы сделаем это - тогда strings в main() может быть изменено, если передано значение NULL, чтобы указать на новый массив:

public static void foo(ref string[] ss)
{
  if(ss==null || ss.Length == 0)
    ss= new string[1];

  ss[0] = "Overwritten";
}

public static void main()
{
   string[] strings = null
   foo(ref strings);
   Console.WriteLine(strings[0]); //will still print 'Overwritten'.
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...