Почему изменяемые структуры «зло»?

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

В чем проблема изменчивости и структур в C #?


person Dirk Vollmar    schedule 13.01.2009    source источник
comment
Заявление о том, что изменяемые структуры являются злом, похоже на утверждение о том, что изменяемые int, bool и все другие типы значений являются злом. Есть случаи изменчивости и неизменности. Эти случаи зависят от роли, которую играют данные, а не от типа распределения / совместного использования памяти.   -  person Slipp D. Thompson    schedule 21.07.2014
comment
@slipp int и bool неизменяемы ..   -  person Blorgbeard    schedule 05.03.2015
comment
.-синтаксис, благодаря которому операции с данными с типизированными ссылками и данными с типизированными значениями выглядят одинаково, даже если они существенно отличаются. Это ошибка свойств C #, а не структур - некоторые языки предлагают альтернативный a[V][X] = 3.14 синтаксис для изменения на месте. В C # лучше предложить методы мутатора-члена структуры, такие как MutateV (Action ‹ref Vector2› mutator), и использовать его как a.MutateV((v) => { v.X = 3; }) (пример чрезмерно упрощен из-за ограничений, которые C # имеет в отношении ref ключевое слово, но должны быть возможны некоторые обходные пути).   -  person Slipp D. Thompson    schedule 05.05.2015
comment
@ Sushi271 обходной путь (n): метод преодоления проблемы или ограничения в программе или системе. Это ограничение синтаксиса C #. Идеальное использование ключевого слова ref в универсальном типе не является двусмысленным; это просто не поддерживается. Это ограничение. И да, C # во многих отношениях заставляет неудобный синтаксис выполнять свою работу. Самый классический пример неуклюжести C # - это public снова и снова и снова для каждого члена, а не префикс в стиле C ++ public: или отдельный public FieldName1, FieldName2, MethodName1, MethodName2; в стиле Ruby.   -  person Slipp D. Thompson    schedule 05.05.2015
comment
@ Sushi271 Структуры с большим количеством элементов не распространены из-за неэффективности копирования большого количества элементов данных. Сценарии использования те же, но хорошие программисты будут (и исторически) выберут другой подход ради эффективности. Итак, да, легкий вес обычно лучше подходит для любых данных с типизированными значениями.   -  person Slipp D. Thompson    schedule 05.05.2015
comment
@Slipp Что ж, я думаю об этих структурах с точностью до наоборот. Как вы думаете, почему структуры, которые уже реализованы в библиотеке .NET, такие как DateTime или TimeSpan (такие похожие), неизменяемы? Возможно, было бы полезно изменить только один член такой структуры var, но это слишком неудобно, приводит к слишком большому количеству проблем. На самом деле вы ошибаетесь насчет того, что вычисляет процессор, поскольку C # не компилируется в ассемблер, он компилируется в IL. В IL (при условии, что у нас уже есть переменная с именем x) эта единственная операция состоит из 4 инструкций: ldloc.0 (загружает 0-индексную переменную в ...   -  person Sushi271    schedule 06.05.2015
comment
... тип. T - это тип. Ref - это просто ключевое слово, которое заставляет переменную передаваться самому методу, а не его копию. Это также имеет смысл для ссылочных типов, поскольку мы можем изменить переменную, т.е. ссылка вне метода будет указывать на другой объект после изменения в методе. Поскольку ref T не тип, а способ передачи параметра метода, вы не можете поместить его в <>, потому что там могут быть помещены только типы. Так что это просто неверно. Может быть, это было бы удобно, возможно, команда C # могла бы сделать это для какой-то новой версии, но сейчас они работают над некоторыми ...   -  person Sushi271    schedule 06.05.2015
comment
... гораздо более важные вещи, такие как условный оператор null или инициализаторы авто-свойств. Что касается _1 _... мне действительно нравится способ сделать это в C #. На самом деле рефакторинг проще, потому что удаление / добавление одного паблика в одном месте для одного участника не влияет на всех остальных участников. В C ++ мне часто приходится добавлять две метки (например, public: и private: снова) или перемещать метод в другую часть класса. Неудобно. Во всяком случае, упоминание этой темы заставило меня понять, что мы должны прекратить это обсуждение. Мы начинаем спорить о собственном мнении, и можем годами сражаться и не найти общего ..   -  person Sushi271    schedule 06.05.2015
comment
земля. Каждый имеет право на свое мнение. Вы, сэр, кажется, более привязаны к C ++, тогда как моим основным языком в последние 8 лет был C #. Конечно, познавать чью-то точку зрения по некоторым вопросам, однако сейчас мы далеко отклонились от основной темы. Так что спасибо за ваш вклад.   -  person Sushi271    schedule 06.05.2015
comment
Вот и мы в 2017 году, и изменяемые структуры вернулись! : D Как и всегда, все дело в знании того, как и когда их использовать. Тем не менее, я бы, вероятно, посоветовал любому новичку держаться подальше от структур или хотя бы сделать их изменяемыми - а затем возвращаться к ним позже, когда у вас будет больше опыта, они предлагают особые преимущества. (Понимание того, что ссылка - это особый тип значения адреса со специальными перегрузками операторов, решает все, но новичкам это слишком сложно пережевывать.)   -  person AnorZaken    schedule 31.08.2017
comment
Я согласен с тем, что изменяемые структуры @AnorZaken являются злом только в том случае, если вы не знаете разницы между типами значений и ссылочными типами, в противном случае можно использовать структуры, даже если они являются изменяемыми, если вы знаете, что делаете. (кстати, сам .NET имеет много изменяемых структур)   -  person R1PFake    schedule 26.03.2018


Ответы (16)


Структуры - это типы значений, что означает, что они копируются при передаче.

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

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

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

