Введите 'string' в качестве аргумента в функции C # - PullRequest
7 голосов
/ 29 мая 2011

Тип string в C # является ссылочным типом, и передача аргумента ссылочного типа по значению копирует ссылку, поэтому мне не нужно использовать модификатор ref. Тем не менее, мне нужно использовать модификатор ref для изменения ввода string. Почему это?

using System;

class TestIt
{
    static void Function(ref string input)
    {
        input = "modified";
    }

    static void Function2(int[] val) // Don't need ref for reference type
    {
        val[0] = 100;
    }

    static void Main()
    {
        string input = "original";
        Console.WriteLine(input);
        Function(ref input);      // Need ref to modify the input
        Console.WriteLine(input);

        int[] val = new int[10];
        val[0] = 1;
        Function2(val);
        Console.WriteLine(val[0]);
    }
}

Ответы [ 7 ]

12 голосов
/ 29 мая 2011

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

Когда вы ссылаетесь на параметр, вы сообщаете функции, что параметр на самом деле является псевдонимом для передаваемой переменной, поэтому присвоение ему будет иметь желаемый эффект.

РЕДАКТИРОВАТЬ : обратите внимание, что хотя string является неизменяемым ссылочным типом, здесь это не слишком важно. Поскольку вы просто пытаетесь назначить новый объект (в данном случае строковый объект «изменен»), ваш подход не будет работать с любым ссылочным типом. Например, рассмотрим эту небольшую модификацию вашего кода:

using System;

class TestIt 
{
    static void Function(ref string input)
    {
        input = "modified";
    }

    static void Function2(int[] val) // don't need ref for reference type
    {
        val = new int[10];  // Change: create and assign a new array to the parameter variable
        val[0] = 100;
    }
    static void Main()
    {
        string input = "original";
        Console.WriteLine(input);
        Function(ref input);      // need ref to modify the input
        Console.WriteLine(input);

        int[] val = new int[10];
        val[0] = 1;
        Function2(val);
        Console.WriteLine(val[0]); // This line still prints 1, not 100!
    }
}

Теперь, проверка массива "не пройдена", потому что вы назначаете новый объект переменной параметра без ссылки.

6 голосов
/ 29 мая 2011

Это помогает сравнить string с типом, который похож на string, но изменчив.Давайте рассмотрим короткий пример с StringBuilder:

public void Caller1()
{
    var builder = new StringBuilder("input");
    Console.WriteLine("Before: {0}", builder.ToString());
    ChangeBuilder(builder);
    Console.WriteLine("After: {0}", builder.ToString());
}

public void ChangeBuilder(StringBuilder builder)
{
    builder.Clear();
    builder.Append("output");
}

. Это дает:

Before: input
After: output

Итак, мы видим, что для изменяемого типа, то есть типа, который может изменить свое значение,можно передать ссылку на этот тип методу, подобному ChangeBuilder, и не использовать ref или out, и при этом мы можем изменить значение после того, как мы его вызвали.

И заметьте, что ни в коем случаедействительно ли мы установили builder на другое значение в ChangeBuilder.

В отличие от этого, если мы сделаем то же самое со строкой:

public void Caller2()
{
    var s = "input";
    Console.WriteLine("Before: {0}", s);
    TryToChangeString(s);
    Console.WriteLine("After: {0}", s);
}

public void TryToChangeString(string s)
{
    s = "output";
}

Это приведет к:

Before: input
After: input

Почему?Поскольку в TryToChangeString мы фактически не изменяем содержимое строки, на которую ссылается переменная s, мы заменяем s совершенно новой строкой.Кроме того, s является локальной переменной для TryToChangeString и поэтому заменяя значение s внутри , функция не влияет на переменную, переданную в вызов функции.

Поскольку string является неизменным , невозможно, без использования ref или out, повлиять на строку вызывающих абонентов.

Наконецпоследний пример делает то, что нам нужно, с помощью string:

public void Caller3()
{
    var s = "input";
    Console.WriteLine("Before: {0}", s);
    ChangeString(ref s);
    Console.WriteLine("After: {0}", s);
}

public void ChangeString(ref string s)
{
    s = "output";
}

Это приводит к:

Before: input
After: output

Параметр ref фактически делает две s переменные псевдонимы друг для друга.Это как если бы они были той же переменной .

5 голосов
/ 29 мая 2011

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

Сравните это, например, со списком: для добавления элементов не требуется ref.Чтобы заменить весь список другим объектом, вам нужен ref (или out).

3 голосов
/ 29 мая 2011

Это относится ко всем неизменным типам. string оказывается неизменным.

Чтобы изменить неизменяемый тип вне метода, вы должны изменить ссылку. Следовательно, ref или out должны иметь эффект вне метода.

Примечание. Стоит отметить, что в вашем примере вы вызываете конкретный случай, который не соответствует другому примеру: вы фактически указываете на другую ссылку, а не просто меняете существующую ссылку. Как отметил dlev (и сам Скит в моих комментариях), если вы сделали то же самое для всех других типов (например, val = new int[1]), включая изменяемые, то вы "потеряете" свои изменения один раз метод возвращается, потому что они не произошли с одним и тем же объектом в памяти, если только вы не используете ref или out, как вы использовали с string выше.

Надеюсь, уточнить:

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

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

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

В вашем примере вы создали новый экземпляр string (равный "modified"), а затем изменили указатель (input), чтобы он указывал на этот новый экземпляр. Для массива int вы изменили одно из 10 значений, на которые фактически указывает val, что не требует вмешательства в указатель val - оно просто идет туда, куда вы хотите (первый элемент массива ), а затем изменяет это первое значение на месте.

Более похожий пример (украденный из dlev, но это как сделать их действительно сопоставимыми):

static void Function(ref string input)
{
    input = "modified";
}

static void Function2(int[] val)
{
    val = new int[1];
    val[0] = 100;
}

Обе функции изменяют указатель своего параметра. Только потому, что вы использовали ref, input «запоминает» свои изменения, потому что когда он изменяет указатель, он изменяет переданный указатель, а не просто его копию.

val все еще будет массивом 10 int s вне функции, а val[0] будет все равно 1, потому что "val" в Function2 является другим указатель, который первоначально указывает на то же местоположение, что и val в Main, но указывает куда-то еще после создания нового массива (другой указатель указывает на новый массив, а исходный указатель продолжает указывать на то же место).

Если бы я использовал ref с массивом int, то он бы изменился. И он бы тоже изменился в размере.

0 голосов
/ 24 июля 2015

Ты прав. Массивы и строки являются ссылочными типами. Но если быть честным и сравнивать подобное поведение, вы должны написать что-то вроде этого:

static void Function2(int[] val) // It doesn't need 'ref' for a reference type
{
    val = new[] { 1, 2, 3, 4 };
}

Но в вашем примере вы выполняете операцию записи в некоторый элемент одномерного массива C # по ссылке val.

0 голосов
/ 30 мая 2011

Лучший пример для новичков:

string a = "one";
string b = a;
string b = "two";

Console.WriteLine(a);

... выведет "one".

Почему?Потому что вы присваиваете всю новую строку в указатель b.

0 голосов
/ 29 мая 2011

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

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

...