Почему структуры C# неизменяемы?


Мне было просто любопытно узнать, почему структуры, строки и т. д. неизменяемы? В чем причина того, что они неизменны, а остальные объекты изменчивы. Что такое вещи, которые считаются сделать объект неизменным?

есть ли разница в том, как выделяется и освобождается память для изменяемых и неизменяемых объектов?

5 53

5 ответов:

Если эта тема вас интересует, у меня есть ряд статей о неизменяемом программировании в http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/

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

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

строки неизменяемы.

в чем причина того, что они неизменны, а остальные объекты изменчивы.

причины сделать все типы неизменными:

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

  • частный случай первого пункта: неизменяемые объекты гораздо легче сделать потокобезопасными. Большинство проблем безопасности потоков связаны с записью в одном потоке и чтением в другом; неизменяемые объекты не имеют записи.

  • неизменяемые объекты могут быть разобраны и использованы повторно. Например, если у вас есть неизменяемое двоичное дерево, то вы можете использовать его левое и правое поддеревья в качестве поддеревьев a разные дерево без беспокоюсь об этом. В изменяемой структуре вы обычно делаете копии данных для повторного использования, потому что не хотите, чтобы изменения одного логического объекта влияли на другой. Это может спасти много времени и памяти.

причины сделать структуры неизменными

есть много причин, чтобы сделать структуры неизменяемыми. Вот только один.

структуры копируются по значению, а не по ссылке. Легко случайно обработать структуру как копируется по ссылке. Например:

void M()
{
    S s = whatever;
    ... lots of code ...
    s.Mutate();
    ... lots more code ...
    Console.WriteLine(s.Foo);
    ...
}

Теперь вы хотите, чтобы отрефакторить этот код в вспомогательный метод:

void Helper(S s)
{
    ... lots of code ...
    s.Mutate();
    ... lots more code ...
}

неправильно! Это должно быть (ref S s) -- Если вы этого не сделаете, то мутация произойдет на A скопировать С. Если вы не позволяете мутации в первую очередь, тогда все подобные проблемы уходят.

причины сделать строки неизменяемыми

помните мой первый пункт о фактах о неизменяемом структуры остаются фактами?

предположим, что строка изменчива:

public static File OpenFile(string filename)
{
    if (!HasPermission(filename)) throw new SecurityException();
    return InternalOpenFile(filename);
}

Что делать, если враждебный абонент мутирует имя файла после проверка безопасности и до файл? Код просто открыл файл, на который у них может не быть разрешения!

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

каковы вещи, которые считаются сделать объект неизменным?

представляет ли тип логически что-то, что является "вечным" значением? Число 12-это число 12, оно не изменяется. Целые числа должны быть неизменяемыми. Точка (10, 30) - это точка (10, 30); это не так изменение. Пункты должны быть неизменными. Строка " abc "- это строка" abc"; она не изменяется. Строки должны быть неизменяемыми. Список (10, 20, 30) не меняется. И так далее.

иногда тип представляет вещи, которые действительно изменяются. Фамилия Мэри Смит-Смит, но завтра она может быть Мэри Джонс. Или мисс Смит сегодня может быть доктором Смитом завтра. Инопланетянин имеет пятьдесят очков здоровья сейчас, но имеет десять после попадания лазерного луча. Некоторые вещи лучше всего представлены как мутации.

есть ли разница в том, как выделяется и освобождается память для изменяемых и неизменяемых объектов?

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

структуры нет...вот почему изменчивые структуры-это зло.

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

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

понятия изменчивости и неизменности имеют различные значения применительно к структурам и классам. Ключевым аспектом (часто, ключевой слабостью) изменяемых классов является if Foo поле Bar типа List<Integer>, который содержит ссылку на список, содержащий (1,2,3), другой код, который имеет ссылку на тот же список, может изменить его, так что Bar содержит ссылку на список, содержащий (4,5,6), даже если этот другой код не имеет никакого доступа к Bar. Напротив, если Foo поле Biz типа System.Drawing.Point, только так что-нибудь может изменить любой аспект Biz будет чтобы иметь доступ на запись в это поле.

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

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

Кстати, некоторые люди жалуются, что когда свойство класса возвращает удобно изменяемую структуру, изменения в структуре не влияют на класс, из которого он пришел. Я бы сказал, что это хорошо-тот факт, что возвращаемый элемент является структурой, делает поведение ясным (особенно если это структура с открытым полем). Сравните фрагмент кода, используя гипотетическую структуру и свойство на Drawing.Matrix С одним использованием фактического свойства этого класса, реализованного Microsoft:

// Hypothetical struct
public struct {
  public float xx,xy,yx,yy,dx,dy;
} Transform2d;

// Hypothetical property of "System.Drawing.Drawing2d.Matrix"
public Transform2d Transform {get;}

// Actual property of "System.Drawing.Drawing2d.Matrix"
public float[] Elements { get; }

// Code using hypothetical struct
Transform2d myTransform = myMatrix.Transform;
myTransform.dx += 20;
... other code using myTransform

// Code using actual Microsoft property
float[] myArray = myMatrix.Elements;
myArray[4] += 20;
... other code using myArray

глядя на фактическое свойство Microsoft, есть ли способ сказать, является ли запись myArray[4] будет аффект myMatrix? Даже глядя на страницу http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx есть ли способ сказать? Если бы свойство было записано с использованием эквивалента на основе структуры, не было бы путаницы; свойство, которое возвращает структуру, не будет возвращать ни больше, ни меньше, чем текущее значение шести чисел. Изменение myTransform.dx будет не более и не менее, чем запись в переменную с плавающей запятой, которая была не привязана ко всему остальному. Всем, кому не нравится то, что меняется myTransform.dx не влияет myMatrix должно быть одинаково раздражает, что писать myArray[4] не влияет myMatrix либо, за исключением того, что независимость myMatrix и myTransform очевидно, в то время как независимость myMatrix и myArray нет.

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

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

изменчивые структуры порождают ошибки.