person trampster    schedule 13.01.2009
comment
Если ваша структура неизменна, все копии будут одинаковыми. Нет, это означает, что вы должны сознательно делать копию, если хотите другое значение. Это означает, что вас не поймают на изменении копии, думая, что вы изменяете оригинал. - person Lucas; 15.05.2009
comment
@Lucas Я думаю, вы говорите о другом виде копии. Я говорю об автоматических копиях, сделанных в результате передачи по значению. Ваша «сознательно созданная копия» отличается от того, что вы сделали ее не по ошибке, а ее На самом деле это не копия, а преднамеренные новые моменты, содержащие разные данные. - person trampster; 27.05.2010
comment
Ваша редакция (16 месяцев спустя) проясняет это. Я все еще поддерживаю (неизменяемая структура) означает, что вас не поймают на изменении копии, думая, что вы изменяете оригинал. - person Lucas; 27.05.2010
comment
@Lucas: опасность создания копии структуры, ее изменения и того, что кто-то думает, что изменяет оригинал (когда тот факт, что кто-то пишет поле структуры, делает очевидным тот факт, что один только написание своей копии) кажется довольно незначительным по сравнению с опасностью того, что кто-то, кто держит объект класса как средство хранения информации, содержащейся в нем, изменит объект, чтобы обновить свою собственную информацию, и в процессе повредит информацию, содержащуюся в каком-либо другом объекте . - person supercat; 28.10.2011
comment
Итак, чтобы избежать зла, нужно ли создавать обертки классов для каждой структуры (прямоугольник, размер, точка и т. Д.), С которыми мы сталкиваемся? Разве это не громоздко? - person Blip; 29.12.2013
comment
@VictorPrograss: сохраните структуру в одноэлементном массиве, и она будет работать как класс. В качестве альтернативы, если кто-то определяет класс public ExposedFieldHolder<T> { public T Value; public ExposedFieldHolder(T v) { Value = v; } }, то можно использовать его для превращения любой структуры в сущность (сделать тип общедоступным безвредно, поскольку вся цель типа иметь в точности указанную семантику, и ее раскрытие не приведет к открытию каких-либо экземпляров для кода, который не должен их видеть). - person supercat; 12.03.2014
comment
Для меня самым большим преимуществом неизменяемых структур данных является то, что, поскольку они не могут быть изменены, вы можете легко выполнять многопоточную распределенную работу с фрагментом данных без необходимости вручную проверять и обрабатывать проблемы синхронизма (межпоточный доступ к этим данным ). По сути, это делает код более чистым, надежным и свободным от ошибок, чем если бы вы использовали изменяемую структуру данных (вы, по сути, вынуждаете, по сути, что любые потоки, смотрящие на него, не могут его изменять). По-прежнему существует проблема последовательности. Если 2 потока ищут данные в одном и том же месте, и с тех пор указатель на эту копию изменился. - person Joao Carlos; 28.12.2014
comment
Я не думаю, что существует какое-либо соглашение о том, что структуры должны быть неизменными. Например, вы можете найти множество изменяемых структур в System.Drawing (Point, Rectangle, ...). - person Dave_cz; 04.04.2016
comment
Третий абзац в лучшем случае звучит неправильно или неясно. Если ваша структура неизменна, вы просто не сможете изменять ее поля или поля любых сделанных копий. Если вы хотите изменить это, вы должны ... это тоже вводит в заблуждение, вы не можете изменить это когда-либо, ни сознательно, ни бессознательно. Создание нового экземпляра, данные которого вам нужны, не имеет ничего общего с исходной копией, кроме той же структуры данных. - person Saeb Amini; 22.05.2016

С чего начать ;-p

Блог Эрика Липперта всегда хорош для цитаты :

Это еще одна причина, почему изменяемые типы значений злы. Старайтесь всегда делать типы значений неизменяемыми.

Во-первых, вы очень легко теряете изменения ... например, выбирая что-то из списка:

Foo foo = list[0];
foo.Name = "abc";

что это изменило? Ничего полезного ...

То же и со свойствами:

myObj.SomeProperty.Size = 22; // the compiler spots this one

заставляя вас делать:

Bar bar = myObj.SomeProperty;
bar.Size = 22;
myObj.SomeProperty = bar;

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

