Назначение строки в C # - PullRequest
20 голосов
/ 04 июня 2011

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

Я чувствую, что мое понимание почти завершено, но я пропускаю один элемент - какую языковую функцию используют строки, чтобы сохранить их неизменяемыми?Чтобы проиллюстрировать это на примере кода:

string valueA = "FirstValue";
string valueB = valueA;
valueA = "AnotherValue";

Assert.AreEqual("FirstValue", valueB); // Passes

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

Я понимаю, что вы можете перегружать, например, операторы == и! =, Но я не могу найти какую-либо документацию по перегрузке= операторы.Какое объяснение?

Ответы [ 3 ]

14 голосов
/ 04 июня 2011

какую языковую функцию используют строки, чтобы сохранить их неизменяемыми?

Это не языковая функция. Это способ определения класса.

Например,

class Integer {
    private readonly int value;

    public int Value { get { return this.value; } }
    public Integer(int value) { this.value = value; } }
    public Integer Add(Integer other) {
        return new Integer(this.value + other.value);
    }
}

похож на int, за исключением того, что это ссылочный тип, но он неизменный. Мы определили так и должно быть. Мы также можем определить его как изменчивый:

class MutableInteger {
    private int value;

    public int Value { get { return this.value; } }
    public MutableInteger(int value) { this.value = value; } }
    public MutableInteger Add(MutableInteger other) {
        this.value = this.value + other.value;
        return this;
    } 
}

См

Я не понимаю, какая языковая функция делает копию valueA, когда я присваиваю ее valueB.

Он не копирует string, он копирует ссылку. string s являются ссылочными типами. Это означает, что переменные типа string s являются местами хранения, значения которых являются ссылками. В этом случае их значения являются ссылками на экземпляры string. Когда вы присваиваете переменную типа string другому типу string, значение копируется. В этом случае значение является ссылкой, и оно копируется назначением. Это верно для любого ссылочного типа, а не только для string или только для неизменяемых ссылочных типов.

Или, возможно, ссылка на valueA не меняется, когда я назначаю ее valueB, только valueA получает новую ссылку на себя, когда я задаю строку.

Нет, значения valueA и valueB относятся к одному и тому же экземпляру string. Их значения являются ссылками, и эти значения равны. Если бы вы могли каким-то образом мутировать * экземпляр string, на который ссылается valueA, референт как valueA, так и valueB увидел бы эту мутацию.

Поскольку это тип экземпляра, я не понимаю, почему это работает.

Нет такой вещи как тип экземпляра.

В основном, string являются ссылочными типами. Но string неизменны. Когда вы изменяете string, получается, что вы получаете ссылку на новую строку, которая является результатом мутации в уже существующем string.

string s = "hello, world!";
string t = s;
string u = s.ToUpper();

Здесь s и t - переменные, значения которых относятся к одному и тому же экземпляру string. Референт s не видоизменяется при вызове String.ToUpper. Вместо этого s.ToUpper создает мутацию референта s и возвращает ссылку на новый экземпляр string, который он создает в процессе применения мутации. Мы присваиваем эту ссылку u.

Я понимаю, что вы можете перегружать, например, операторы == и! =, Но я не могу найти никакой документации по перегрузке операторов =.

Вы не можете перегрузить =.

* Можно с некоторыми хитростями. Игнорировать их.

6 голосов
/ 04 июня 2011

Прежде всего, ваш пример будет работать одинаково с любыми ссылочными переменными, а не только со строками.

Что происходит:

string valueA = "FirstValue"; //ValueA is referenced to "FirstValue"  
string valueB = valueA; //valueB references to what valueA is referenced to which is "FirstValue"  
valueA = "AnotherValue"; //valueA now references a new value: "AnotherValue"
Assert.AreEqual("FirstValue", valueB); // remember that valueB references "FirstValue"

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

string valueA = "FirstValue"; //ValueA is referenced to "FirstValue"  
string valueB = valueA; //valueB references to what valueA is referenced to which is "FirstValue"  
valueA.Replace('F','B'); //valueA will now be: "BirstValue"
Assert.AreEqual("FirstValue", valueB); // remember that valueB references "FirstValue"

Это из-за неизменности String, valueA не изменяет саму строку... Создается новый COPY с изменениями и ссылками, которые.

3 голосов
/ 04 июня 2011

Или, возможно, ссылка на значение A не изменяется, когда я присваиваю его значению B, только значение A получает новую ссылку на себя, когда я задаю строку.

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

Кажется, я не могу найти никакой документации по перегрузке операторов =.

Это не из-за каких-либо недостатков на вашей стороне, это потому, что нет способа перегрузить оператор присваивания в C #.

Оператор = довольно прост, он принимает значение нас правой стороны и присваивает значение переменной с левой стороны.Если это ссылочный тип, значение является ссылкой, так что это то, что назначено.

...