Введите "string" в качестве аргумента в функции C#
на 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 ответов
причина, по которой вам нужно ссылаться на параметр string, заключается в том, что, хотя вы передаете ссылку на объект string, назначение чего-то другого параметру просто заменит ссылку, хранящуюся в настоящее время в параметре переменная. Другими словами, вы изменили то, на что ссылается параметр, но исходный объект не изменился.
когда вы ссылаетесь на параметр, вы сказали функции, что параметр на самом деле является псевдонимом для переданной переменной, таким образом, назначение ему будет иметь желаемый эффект.
редактировать: обратите внимание, что хотя string является неизменяемым ссылочным типом, это не слишком актуально здесь. Поскольку вы просто пытаетесь назначить новый объект (в данном случае объект 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!
}
}
теперь тест массива "терпит неудачу", потому что вы назначаете новый объект переменная параметра non-ref.
это помогает сравнить 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
и все еще имеют значение, измененное после того, как мы его вызвали.
и обратите внимание, что мы никогда не делали на самом деле set 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
is неизменяемые, нет никакого способа, без использования ref
или out
, to влияет строку абонентов.
наконец, последний пример делает то, что мы хотим с 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
переменные псевдонимы друг к другу. Как будто они были ... --45-->та же переменная.
строки неизменяемы - вы не изменяете строку, а заменяете объект, на который ссылаются точки, На другой.
сравните это, например, со списком: чтобы добавить элементы, вам не нужно ref. Чтобы заменить весь список другим объектом, вам нужно ref (или out).
это относится ко всем неизменяемым типам. 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
это разные указатель, который первоначально указывает на то же место, что и Main val
, но он указывает где-то еще после создания нового массива (разные указатель на новый массив, а указатель продолжает указывать на то же место).
если я ref
С int
массив, тогда бы он изменился. И размер тоже изменился бы.
путаница заключается в том, что ссылки типа ref передаются по значению по умолчанию, чтобы изменить саму ссылку (на что указывает объект), вы должны передать ссылку по ссылке - используя ref
.
в вашем случае вы обрабатываете строки-назначение строки переменной (или добавление и т. д.) изменяет ссылку, так как строки неизменяемы, нет никакого способа избежать этого, поэтому вы должны использовать ref
.
лучший пример для новичков:
string a = "one";
string b = a;
string b = "two";
Console.WriteLine(a);
... выводит "one"
.
почему? Потому что вы назначаете совершенно новая строка в указатель b
.
Вы правы. Массивы и строки являются ссылочными типами. Но если честно и сравнить подобное поведение стоит написать что-то вроде этого:
static void Function2(int[] val) // It doesn't need 'ref' for a reference type
{
val = new[] { 1, 2, 3, 4 };
}
но в вашем примере вы выполняете операцию записи в некотором элементе одномерного массива C# через reference val
.