person Marc Gravell    schedule 13.01.2009
comment
Ну да, но компилятор в этом смысле глуп. Запретить присвоение элементам структуры свойств было ИМХО глупым дизайнерским решением, потому что это разрешено для оператора ++. В этом случае компилятор просто сам пишет явное присвоение вместо того, чтобы толкать программиста. - person Konrad Rudolph; 14.01.2009
comment
@Konrad: myObj.SomeProperty.Size = 22 изменит КОПИЮ myObj.SomeProperty. Компилятор избавляет вас от очевидной ошибки. И это НЕ разрешено для ++. - person Lucas; 15.05.2009
comment
@Lucas: Что ж, компилятор Mono C # определенно позволяет это - поскольку у меня нет Windows, я не могу проверить компилятор Microsoft. - person Konrad Rudolph; 20.09.2010
comment
@Konrad - компилятор MS отказывает: `class Test {public MyStruct Value {get; установленный; } публичная структура MyStruct {публичная int Bar {получить; установленный; }} public static void Main () {Test test = new Test (); test.Value.Bar ++; }} `- это работает с моно? - person Marc Gravell; 20.09.2010
comment
@Marc: На самом деле я говорил об одном косвенном обращении меньше: test.Bar++ или test.Bar += 1. Это также изменяет значение - фактически, оно более или менее эквивалентно var tmp = test.Bar; tmp += 1; test.Bar = tmp;. Так что ситуация не полностью идентична, но я никогда не об этом говорил. Я хотел сказать, что компилятор может и действительно переписывает код там, где это имеет смысл. И полученная перезапись здесь почти полностью идентична ручной перезаписи, которую вы предложили в своем ответе. Единственное, что отличается - bar.Size = 22; и bar = bar + 1. - person Konrad Rudolph; 20.09.2010
comment
@Marc: Ага. Я беру это назад - здесь есть существенная разница, мой код всегда создает новый объект, он также работает с неизменяемыми объектами. - person Konrad Rudolph; 20.09.2010
comment
@Konrad - с одним косвенным обращением должно работать; это изменение значения чего-то, что существует только как временное значение в стеке и которое вот-вот испарится в ничто, что и является случаем, которое заблокировано. - person Marc Gravell; 20.09.2010
comment
@Marc Gravell: В предыдущем фрагменте кода вы получаете Foo с именем abc и другими атрибутами, аналогичными List [0], не нарушая List [0]. Если бы Foo был классом, его необходимо было бы клонировать, а затем изменить копию. На мой взгляд, большая проблема, связанная с различием типа значения и класса, заключается в использовании расширения. оператор для двух целей. Если бы у меня были мои ученики, классы могли бы поддерживать и то, и другое. и - ›для методов и свойств, но нормальная семантика для. properties будет создавать новый экземпляр с измененным соответствующим полем. - person supercat; 10.10.2010
comment
Почему myObj.SomeProperty.Size = следует изменять копию myObj.SomeProperty, но bar.Size = изменять bar, а не копию bar? Вот где кажется плохое решение ... - person NetMage; 09.11.2017
comment
Я пробовал myObj.SomeProperty.Size = 22; и он компилируется и работает нормально. - person David Klempfner; 03.04.2018
comment
@Backwards_Dave, тогда вы можете сравнивать другой сценарий; либо SomeProperty на самом деле не является свойством (возможно, это поле?), либо тип SomeProperty на самом деле не является struct. Вот минимальная репродукция, что показывает CS1612: sharplab.io/ - person Marc Gravell; 03.04.2018
comment
@MarcGravell, ты прав. Спасибо, что прояснили это. Как только вы поймете, что свойство - это просто метод, который возвращает значение, тогда станет очевидно, что возвращаемое значение является копией типа значения! - person David Klempfner; 04.04.2018
comment
..если у вас есть структура с двумя int, строкой, DateTime и bool, вы можете очень быстро прожигать много памяти, чтобы это звучало как заданный негатив. Однако, учитывая дополнительные накладные расходы на объекты кучи и ссылки на них, такая не прожигающая память применяется только в том случае, если на «один и тот же объект» «ссылаются» несколько / много раз и правильно кэшируются / повторно используются. - person user2864740; 21.01.2019
comment
Может быть, что-то было потеряно после правок, но включение цитаты Эрика добавляет нулевой ценности при ответе на вопрос ОП. Я думаю, нам следует просто удалить цитату и сказать, что блог Эрика Липперта подробно освещает это. С другой стороны, вы очень умный парень, а я нет, так что я, возможно, что-то упускаю. - person Ash; 22.08.2020
comment
@Ash цитата была добавлена ​​примерно через 5 минут после исходного ответа, поэтому в общей схеме более 11 лет мы, вероятно, должны рассматривать ее как часть самого исходного ответа. В конечном счете, цитата была просто лаконичным заголовком, чтобы создать сцену для блога Эрика, и хотя блог Эрика широко освещает это, мы также должны были бы дать некоторое пространство в жизни для небольшого артистического расцвета время от времени :) - person Marc Gravell; 22.08.2020

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

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

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

person Konrad Rudolph    schedule 13.01.2009
comment
Эрик Липперт говорит, что они ... см. Мой ответ. - person Marc Gravell; 14.01.2009
comment
Неизменяемость, безусловно, хороша для многопоточности, но вы можете написать неизменяемый класс так же легко и с такой же пользой. Но все же хороший ответ. Я бы +1, но сегодня я ухожу. - person Marc Gravell; 14.01.2009
comment
Как бы я ни уважал Эрика Липперта, он не Бог (по крайней мере, пока). Сообщение в блоге, на которое вы ссылаетесь, и ваше сообщение выше, само собой разумеется, являются разумными аргументами в пользу того, чтобы сделать структуры неизменяемыми, но на самом деле они очень слабые аргументы в пользу никогда использования изменяемых структур. Этот пост, однако, имеет +1. - person Stephen Martin; 14.01.2009
comment
При разработке на C # вам обычно время от времени требуется изменчивость - особенно с вашей бизнес-моделью, где вы хотите, чтобы потоковая передача и т. Д. Бесперебойно работала с существующими решениями. Я написал статью о том, как работать с изменяемыми И неизменяемыми данными, решив большинство проблем, связанных с изменчивостью (надеюсь): rickyhelgesson.wordpress.com/2012/07/17/ - person Ricky Helgesson; 31.07.2012
comment
@RickyHelgesson: Я думаю, что одна вещь была бы полезной как часть дизайна, включающего изменяемые и неизменяемые бизнес-объекты, - это общий читаемый интерфейс объекта. Методы, которые не изменяют и не сохраняют свой аргумент, должны иметь возможность работать взаимозаменяемо с изменяемыми или неизменяемыми объектами. - person supercat; 05.06.2013
comment
@StephenMartin: структуры, которые инкапсулируют одно значение, часто должны быть неизменяемыми, но структуры, безусловно, являются лучшей средой для инкапсуляции фиксированных наборов независимых, но связанных переменных (таких как координаты X и Y точки), которые не имеют идентичности как группа. Структуры, которые используются для этой цели, обычно должны предоставлять свои переменные как общедоступные поля. Я считаю, что идея о том, что для таких целей более уместно использовать класс, чем структуру, просто неверна. Неизменяемые классы часто менее эффективны, а изменяемые классы часто имеют ужасную семантику. - person supercat; 05.06.2013
comment
@StephenMartin: Рассмотрим, например, метод или свойство, которое должно возвращать шесть float компонентов графического преобразования. Если такой метод возвращает структуру открытого поля с шестью компонентами, очевидно, что изменение полей структуры не приведет к изменению графического объекта, от которого она была получена. Если такой метод возвращает изменяемый объект класса, возможно, изменение его свойств изменит базовый графический объект, а, возможно, и нет - на самом деле никто не знает. - person supercat; 05.06.2013
comment
@supercat: Я не понимаю, какую проблему это решит. Важность неизменяемого доступа к объекту заключается в том, что вы знаете, что он будет согласован между чтениями, и все свойства объекта, очевидно, всегда согласованы друг с другом. Читаемый интерфейс позволит вам читать данные из объекта, который может быть нестабильным и, возможно, даже опасным для чтения, если у вас есть свойства, которые не могут быть обновлены за одну операцию, что делает интерфейс опасным. Итак, если бы я добавил это, это должна была бы быть очень важная проблема, которую нужно было бы решить, чтобы представить эти опасности. - person Ricky Helgesson; 17.06.2013
comment
@RickyHelgesson: код, которому требуется неизменяемый объект, должен требовать изменяемый объект. Читаемый интерфейс предназначен для кода, который хочет прочитать текущее состояние объекта, который может быть изменяемым. Среди прочего, если бы конструктор изменяемого типа был бы готов принять изменяемый объект, во многих случаях не было бы причин, по которым он также не был бы полностью счастлив, если бы ему был предоставлен объект, который содержал требуемую информацию, но был неизменным. Определение читаемого интерфейса позволит избежать необходимости перегрузки конструктора изменяемого объекта для изменяемых или неизменяемых исходных операндов. - person supercat; 17.06.2013
comment
Почему не изменяемый? У меня есть свойство Rectangle, которое может быть моим размером экрана, и я хотел бы изменить его вне класса. - person Ahmad; 13.02.2015
comment
@Ahmad Я привел причины этого в своем сообщении, и в том, что связано. - person Konrad Rudolph; 13.02.2015

