Неизменяемость структур

Возможный дубликат:
Почему изменчивые структуры - зло?

Я читал во многих местах, в том числе и здесь, что лучше сделать структуры неизменными.

В чем причина этого? Я вижу множество изменяемых структур, созданных Microsoft, например, в xna. Наверное, в BCL их намного больше.

Каковы плюсы и минусы несоблюдения этого правила?


person Joan Venge    schedule 03.03.2009    source источник


Ответы (9)


Структуры должны представлять значения. Ценности не меняются. Число 12 вечно.

Однако учтите:

Foo foo = new Foo(); // a mutable struct
foo.Bar = 27;
Foo foo2 = foo;
foo2.Bar = 55;

Теперь foo.Bar и foo2.Bar разные, что часто бывает неожиданно. Особенно в таких сценариях, как свойства (к счастью, компилятор это обнаруживает). Но также коллекции и т. Д .; как вообще разумно их видоизменить?

С изменяемыми структурами потеря данных слишком проста.

person Marc Gravell    schedule 03.03.2009
comment
Спасибо, Марк. Считаете ли вы, что с точки зрения скорости изменение значения значения в структуре быстрее, чем создание новой структуры? - person Joan Venge; 04.03.2009
comment
Сомневаюсь, что вы когда-нибудь заметите разницу в стандартном приложении. Могут быть некоторые очень специфические случаи, когда вы это заметите, но для общего руководства неизменный выигрывает. - person Marc Gravell; 04.03.2009
comment
Независимо от ответа (которого я не знаю), вы никогда не должны позволять проблемам производительности важнее хорошего дизайна , если не существует узкого места, и профилировщик не сообщает вам, что это источник проблемы. - person Michael Meadows; 04.03.2009
comment
Спасибо, Марк, тогда я должен следовать этому как руководству. - person Joan Venge; 04.03.2009
comment
Спасибо, Майкл, я говорил о скорости потому, что для меня изменение значений в структуре кажется разумным, может быть, из-за других языков, которые я использовал, или из-за того, что это похоже на классы. - person Joan Venge; 04.03.2009
comment
@Henk = это другое. Я не * переназначал foo2; Я его обновил. Например, я не говорю i.Sign =! I.Sign для update i; Я переназначаю его с помощью i = -i; ... переназначение сильно отличается от обновления свойств. - person Marc Gravell; 04.03.2009
comment
Мой более поздний пост (надеюсь, вам понравился) о том, как нарушить правило неизменяемости компилятора C # только для чтения: stackoverflow.com/questions/4720475/ - person Artur Mustafin; 06.10.2011
comment
Я действительно не понимаю, насколько плохи изменяемые структуры, я не ожидаю изменения оригинала, если я изменяю копию, для чего я использую классы. Я согласен с оценкой @Henk Holterman, и я не понимаю, почему вы вообще хотите, чтобы структура была неизменной. - person Jackson Tarisa; 01.09.2018

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

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

РЕДАКТИРОВАТЬ: Я только что нашел электронное письмо, которое я написал некоторое время назад по этой теме. Он немного уточняет:

  • С философской точки зрения это неправильно: структура должна представлять некую фундаментальную ценность. Они в основном неизменны. Вы не можете изменить число 5. Вы можете изменить значение переменной с 5 на 6, но вы не можете логически изменить само значение.

  • Это практически проблема: возникает множество странных ситуаций. Особенно плохо, если его можно изменять через интерфейс. Затем вы можете начать изменять значения в коробках. Ик. Я видел много сообщений групп новостей, которые связаны с тем, что люди пытаются использовать изменяемые структуры и сталкиваются с проблемами. Я видел очень странный пример LINQ, который, например, терпел неудачу, потому что List<T>.Enumerator - это структура.

