Объект неизменен, если его состояние
не изменяется, если объект
был создан.
Краткий ответ: Нет, типы значений не являются неизменяемыми по определению. И структуры, и классы могут быть изменяемыми или неизменяемыми. Возможны все четыре комбинации. Если структура или класс имеют общедоступные поля, не предназначенные только для чтения, общедоступные свойства с установщиками или методы, которые устанавливают закрытые поля, это изменчиво, поскольку вы можете изменить его состояние, не создавая новый экземпляр этого типа.
Длинный ответ: Прежде всего, вопрос неизменности применим только к структурам или классам с полями или свойствами. Самые основные типы (числа, строки и нуль) по своей природе неизменны, потому что нечего (поле / свойство) изменять в них. 5 - это 5 - это 5. Любая операция над 5 возвращает только другое неизменное значение.
Вы можете создавать изменяемые структуры, такие как System.Drawing.Point
. И X
, и Y
имеют сеттеры, которые изменяют поля структуры:
Point p = new Point(0, 0);
p.X = 5;
// we modify the struct through property setter X
// still the same Point instance, but its state has changed
// it's property X is now 5
Некоторые люди, похоже, путают неизменность с тем фактом, что типы значений передаются по значению (отсюда и их имя), а не по ссылке.
void Main()
{
Point p1 = new Point(0, 0);
SetX(p1, 5);
Console.WriteLine(p1.ToString());
}
void SetX(Point p2, int value)
{
p2.X = value;
}
В этом случае Console.WriteLine()
пишет "{X=0,Y=0}
". Здесь p1
не был изменен, потому что SetX()
изменил p2
, который является копией из p1
. Это происходит потому, что p1
является типом значения , а не потому, что он неизменен (это не так).
Почему должны типы значений быть неизменяемыми? Множество причин ... См. этот вопрос . Главным образом это происходит потому, что изменяемые типы значений приводят ко всевозможным неочевидным ошибкам. В приведенном выше примере программист мог ожидать, что p1
будет (5, 0)
после вызова SetX()
. Или представьте сортировку по значению, которое позже может измениться. Тогда ваша отсортированная коллекция больше не будет сортироваться, как ожидалось. То же самое касается словарей и хэшей. Сказочный Эрик Липперт ( блог ) написал целый ряд статей об неизменности и почему он считает, что это будущее C #. Вот один из его примеров , который позволяет вам «модифицировать» переменную только для чтения.
ОБНОВЛЕНИЕ: ваш пример с:
this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
- именно то, что Липперт упоминал в своем посте об изменении переменных только для чтения. Offset(3,4)
фактически изменил Point
, но это была копия из readOnlyPoint
, и она никогда не была назначена чему-либо, поэтому она потеряна.
И , что является причиной того, что изменяемые типы значений являются злыми: они позволяют вам думать , что вы модифицируете что-то, когда иногда вы фактически модифицируете копию, что приводит к неожиданным ошибкам. Если бы Point
был неизменным, Offset()
должен был бы вернуть новый Point
, и вы не смогли бы присвоить его readOnlyPoint
. А потом вы идете «О, верно, это только для чтения по причине. Почему я пытался это изменить? Хорошо, компилятор остановил меня сейчас».
ОБНОВЛЕНИЕ: По поводу вашего перефразированного запроса ... Я думаю, я знаю, к чему вы клоните. В некотором смысле, вы можете «думать» о структурах как о внутренне неизменных, что изменение структуры аналогично замене ее измененной копией. Это может быть даже то, что CLR делает внутри памяти, насколько я знаю. (Вот как работает флеш-память. Вы не можете редактировать всего несколько байтов, вам нужно прочитать целый блок килобайт в память, изменить те немногие, которые вы хотите, и записать весь блок обратно.) Однако, даже если они были «внутренне неизменяемыми» ", это деталь реализации, и для нас, разработчиков, как пользователей структур (их интерфейс или API, если хотите), они могут быть изменены. Мы не можем игнорировать этот факт и «думать о них как о неизменных».
В комментарии вы сказали: «У вас не может быть ссылки на значение поля или переменной». Вы предполагаете, что каждая переменная структуры имеет свою копию, так что изменение одной копии не влияет на другие. Это не совсем верно. Помеченные ниже строки не подлежат замене, если ...
interface IFoo { DoStuff(); }
struct Foo : IFoo { /* ... */ }
IFoo otherFoo = new Foo();
IFoo foo = otherFoo;
foo.DoStuff(whatEverArgumentsYouLike); // line #1
foo = foo.DoStuff(whatEverArgumentsYouLike); // line #2
Линии № 1 и № 2 не имеют одинаковых результатов ... Почему? Потому что foo
и otherFoo
относятся к одному и тому же упакованному экземпляру из Foo. Все, что изменилось в foo
в строке # 1, отражено в otherFoo
. Строка # 2 заменяет foo
новым значением и ничего не делает с otherFoo
(при условии, что DoStuff()
возвращает новый экземпляр IFoo
и не изменяет сам foo
).
Foo foo1 = new Foo(); // creates first instance
Foo foo2 = foo1; // create a copy (2nd instance)
IFoo foo3 = foo2; // no copy here! foo2 and foo3 refer to same instance
Изменение foo1
не повлияет на foo2
или foo3
. Изменение foo2
будет отражено в foo3
, но не в foo1
. Изменение foo3
будет отражено в foo2
, но не в foo1
.
Смешение? Придерживайтесь неизменных типов значений, и вы избавляетесь от необходимости изменять любой из них.
ОБНОВЛЕНИЕ: исправлена опечатка в первом примере кода