Изменяемые структуры не являются злом.

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

Я бы не назвал использование неизменяемой структуры в этих совершенно правильных сценариях использования «злом».

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

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

Например: "структуры - это типы значений, которые копируются по умолчанию. Вам нужна ссылка, если вы не хотите их копировать" или "попробуйте сначала поработать со структурами только для чтения" < / em>.

person JE42    schedule 04.06.2013
comment
Я бы также сказал, что если кто-то хочет скрепить фиксированный набор переменных вместе изолентой, чтобы их значения могли обрабатываться или сохраняться либо отдельно, либо как единое целое, имеет смысл попросить компилятор закрепить фиксированный набор переменных. переменные вместе (т.е. объявлять struct с общедоступными полями), чем определять класс, который можно неуклюже использовать для достижения тех же целей, или добавлять кучу мусора в структуру, чтобы заставить ее эмулировать такой класс (вместо того, чтобы иметь он ведет себя как набор переменных, склеенных изолентой, чего действительно хочется в первую очередь) - person supercat; 04.06.2013

Структуры с общедоступными изменяемыми полями или свойствами не являются злом.

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

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

struct PointyStruct {public int x,y,z;};
class PointyClass {public int x,y,z;};

void Method1(PointyStruct foo);
void Method2(ref PointyStruct foo);
void Method3(PointyClass foo);

Для каждого метода ответьте на следующие вопросы:

  1. Assuming the method doesn't use any "unsafe" code, might it modify foo?
  2. If no outside references to 'foo' exist before the method is called, could an outside reference exist after?

Ответы:

Вопрос 1:
Method1(): нет (ясное намерение)
Method2(): да (ясное намерение)
Method3(): да (неопределенное намерение)
Вопрос 2:
Method1(): нет
Method2(): нет (если только небезопасно)
Method3(): да

Method1 не может изменять foo и никогда не получает ссылку. Method2 получает кратковременную ссылку на foo, которую он может использовать для изменения полей foo любое количество раз и в любом порядке, пока он не вернется, но он не может сохранить эту ссылку. Перед возвратом Method2, если он не использует небезопасный код, все копии, которые могли быть сделаны из его ссылки 'foo', исчезнут. Method3, в отличие от Method2, получает ссылку на foo с возможностью беспорядочного обмена, и неизвестно, что он может с ней сделать. Он может вообще не изменить foo, он может изменить foo, а затем вернуться, или он может дать ссылку на foo на другой поток, который может изменить его произвольным образом в какое-то произвольное время в будущем. Единственный способ ограничить то, что Method3 может делать с переданным в него изменяемым объектом класса, - это инкапсулировать изменяемый объект в оболочку только для чтения, что некрасиво и громоздко.

Массивы структур предлагают прекрасную семантику. Учитывая RectArray [500] типа Rectangle, ясно и очевидно, как, например, скопируйте элемент 123 в элемент 456, а затем через некоторое время установите ширину элемента 123 равной 555, не нарушая элемент 456. «RectArray [432] = RectArray [321]; ...; RectArray [123] .Width = 555;» . Знание того, что Rectangle - это структура с целочисленным полем с именем Width, расскажет все, что нужно знать о приведенных выше операторах.

Теперь предположим, что RectClass был классом с теми же полями, что и Rectangle, и нужно проделать те же операции с RectClassArray [500] типа RectClass. Возможно, массив должен содержать 500 предварительно инициализированных неизменяемых ссылок на изменяемые объекты RectClass. в этом случае правильный код будет выглядеть примерно так: «RectClassArray [321] .SetBounds (RectClassArray [456]); ...; RectClassArray [321] .X = 555;». Возможно, предполагается, что массив содержит экземпляры, которые не будут меняться, поэтому правильный код будет больше похож на «RectClassArray [321] = RectClassArray [456]; ...; RectClassArray [321] = New RectClass (RectClassArray [321] ]); RectClassArray [321] .X = 555; " Чтобы знать, что нужно делать, нужно знать намного больше как о RectClass (например, поддерживает ли он конструктор копирования, метод копирования из и т. Д.), Так и о предполагаемом использовании массива. И даже близко не так чисто, как использование структуры.