person Jon Skeet    schedule 03.03.2009
comment
Спасибо, Джон. Считаете ли вы, что с точки зрения скорости изменение значения значения в структуре быстрее, чем создание новой структуры? - person Joan Venge; 04.03.2009
comment
@Joan Venge: Я отвечу на это: Нет. Вернее, есть вызов метода для конструктора, но он, скорее всего, встроен, если только ваш конструктор не слишком тяжелый. - person Randolpho; 04.03.2009
comment
@Randolpho: Спасибо. Но скажем, для такого случая, как .X = 2, vs X = x, Y = y, Z = z в конструкторе, будет ли это действительным? - person Joan Venge; 04.03.2009
comment
@Joan: В очень немногих случаях это может быть значительно быстрее. Я бы не стал использовать изменяемую структуру ради скорости без в значительной степени веской причины. - person Jon Skeet; 04.03.2009
comment
Да, изменение значения значения в структуре происходит быстрее, чем создание новой структуры. Но если вы не чувствительны к производительности, как XNA, разница не будет иметь значения. - person Jonathan Allen; 04.03.2009
comment
Спасибо, Джон. Вы правы, я на самом деле не предпочитаю скорость дизайну, но когда вы видите множество примеров, делающих это, например, фреймворк xna и т. Д., Где находится моя область разработки, я просто предполагаю, что он должен быть лучше, быстрее и т. Д. - person Joan Venge; 04.03.2009
comment
Спасибо Grauenwolf. Я понимаю вашу точку зрения, но я также очень осторожен со скоростью, потому что я пишу очень большое 3D-приложение, которое требует больших вычислений, поэтому стараюсь делать все правильно в дизайне раньше, а не позже. Также я не одобряю плохие практики, но использование изменяемых структур не кажется ... - person Joan Venge; 04.03.2009
comment
или выглядят очень неестественно с точки зрения синтаксиса, но я могу понять некоторые из проблем, о которых вы все упомянули. - person Joan Venge; 04.03.2009
comment
Я вижу, что XNA является исключением (и, как мне кажется, там уже есть изменяемые структуры), но будьте осторожны и не говорите, что мы вас не предупреждали;) - person Jon Skeet; 04.03.2009
comment
Джон, чем структурная переменная отличается от переменной типа int? Я не оспариваю этот совет, но не принимаю «философских» аргументов. - person Henk Holterman; 04.03.2009
comment
@Henk; вы никогда не меняете int; вы создаете новый. Это неизменность. - person Marc Gravell; 04.03.2009
comment
Ты когда нибудь спишь?? XDDD - person Jorge Córdoba; 04.03.2009
comment
Настоящие программисты не спят, они просто время от времени отказываются от оставшейся части временного интервала. ;) - person Guffa; 04.03.2009
comment
А как насчет передачи структур как ref? Это тоже жульничество? Единственная проблема, которую я вижу в этом, заключалась в том, что это было медленнее, чем просто обгон. - person Joan Venge; 04.03.2009
comment
@Joan: Передача структур по ссылке - это нормально, если это имеет смысл. Это не меняет содержимое внутри определенного значения, а просто меняет значение переменной на новое значение. - person Jon Skeet; 04.03.2009

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

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

person MattDavey    schedule 16.02.2011
comment
Имейте в виду, что большинство рекомендаций взято из Руководства по дизайну фреймворка. Это означает, что их целевая аудитория - это люди, пишущие API для использования другими людьми. В этом сценарии возможность записать нерабочий myClass.SomeValue.AProperty = 5 просто означает отказ. - person Jonathan Allen; 19.02.2011
comment
@JonathanAllen: Средства для этого: (1) Разрешить пометить свойства и методы с помощью атрибута, указывающего, должны ли они использоваться в структурах в контекстах только для чтения; (2) Структуры должны предоставлять поля, а не самоизменяющиеся свойства. Я не могу придумать никаких структурных структур, которые раскрывают самоизменяющиеся свойства, которые не могли бы быть лучше реализованы с использованием открытых полей. Обратите внимание, что компилятору не нужно было бы угадывать, попытается ли myClass.SomeValue.AField = 5 изменить myClass.SomeValue. Он мог сказать, что будет. - person supercat; 29.09.2012
comment
@JonathanAllen: Меня действительно раздражает, что нет способа переопределить предположения компилятора о том, что установщик свойств изменит структуру (и должен быть запрещен в контекстах только для чтения) или что другие методы не будут (и, следовательно, должны быть разрешены) - тем более, что в некоторых случаях было бы полезно, чтобы структура обернула неизменяемую ссылку на изменяемый класс и установщики свойств действовали бы на элемент класса, но в конечном итоге приходится создавать ненужное давление сборщика мусора, используя класс вместо этого, чтобы позволить аксессорам свойств работать. - person supercat; 29.09.2012

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

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

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

person Jonathan Allen    schedule 03.03.2009
comment
IMHO, тот факт, что свойства не работают со структурами, является ошибкой того, как свойства работают, и отсутствием каких-либо стандартных средств возврата ссылки на элемент массива значений типа. Если бы в системе был стандартный тип ArrayIndexer (Of T), который содержал ссылку на массив и индекс к нему, свойства типа значения могли бы работать намного лучше. - person supercat; 19.02.2011
comment
Не сработает, вы все равно рискуете, что вся структура будет скопирована в стек компилятором или оптимизатором JIT. - person Jonathan Allen; 19.02.2011
comment
Чтобы идея была действительно полезной, компилятор и JITter должны знать и соблюдать семантику типа ArrayIndexer (Of T), который должен обеспечивать средства доступа к соответствующему элементу массива без раскрытия самого массива. Возможно, это можно сделать, используя метод AccessElement ‹PT1, PT2› (int index, ref PT1 p1, ref PT2 p2, delegate (ref T item, ref PT1 p1, ref PT2 p2)), который будет принимать функцию обратного вызова, для которой должен быть передан конкретный элемент массива. Код был бы уродливым без поддержки компилятора, но с поддержкой все должно быть хорошо. - person supercat; 29.10.2011

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

