Почему строки ведут себя как ValueType - PullRequest
8 голосов
/ 26 мая 2010

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

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

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";
        }
    }
}

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

приветствуя мнения экспертов по этому вопросу.

Ответы [ 7 ]

23 голосов
/ 26 мая 2010

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

a = new Person (); б = а; b = новый человек (); http://dev.morethannothing.co.uk/valuevsreference/Valuevs.Reference3.png

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

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

a = new Person (); б = а; b.Name =…; http://dev.morethannothing.co.uk/valuevsreference/Valuevs.Reference1.png

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

a = new Person (); б = а; b.Name =…; http://dev.morethannothing.co.uk/valuevsreference/Valuevs.Reference2.png

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

9 голосов
/ 26 мая 2010

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

variableName = someValue;

это изменяет значение переменной - это не , изменяющее содержимое объекта, к которому относится значение переменной.

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

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

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

.

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

7 голосов
/ 26 мая 2010
   a2 = "XYZ";

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

   a2 = CreateStringObjectFromLiteral("XYZ")

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

   IL_0000:  ldstr      "XYZ"

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

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

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

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

7 голосов
/ 26 мая 2010

Они не. Вы изменили указатель 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
0 голосов
/ 26 июля 2011

Существует три семантических типа сущностей:

  1. Изменяемые ссылочные типы
  2. Типы изменяемых значений (*)
  3. Неизменяемые типы

Если кто-то делает копию X изменяемой ссылки Y и затем что-то делает с копией, любая мутация, выполненная над X, повлияет на Y, и наоборот, поскольку X и Y оба ссылаются на один и тот же объект. В отличие от этого, если сделать копию XX экземпляра изменяемого типа значения YY, изменения в XX не повлияют на YY и наоборот.

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

(*) Значения типов значений, которые могут быть частично изменены без полной замены. Точка, например, является изменчивой, потому что можно изменить ее часть, не читая и не переписывая ее целиком. В отличие от этого, Int32 является неизменяемым, поскольку (по крайней мере из «безопасного» кода) невозможно внести какие-либо изменения в Int32 без переписывания всего этого.

0 голосов
/ 27 мая 2010

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

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


Например:

строка x = 123;

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

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

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


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

0 голосов
/ 26 мая 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...