Безусловно, к сожалению, нет хорошего способа для любого класса контейнера, кроме массива, предложить чистую семантику массива структур. Лучшее, что можно было сделать, если бы кто-то хотел, чтобы коллекция индексировалась, например, строка, вероятно, будет предлагать универсальный метод «ActOnItem», который будет принимать строку для индекса, универсальный параметр и делегат, который будет передаваться по ссылке как универсальный параметр, так и элемент коллекции. Это позволит получить почти ту же семантику, что и структурные массивы, но если люди, работающие с vb.net и C #, не смогут предложить хороший синтаксис, код будет выглядеть неуклюже, даже если он будет достаточно производительным (передача универсального параметра будет разрешить использование статического делегата и избежать необходимости создавать какие-либо временные экземпляры класса).

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

person supercat    schedule 29.09.2011
comment
@ Рон Уорхолик: не очевидно, что SomeRect является прямоугольником. Это может быть какой-то другой тип, который может быть неявно приведен к типу Rectangle. Хотя единственный системный тип, который может быть неявно приведен к типу из Rectangle, - это RectangleF, и компилятор закричит, если кто-то попытается передать поля RectangleF в конструктор Rectangle (поскольку первые являются Single, а последние - Integer) могут существовать определяемые пользователем структуры, допускающие такое неявное приведение типов. Кстати, первый оператор будет работать одинаково хорошо, будь SomeRect Rectangle или RectangleF. - person supercat; 28.10.2011
comment
Все, что вы показали, - это то, что на надуманном примере вы считаете, что один метод более ясен. Если мы возьмем ваш пример с Rectangle, я могу легко придумать типичную ситуацию, в которой вы получаете крайне неясное поведение. Учтите, что WinForms реализует изменяемый тип Rectangle, используемый в свойстве формы Bounds. Если я хочу изменить границы, я бы хотел использовать ваш красивый синтаксис: form.Bounds.X = 10; Однако это точно ничего в форме не меняет (и генерирует прекрасную ошибку, информирующую вас об этом). Непоследовательность - это проклятие программирования, и именно поэтому требуется неизменность. - person Ron Warholic; 28.10.2011
comment
Что касается вашего второго пункта, я не понимаю, насколько это важно. Возможность заменить Rectangle на RectangleF с немного меньшим количеством изменений вряд ли является распространенной ситуацией, и, тем более, все еще требует изменения объявленного типа на любых сайтах объявления. Я думаю, что утверждать, что возможность немного изменить тип проще, стоит потери ясности, ясно показанной в ответах здесь. - person Ron Warholic; 28.10.2011
comment
@ Рон Уорхолик: Тот факт, что код, подобный form.Bounds.X = 10, не компилируется, делает очевидным, что он ничего не изменит. Моя точка зрения в отношении RectangleF заключалась просто в том, что объем информации, необходимый для определения эффекта второго оператора, намного больше, чем для первого. Если вы видели код MyRect = New Rectangle (MyRect.Left + 10, MyRect.Right, MyRect.Width, MyRect.Height); Можете ли вы с уверенностью сказать, что правильно определили бы, что он делает? Также предположим, что Rectangle - это класс. Чего вы ожидаете someForm.Bounds.X = 10; делать. Почему? - person supercat; 28.10.2011
comment
@ Рон Уорхолик: Кстати, я бы хотел иметь возможность сказать form.Bounds.X = 10; и пусть он просто работает, но система не предоставляет никакого чистого способа сделать это. Соглашение о предоставлении свойств типа значения в качестве методов, принимающих обратные вызовы, может предложить гораздо более чистый, эффективный и достоверно правильный код, чем любой подход с использованием классов. - person supercat; 28.10.2011
comment
позвольте нам продолжить это обсуждение в чате - person Ron Warholic; 28.10.2011
comment
Этот ответ гораздо более информативный, чем несколько ответов, получивших наибольшее количество голосов. Это своего рода абсурд, что аргумент против изменяемых типов значений основан на представлении о том, что вы ожидаете, когда вы смешиваете сглаживание и мутацию. Это ужасно, в любом случае! - person Eamon Nerbonne; 06.05.2016
comment
@EamonNerbonne: Я не подумал об этой фразеологии, но вы совершенно правы, и это связано с тем, почему структуры открытого поля часто являются правильным подходом: они поддерживают только эфемерные псевдонимы. Жаль, что в языках .NET нет хорошего шаблона для раскрытия эфемерных псевдонимов; Я бы хотел, чтобы был такой шаблон, что foo.Bar (57) .X = boz; может автоматически переводиться как foo.access_Bar (57, (ref Point it, ref int p1) = ›it.X = p1, ref boz), что позволяет коллекциям, отличным от массивов, разрешать доступ к внутренним компонентам структур, содержащихся в них. - person supercat; 06.05.2016
comment
@supercat: Кто знает, может быть, эта функция ref-return, о которой они говорят для C # 7, может охватывать эту основу (я на самом деле не рассматривал ее подробно, но на первый взгляд это похоже). - person Eamon Nerbonne; 06.05.2016
comment
@EamonNerbonne: намерение может быть аналогичным, но IMHO правильный протокол доступа должен сообщать коллекции, когда осуществляется доступ к объекту, и когда получатель закончил с ним. С точки зрения реализации, ref-return с последующим вызовом второго метода с защитой try-finally было бы хорошим подходом, но я не думаю, что есть какие-либо планы на этот счет. - person supercat; 06.05.2016
comment
@supercat посмотрите, хотите ли вы обновить этот ответ с помощью 7.2: blogs.msdn.microsoft.com/seteplia/2018/03/07/ - person Alexei Levenkov; 02.02.2019

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

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

    // Simple mutable structure. 
    // Method IncrementI mutates current state.
    struct Mutable
    {
        public Mutable(int i) : this() 
        {
            I = i;
        }

        public void IncrementI() { I++; }

        public int I { get; private set; }
    }

    // Simple class that contains Mutable structure
    // as readonly field
    class SomeClass 
    {
        public readonly Mutable mutable = new Mutable(5);
    }

    // Simple class that contains Mutable structure
    // as ordinary (non-readonly) field
    class AnotherClass 
    {
        public Mutable mutable = new Mutable(5);
    }

    class Program
    {
        void Main()
        {
            // Case 1. Mutable readonly field
            var someClass = new SomeClass();
            someClass.mutable.IncrementI();
            // still 5, not 6, because SomeClass.mutable field is readonly
            // and compiler creates temporary copy every time when you trying to
            // access this field
            Console.WriteLine(someClass.mutable.I);

            // Case 2. Mutable ordinary field
            var anotherClass = new AnotherClass();
            anotherClass.mutable.IncrementI();

            // Prints 6, because AnotherClass.mutable field is not readonly
            Console.WriteLine(anotherClass.mutable.I);
        }
    }