public void DoSomething(MySomething something)
{
    something.Property = 10;
}

Ведет себя по-разному в зависимости от того, является ли MySomething struct или class. Для меня это веская, но не самая веская причина. Если вы посмотрите на DDD Value Object, вы можете увидеть связь с тем, как следует обрабатывать структуры. Объект значения в DDD может быть лучше всего представлен как тип значения в .Net (и, следовательно, структура). Поскольку у него нет идентичности, он не может измениться.

Подумайте об этом как о своем адресе. Вы можете «изменить» свой адрес, но сам адрес не изменился. Фактически, вам назначен новый адрес. Концептуально это работает, потому что, если вы действительно измените свой адрес, вашим соседям по комнате также придется переехать.

person Michael Meadows    schedule 03.03.2009

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

Минусы: Минусы подробно описаны в существующих ответах, и большинство описанных проблем вызвано одной и той же причиной - неожиданное поведение из-за семантики значений структур.

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

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

person Ergwun    schedule 24.01.2011

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

Семантика упрощается при использовании неизменяемых структур, и вы избегаете таких ловушек:

// a struct
struct Interval {
   int From { get; set; }
   int To { get; set; }
}

// create a list of structs
List<Interval> intervals = new List<Interval>();

// add a struct to the list
intervals.Add(new Interval());

// try to set the values of the struct
intervals[0].From = 10;
intervals[0].To = 20;

В результате структура в списке вообще не изменяется. Выражение Interval [0] копирует значение структуры из списка, затем вы изменяете свойство временного значения, но значение никогда не возвращается в список.

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

person Guffa    schedule 03.03.2009
comment
Это меня много раз убивало при работе с устаревшим кодом! Реализация структур как неизменяемых объектов значений предотвращает именно эту ситуацию. - person Michael Meadows; 04.03.2009
comment
Спасибо, Гуффа, а ты уверен? Массив AFAIK - единственная коллекция, которая позволяет это. - person Joan Venge; 04.03.2009
comment
Да, вы правы, я это тестировал, и он действительно работает с массивом. Я отредактировал пример, чтобы использовать список. - person Guffa; 04.03.2009
comment
Спасибо, Гуффа, все хорошо :) - person Joan Venge; 04.03.2009
comment
Interval - это структура, поэтому вы не можете использовать для этого индексатор. Я полагаю, вы имеете в виду intervals[0], а не Interval[0]. Даже в этом случае компилятор не разрешает присваивание, поскольку это не переменная. Сначала вам нужно назначить локальную переменную, например var i = intervals[0];. Изменение этого элемента не приведет к изменению элемента в списке. - person Brian Rasmussen; 08.03.2010
comment
@Brian: Да, Interval [0] был опечаткой, спасибо, что заметили. Более поздние версии компилятора могут провести дополнительную проверку и заметить, что изменение свойств на самом деле ничего не меняет, а более ранние версии - нет. Даже если компиляторы научатся замечать такие проблемы, всегда будут ситуации, о которых они не смогут вас предупредить. - person Guffa; 08.03.2010

Когда вы копируете структуры вокруг, вы копируете их содержимое, поэтому, если вы измените скопированную версию, «оригинал» не будет обновлен.

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

Только что случилось со мной снова на прошлой неделе, и у меня ушел час на поиск ошибки.

Сохранение структур неизменными предотвращает это ...

Помимо этого, вам нужно убедиться, что у вас есть действительно веская причина для использования структур в первую очередь - «оптимизация» или «мне нужно что-то, что быстро выделяется в стеке» не считается ответом. Маршалинг или вещи, в которых вы зависите от макета - хорошо, но обычно вам не следует хранить эти структуры очень долго, они являются значениями, а не объектами.

person froh42    schedule 03.03.2009
comment
Спасибо, я больше всего полагаюсь на структуры из-за того, что такие фреймворки, как xna и т. Д., Используют их практически для всего, поэтому даже если мне нужно написать свою собственную, я просто использую структуры. - person Joan Venge; 04.03.2009

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

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

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

person Randolpho    schedule 03.03.2009