Почему изменчивые структуры «злые»? - PullRequest
463 голосов
/ 14 января 2009

После обсуждения здесь SO, я уже несколько раз читал замечание о том, что изменяемые структуры являются «злыми» (как в ответе на этот вопрос ).

Какая на самом деле проблема с изменчивостью и структурами в C #?

Ответы [ 16 ]

7 голосов
/ 02 октября 2013

Когда что-то может быть видоизменено, оно приобретает чувство идентичности.

struct Person {
    public string name; // mutable
    public Point position = new Point(0, 0); // mutable

    public Person(string name, Point position) { ... }
}

Person eric = new Person("Eric Lippert", new Point(4, 2));

Поскольку Person является изменчивым, более естественно думать о изменении позиции Эрика , чем клонировании Эрика, перемещении клона и уничтожении оригинала . Обе операции успешно изменят содержимое eric.position, но одна из них более интуитивна, чем другая. Аналогично, более интуитивно понятно передать Эрику (в качестве ссылки) методы его модификации. Давать метод клону Эрика почти всегда будет удивительно. Любой, кто хочет видоизменить Person, должен не забыть попросить ссылку на Person, иначе он поступит неправильно.

Если вы сделаете тип неизменным, проблема исчезнет; если я не могу изменить eric, мне все равно, получу ли я eric или клон eric. В более общем смысле тип можно безопасно передавать по значению, если все его наблюдаемое состояние хранится в следующих членах:

  • неизменное
  • ссылочные типы
  • безопасно передать по значению

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

Интуитивность неизменного Person зависит от того, что вы пытаетесь сделать. Если Person просто представляет набор данных о человеке, в этом нет ничего не интуитивного; Person переменные действительно представляют абстрактные значения , а не объекты. (В этом случае, вероятно, было бы более уместно переименовать его в PersonData.) Если Person фактически моделирует самого человека, идея постоянного создания и перемещения клонов глупа, даже если вы избежали ловушки думать, что вы модифицируете оригинал. В этом случае, вероятно, было бы более естественным просто сделать Person ссылочным типом (то есть классом).

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

5 голосов
/ 12 мая 2015

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

  1. Предполагается, что структура имеет только открытые члены и не требует инкапсуляции. Если это так, то это действительно должен быть тип / класс. Вам действительно не нужны две конструкции, чтобы сказать одно и то же.

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

Правильная реализация будет выглядеть следующим образом.

struct Mutable {
public int x;
}

class Test {
    private Mutable m = new Mutable();
    public int mutate()
    { 
        m.x = m.x + 1;
        return m.x;
    }
  }
  static void Main(string[] args) {
        Test t = new Test();
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
        System.Console.WriteLine(t.mutate());
    }

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

Результат изменений вуаля ведет себя как ожидалось:

1 2 3 Нажмите любую клавишу для продолжения . , .

5 голосов
/ 07 февраля 2011

Лично, когда я смотрю на код, мне кажется довольно неуклюжим следующее:

data.value.set (data.value.get () + 1);

, а не просто

data.value ++; или data.value = data.value + 1;

Инкапсуляция данных полезна при передаче класса, и вы хотите убедиться, что значение изменено контролируемым образом. Однако если у вас есть общедоступные функции set и get, которые делают чуть больше, чем устанавливают значение того, что когда-либо передается, как это по сравнению с простой передачей структуры открытых данных?

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

Для меня это предотвращает правильное использование структур, используемых для организации открытых переменных, если бы я хотел контроля доступа, я бы использовал класс.

5 голосов
/ 14 января 2009

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

4 голосов
/ 14 января 2009

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

Преимущество в миллион долларов - иногда модульность. Изменяемое состояние может позволить вам скрыть изменяющуюся информацию от кода, который не должен знать об этом.

Искусство интерпретатора подробно описывает эти компромиссы и приводит несколько примеров.

1 голос
/ 19 декабря 2013

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

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

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

...