Изменяемые типы значений и массив

Предположим, у нас есть массив нашей структуры Mutable, и мы вызываем метод IncrementI для первого элемента этого массива. Какого поведения вы ожидаете от этого звонка? Должен ли он изменить значение массива или только его копию?

    Mutable[] arrayOfMutables = new Mutable[1];
    arrayOfMutables[0] = new Mutable(5);

    // Now we actually accessing reference to the first element
    // without making any additional copy
    arrayOfMutables[0].IncrementI();

    // Prints 6!!
    Console.WriteLine(arrayOfMutables[0].I);

    // Every array implements IList<T> interface
    IList<Mutable> listOfMutables = arrayOfMutables;

    // But accessing values through this interface lead
    // to different behavior: IList indexer returns a copy
    // instead of an managed reference
    listOfMutables[0].IncrementI(); // Should change I to 7

    // Nope! we still have 6, because previous line of code
    // mutate a copy instead of a list value
    Console.WriteLine(listOfMutables[0].I);

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

person Sergey Teplyakov    schedule 14.07.2011
comment
Что должно произойти, если бы языки .net имели немного лучшую поддержку типов значений, методам структуры следует запретить изменять 'this', если они явно не объявлены как делающие это, а методы, объявленные таким образом, должны быть запрещены в режиме только для чтения контексты. Массивы изменяемых структур предлагают полезную семантику, которую невозможно эффективно реализовать другими способами. - person supercat; 29.09.2011
comment
это хорошие примеры очень тонких проблем, которые могут возникнуть из-за изменяемых структур. Я бы не ожидал такого поведения. Почему массив дает вам ссылку, а интерфейс дает вам значение? Я бы подумал, если не считать постоянных значений (чего я действительно ожидал), что все будет по крайней мере наоборот: интерфейс дает ссылки; массивы, дающие значения ... - person Dave Cousineau; 06.02.2012
comment
@Sahuagin: К сожалению, не существует стандартного механизма, с помощью которого интерфейс может предоставлять ссылку. Существуют способы .net сделать такие вещи безопасными и полезными (например, путем определения специальной структуры ArrayRef ‹T›, содержащей T[] и целочисленный индекс, и при условии, что доступ к свойству типа ArrayRef<T> будет интерпретироваться как доступ к соответствующему элементу массива) [если класс хочет предоставить ArrayRef<T> для какой-либо другой цели, он может предоставить метод - в отличие от свойства - для его получения]. К сожалению, таких положений нет. - person supercat; 02.06.2012
comment
О боже ... это делает изменчивые структуры чертовски злыми! - person nawfal; 08.10.2013
comment
Когда вы реорганизуете методы изменения в статические методы, требующие параметр ref: public static void IncrementI(ref Mutable m) { m.I++; }, компилятор должен мешать вам делать неправильные вещи в большинстве случаев. - person springy76; 19.10.2016
comment
Мне нравится этот ответ, потому что он содержит очень ценную информацию, которая неочевидна. Но на самом деле это не аргумент против изменяемых структур, как утверждают некоторые. Да, то, что мы видим здесь, - это яма отчаяния, как сказал бы Эрик, но источник этого отчаяния не в изменчивости. Источником отчаяния являются самоизменяющиеся методы структур. (Что касается того, почему массивы и списки ведут себя по-разному, это потому, что один в основном является оператором, который вычисляет адрес памяти, а другой - свойством. В общем, все становится ясно, когда вы понимаете, что ссылка - это адрес value .) - person AnorZaken; 31.08.2017

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

Как упоминает Конрад, нет смысла изменять дату, поскольку значение представляет собой этот уникальный момент времени, а не экземпляр объекта времени, который имеет какое-либо состояние или зависимость от контекста.

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

