Почему строки ведут себя как ValueType

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

Вот часть кода, который я сделал для тестирования этого поведения.

using System;

namespace RefTypeDelimma
{
    class Program
    {
        static void Main(string[] args)
        {
            string a1, a2;

            a1 = "ABC";
            a2 = a1; //This should assign a1 reference to a2
            a2 = "XYZ";  //I expect this should change the a1 value to "XYZ"

            Console.WriteLine("a1:" + a1 + ", a2:" + a2);//Outputs a1:ABC, a2:XYZ
            //Expected: a1:XYZ, a2:XYZ (as string being a ref type)

            Proc(a2); //Altering values of ref types inside a procedure 
                      //should reflect in the variable thats being passed into

            Console.WriteLine("a1: " + a1 + ", a2: " + a2); //Outputs a1:ABC, a2:XYZ
            //Expected: a1:NEW_VAL, a2:NEW_VAL (as string being a ref type)
        }

        static void Proc(string Val)
        {
            Val = "NEW_VAL";
        }
    }
}

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

радушный эксперт просматривает на этом.

8
задан Sam Holder 26 May 2010 в 09:45
поделиться

6 ответов

   a2 = "XYZ";

Это синтаксический сахар, предоставленный компилятором. Более точным представлением этого утверждения было бы:

   a2 = CreateStringObjectFromLiteral("XYZ")

что объясняет, как a2 просто получает ссылку на новый строковый объект и отвечает на ваш вопрос. Фактический код сильно оптимизирован, потому что он настолько распространен. В IL для этого имеется специальный опкод:

   IL_0000:  ldstr      "XYZ"

Строковые литералы собираются в таблицу внутри сборки. Это позволяет JIT-компилятору очень эффективно реализовать оператор присваивания:

   00000004  mov         esi,dword ptr ds:[02A02088h] 

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

Также обратите внимание, что эта схема легко допускает интернирование строк. Компилятор просто генерирует один и тот же аргумент LDSTR для идентичного литерала.

7
ответ дан 5 December 2019 в 04:38
поделиться

Они не делают этого. Вы изменили указатель a2, а не объект, на который он указывал.
Когда вы используете классы и получаете ожидаемое поведение, вы должны устанавливать свойство объекта, а не его ссылку.

Любой другой класс будет вести себя так же:

Foo a = new Foo(1);
Foo b = a; //a, b point to the same object

b.Value = 4; // change property
Assert.Equals(a.Value, 4); //true - changed for a

b = new Foo(600); // new reference for b
Assert.Equals(a.Value, 4); //true
Assert.Equals(b.Value, 600); //true
7
ответ дан 5 December 2019 в 04:38
поделиться

Вы ничего не меняете в объекте, на который указывает a1, а вместо этого меняете, на какой объект указывает a1.

a = new Person(); b = a; b = new Person();
(источник: morethannothing.co.uk)

В вашем примере "new Person { ... }" заменяется строковым литералом, но принцип тот же.

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

a = new Person(); b = a; b.Name = …;
(источник: morethannothing.co.uk)

Изменение свойства ссылочного типа отражается в оригинале.

a = new Person(); b = a; b.Name = …;

p.s. Извините за размер изображений, они просто взяты из того, что у меня было под рукой. Вы можете посмотреть полный набор на http://dev.morethannothing.co.uk/valuevsreference/, где рассматриваются типы значений, ссылочные типы, передача типов значений по значению и по ссылке, передача ссылочных типов по значению и по ссылке.

23
ответ дан 5 December 2019 в 04:38
поделиться

Всякий раз, когда вы видите

variableName = someValue;

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

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

StringBuilder b1 = new StringBuilder("first");
StringBuilder b2 = b1;
b2 = new StringBuilder("second");

Эта последняя строка ничего не меняет в b1 - она ​​не меняет, на какой объект она ссылается, или содержимое объекта, на который она ссылается. Он просто заставляет b2 ссылаться на новый StringBuilder .

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

9
ответ дан 5 December 2019 в 04:38
поделиться

Первая причина в том, что String является неизменяемым классом.

Объект считается неизменным, если его значение не может быть изменено после того, как он был создан. Например, методы, которые кажутся изменяющими String, на самом деле возвращают новую String, содержащую модификацию. Разработчики постоянно модифицируют строки в своем коде. Это может показаться разработчику изменчивым, но это не так. На самом деле происходит то, что ваша строковая переменная / объект была изменена для ссылки на новое строковое значение, содержащее результаты вашего нового строкового значения. Именно по этой причине .NET имеет класс System.Text.StringBuilder. Если вы считаете необходимым сильно изменить фактическое содержимое строкового объекта, например, в цикле for или foreach, используйте класс System.Text.StringBuilder.


Например:

string x = 123;

если вы выполните x = x + abc, то он назначит новую ячейку памяти для 123 и abc. Затем складывает две строки и помещает вычисленные результаты в новую ячейку памяти и указывает на нее x.

если вы используете System.Text.StringBuilder sb новый System.Text.StringBuilder (123); sb.Append (abc); x sb.ToString ();

построитель строк - это изменяемый класс. Он просто добавляет строку в ту же ячейку памяти. Таким образом, обработка строк выполняется быстрее.


Строка - это объект типа String, значением которого является текст. Внутри текст хранится как доступная только для чтения коллекция объектов Char , каждый из которых представляет один символ Unicode, закодированный в UTF-16.

0
ответ дан 5 December 2019 в 04:38
поделиться

Если я правильно помню, однажды Эрик Липперт написал на SO, что это поведение было выбрано для упрощения и большей безопасности многопоточности. Таким образом, когда вы сохраняете строку в a1, вы знаете, что только вы можете ее изменить. Например, его нельзя изменить из других потоков.

0
ответ дан 5 December 2019 в 04:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: