С изменяемыми типами разница в поведении между типами значений и ссылками очевидна:
// Mutable value type
PointMutStruct pms1 = new PointMutStruct(1, 2);
PointMutStruct pms2 = pms1;
// pms1 == (1, 2); pms2 == (1, 2);
pms2.X = 3;
MutateState(pms1); // Changes the X property to 4.
// pms1 == (1, 2); pms2 == (3, 2);
// Mutable reference type
PointMutClass pmc1 = new PointMutClass(1, 2);
PointMutClass pmc2 = pmc1;
// pmc1 == (1, 2); pmc2 == (1, 2);
pmc2.X = 3;
MutateState(pmc1); // Changes the X property to 4.
// pmc1 == (4, 2); pmc2 == (4, 2);
Однако с неизменяемыми типами эта разница менее заметна:
// Immutable value type
PointImmStruct pis1 = new PointImmStruct(1, 2);
PointImmStruct pis2 = pis1;
// pis1 == (1, 2); pis2 == (1, 2);
pis2 = new PointImmStruct(3, pis2.Y);
// Can't mutate pis1
// pis1 == (1, 2); pis2 == (3, 2);
// Immutable reference type
PointImmClass pic1 = new PointImmClass(1, 2);
PointImmClass pic2 = pic1;
// pic1 == (1, 2); pic2 == (1, 2);
pic2 = new PointImmClass(3, pic2.Y);
// Can't mutate pic1 either
// pic1 == (1, 2); pic2 == (3, 2);
Неизменяемые ссылочные типы также часто используют семантику значений (например, канонический пример System.String
):
string s1 = GenerateTestString(); // Generate identical non-interned strings
string s2 = GenerateTestString(); // by dynamically creating them
// object.ReferenceEquals(strA, strB)) == false;
// strA.Equals(strB) == true
// strA == strB
Эрик Липперт ранее обсуждал в своем блоге (например, здесь ), что тот факт, что типы значений часто (когда это не имеет значения для этого обсуждения) выделяется в стеке, является деталью реализации, и что обычно это не должно указывать, делаете ли вы объект значением или ссылочным типом.
Учитывая это размытое различие в поведении для неизменяемых типов, какие критерии оставляют нам решать, делать ли неизменяемый тип ссылочным типом или типом значения?
Кроме того, с неизменным акцентом на значениях по сравнению с переменными, должны ли неизменяемые типы всегда реализовывать семантику значений?