person Morten Christiansen    schedule 13.01.2009
comment
Что ж, они должны представлять неизменные концепции, по крайней мере ;-p - person Marc Gravell; 14.01.2009
comment
Верно, но я полагаю, что вы можете неправильно использовать большинство программных конструкций. - person Morten Christiansen; 14.01.2009
comment
Что ж, я полагаю, они могли бы сделать System.Drawing.Point неизменным, но это было бы серьезной ошибкой дизайна ИМХО. Я думаю, что точки на самом деле являются архетипическим типом значений, и они изменчивы. И они не доставляют никаких проблем никому, кроме действительно начинающих программистов 101. - person Stephen Martin; 14.01.2009
comment
В принципе, я думаю, что точки также должны быть неизменяемыми, но если это делает тип более сложным или менее элегантным в использовании, то, конечно, это тоже следует учитывать. Нет смысла иметь конструкции кода, поддерживающие лучшие принципы, если никто не хочет их использовать;) - person Morten Christiansen; 14.01.2009
comment
Типы значений полезны для представления простых неизменяемых концепций, но структуры открытых полей - лучшие типы для хранения или передачи небольших фиксированных наборов связанных, но независимых значений (таких как координаты точки). Место хранения такого типа значения инкапсулирует значения его полей и ничего больше. Напротив, место хранения изменяемого ссылочного типа может использоваться для сохранения состояния изменяемого объекта, но также инкапсулирует идентичность всех других ссылок во всем юниверсе, которые существуют для того же объекта. - person supercat; 13.06.2013
comment
@supercat Это лучшее мнение. Структура - это просто набор ценностей. Когда вы начинаете видеть это таким образом, вас не должны зацепить никакие угловые случаи. - person IS4; 12.11.2014
comment
@ IllidanS4: Совершенно верно. Структуры могут быть полезны для вещей, отличных от простых агрегатов, и структуры, которые претендуют на звание недорогих объектов, должны вести себя как объекты (обычно подразумевая, что они должны быть неизменяемыми), но для структур, которые используются просто как агрегаты, чем более открыта структура, тем легче кто-то сможет увидеть, что это не более чем агрегирование. - person supercat; 12.11.2014
comment
«Типы значений в основном представляют собой неизменяемые концепции». Нет, это не так. Одним из старейших и наиболее полезных приложений переменной с типом значения является int итератор, который был бы совершенно бесполезен, если бы он был неизменным. Я думаю, вы объединяете «реализации компилятора / среды выполнения типов значений» с «переменными, типизированными для типа значения» - последний, безусловно, может быть изменен на любое из возможных значений. - person Slipp D. Thompson; 07.04.2015
comment
По логике, которую вы указали в этом ответе, все типы неизменяемы. Классы хранятся как коллекции типов значений и ссылок (указатели / дескрипторы адресов памяти) - поэтому они также неизменяемы, поскольку вы не меняете адрес памяти, вы просто «присваиваете другое уникальное значение». Q явно относится к предлагаемому использованию структур данных структурной категории таким образом, чтобы с точки зрения высокоуровневых программистов, изменяются значения и ячейки памяти, которые они содержат во время инициализации. Переключение обсуждения на оптимизацию компилятора делает это A несущественным. - person Slipp D. Thompson; 07.04.2015

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

Итак, изменяемые структуры - это не зло, C # сделал их злыми. Я постоянно использую изменяемые структуры в C ++, они очень удобны и интуитивно понятны. Напротив, C # заставил меня полностью отказаться от структур как членов классов из-за того, как они обрабатывают объекты. Их удобство стоило нам нашего.

person ThunderGr    schedule 18.10.2013
comment
Наличие полей классов структурных типов часто может быть очень полезным шаблоном, хотя, по общему признанию, есть некоторые ограничения. Производительность будет ухудшаться, если использовать свойства, а не поля, или readonly, но если этого не делать, поля классов структурных типов вполне подойдут. Единственное действительно фундаментальное ограничение структур состоит в том, что поле структуры изменяемого типа класса, такого как int[], может инкапсулировать идентичность или неизменный набор значений, но не может использоваться для инкапсуляции изменяемых значений без инкапсуляции нежелательной идентичности. - person supercat; 13.11.2013

Если вы будете придерживаться того, для чего предназначены структуры (в C #, Visual Basic 6, Pascal / Delphi, тип структуры C ++ (или классы), когда они не используются в качестве указателей), вы обнаружите, что структура не более чем составная переменная. Это означает: вы будете рассматривать их как упакованный набор переменных под общим именем (переменная записи, из которой вы ссылаетесь на члены).

Я знаю, что это сбило бы с толку многих людей, глубоко привыкших к ООП, но этого недостаточно, чтобы говорить, что такие вещи изначально злы, если они используются правильно. Некоторые структуры неизменяемы по своему назначению (это случай namedtuple в Python), но это еще одна парадигма, которую следует рассмотреть.

Да: структуры требуют много памяти, но это не будет точно больше памяти, если:

point.x = point.x + 1

по сравнению с:

point = Point(point.x + 1, point.y)

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

Но, наконец, структуры - это структуры, а не объекты. В POO основным свойством объекта является его идентичность, которая в большинстве случаев не превышает его адрес в памяти. Struct обозначает структуру данных (не правильный объект, поэтому у них все равно нет идентичности), и данные могут быть изменены. В других языках запись (вместо struct, как в случае с Паскалем) является словом и имеет ту же цель: просто переменная записи данных, предназначенная для чтения из файлов, измененных и сброшенных в файлы (это основное использование, и на многих языках вы даже можете определить выравнивание данных в записи, хотя это не обязательно так для правильно называемых объектов).

Хотите хороший пример? Структуры используются для легкого чтения файлов. В Python есть эта библиотека, потому что, поскольку она объектно-ориентирована и не поддерживает структуры, она должна была реализовать его по-другому, что несколько некрасиво. Языки, реализующие структуры, имеют эту функцию ... встроенную. Попробуйте прочитать заголовок растрового изображения с соответствующей структурой на таких языках, как Pascal или C. Это будет легко (если структура правильно построена и выровнена; в Pascal вы не будете использовать доступ на основе записей, а функции для чтения произвольных двоичных данных). Итак, для файлов и прямого (локального) доступа к памяти структуры лучше, чем объекты. На сегодняшний день мы привыкли к JSON и XML, поэтому забываем об использовании двоичных файлов (и, как побочный эффект, об использовании структур). Но да: они существуют и имеют цель.

Они не злые. Просто используйте их по назначению.

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

person Luis Masuelli    schedule 02.12.2015

Представьте, что у вас есть массив из 1 000 000 структур. Каждая структура, представляющая капитал с такими вещами, как bid_price, offer_price (возможно, десятичные дроби) и т. Д., Создается C # / VB.

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

Представьте, что код C # / VB слушает рыночный поток изменений цен, этому коду может потребоваться доступ к некоторому элементу массива (для любой безопасности), а затем изменить некоторые поля цены.

Представьте, что это делается десятки или даже сотни тысяч раз в секунду.

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

Хьюго

person Hugo    schedule 21.03.2010
comment
Верно, но в этом случае причина использования структуры заключается в том, что это налагается на дизайн приложения внешними ограничениями (теми, которые связаны с использованием собственного кода). Все остальное, что вы описываете об этих объектах, предполагает, что они явно должны быть классами в C # или VB.NET. - person Jon Hanna; 15.10.2010
comment
Я не уверен, почему некоторые люди думают, что вещи должны быть объектами класса. Если все слоты массива заполнены ссылками на отдельные экземпляры, использование типа класса добавит дополнительные двенадцать или двадцать четыре байта к требованиям к памяти, а последовательный доступ к массиву ссылок на объекты класса будет намного медленнее, чем последовательный доступ к ним. массив структур. - person supercat; 05.06.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 и изменять его), но, поскольку это не идиоматично в ООП, оно все равно будет быть не интуитивно понятным для всех, кто работает с вашим кодом.

person Doval    schedule 02.10.2013
comment
Ваша точка зрения о идентичности верна; возможно, стоит отметить, что идентичность актуальна только тогда, когда на что-то существует несколько ссылок. Если foo содержит единственную ссылку на свою цель где-либо во вселенной, и ничто не захватило значение хэша идентичности этого объекта, тогда изменяющееся поле foo.X семантически эквивалентно тому, что foo указывает на новый объект, который точно такой же, как тот, на который он ранее ссылался , но с X, содержащим желаемое значение. С типами классов обычно трудно узнать, существует ли несколько ссылок на что-либо, но со структурами это просто: их нет. - person supercat; 03.10.2013
comment
Если Thing является изменяемым типом класса, Thing[] будет инкапсулировать идентификаторы объектов - хочет кто-то этого или нет - если только никто не может гарантировать, что ни один Thing в массиве, на который существуют какие-либо внешние ссылки, никогда не будет мутировать. Если кто-то не хочет, чтобы элементы массива инкапсулировали идентичность, обычно необходимо убедиться, что ни один элемент, на который он содержит ссылки, никогда не будет мутирован, или что никакие внешние ссылки никогда не будут существовать ни на какие элементы, которые он хранит [гибридные подходы также могут работать ]. Ни один из подходов не очень удобен. Если Thing - это структура, Thing[] инкапсулирует только значения. - person supercat; 04.10.2013
comment
Что касается объектов, их идентичность определяется их местоположением. Экземпляры ссылочных типов имеют свою идентичность благодаря своему расположению в памяти, и вы передаете только их идентичность (ссылку), а не их данные, в то время как типы значений имеют свою идентичность во внешнем месте, где они хранятся. Идентичность вашего типа значения Эрика определяется только переменной, в которой он хранится. Если вы пройдете мимо него, он потеряет свою личность. - person IS4; 12.11.2014

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

person Bombe    schedule 13.01.2009
comment
Это верно, если вы также используете класс в качестве ключа на карте. - person Marc Gravell; 14.01.2009

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

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

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

person Andru Luvisi    schedule 13.01.2009
comment
структуры не имеют псевдонимов в C #. Каждое присвоение структуры - это копия. - person recursive; 24.09.2010
comment
@recursive: в некоторых случаях это главное преимущество изменяемых структур, которое заставляет меня усомниться в том, что структуры не должны быть изменяемыми. Тот факт, что компиляторы иногда неявно копируют структуры, не снижает полезности изменяемых структур. - person supercat; 10.10.2010

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

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

а не просто

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

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

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

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

person Mike    schedule 07.02.2011
comment
Прямо в точку! Структуры - это организационные единицы без ограничений контроля доступа! К сожалению, C # сделал их бесполезными для этой цели! - person ThunderGr; 18.10.2013
comment
this полностью упускает из виду суть, поскольку оба ваших примера показывают изменяемые структуры. - person vidstige; 28.09.2015
comment
C # сделал их бесполезными для этой цели, потому что это не предназначение структур - person Luiz Felipe; 10.02.2017

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

  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 Нажмите любую кнопку, чтобы продолжить. . .

person Ramesh Kadambi    schedule 12.05.2015
comment
Нет ничего плохого в том, чтобы проектировать небольшие непрозрачные структуры, которые ведут себя как неизменяемые объекты класса; рекомендации MSDN разумны , когда кто-то пытается сделать что-то, что ведет себя как объект. Структуры подходят в некоторых случаях, когда нужны легкие вещи, которые ведут себя как объекты, а также в случаях, когда требуется связка переменных, склеенных изолентой. Однако по какой-то причине многие люди не понимают, что у структур есть два разных использования и что руководящие принципы, подходящие для одного, не подходят для другого. - person supercat; 13.05.2015

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

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

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

person ShaunnyBwoy    schedule 19.12.2013
comment
На мой взгляд, в основе структуры лежит куча переменных, склеенных изолентой. В .NET возможно, чтобы структура притворялась чем-то другим, а не кучей переменных, склеенных вместе изолентой, и я бы посоветовал, когда это будет практично, тип, который будет притворяться чем-то другим, а не связкой переменных, склеенных вместе с изолентой должен вести себя как единый объект (что для структуры подразумевает неизменность), но иногда полезно склеить кучу переменных вместе изолентой. Даже в производственном коде я считаю, что лучше иметь тип ... - person supercat; 19.12.2013
comment
... который явно не имеет семантики, кроме того, что каждое поле содержит последнее, что записано в него, вставляя всю семантику в код, который использует структуру, чем пытаться заставить структуру делать больше. Учитывая, например, тип Range<T> с полями членов Minimum и Maximum типа T и код Range<double> myRange = foo.getRange();, любые гарантии относительно того, что содержат Minimum и Maximum, должны исходить от foo.GetRange();. Если Range будет структурой с открытым полем, это даст понять, что она не собирается добавлять какое-либо собственное поведение. - person supercat; 19.12.2013