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

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

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

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

Slipp D. Thompson 21.07.2014 09:05

@slipp int и bool являются изменяемыми нет ..

Blorgbeard 05.03.2015 07:04

.-синтаксис, благодаря которому операции с типизированными данными и данными с типизированными значениями выглядят одинаково, даже если они существенно отличаются. Это ошибка свойств C#, а не структур - некоторые языки предлагают альтернативный синтаксис a[V][X] = 3.14 для изменения на месте. В C# лучше предложить методы мутатора-члена структуры, такие как 'MutateV (Action <ref Vector2> mutator) `, и использовать их как a.MutateV((v) => { v.X = 3; })(пример чрезмерно упрощен из-за ограничений, которые C# имеет в отношении ключевого слова ref, но с некоторыми обходными путями должны быть возможны).

Slipp D. Thompson 05.05.2015 22:58

@ Sushi271 обходной путь (n): метод преодоления проблемы или ограничения в программе или системе. Это ограничение синтаксиса C#. Идеальное использование ключевого слова ref в универсальном типе не является двусмысленным; это просто не поддерживается. Это ограничение. И да, C# во многих отношениях заставляет неудобный синтаксис выполнять свою работу. Самый классический пример неуклюжести C# - это public снова и снова для каждого члена, а не префикс public: в стиле C++ или отдельный public FieldName1, FieldName2, MethodName1, MethodName2; в стиле Ruby.

Slipp D. Thompson 05.05.2015 23:33

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

Slipp D. Thompson 05.05.2015 23:36

@Slipp Что ж, я думаю об этих структурах с точностью до наоборот. Как вы думаете, почему структуры, которые уже реализованы в библиотеке .NET, такие как DateTime или TimeSpan (такие похожие), неизменяемы? Возможно, было бы полезно изменить только один член такой структуры var, но это слишком неудобно, приводит к слишком большому количеству проблем. На самом деле вы ошибаетесь насчет того, что вычисляет процессор, поскольку C# не компилируется в ассемблер, он компилируется в IL. В IL (при условии, что у нас уже есть переменная с именем x) эта единственная операция состоит из 4 инструкций: ldloc.0 (загружает переменную с нулевым индексом в ...

Sushi271 06.05.2015 11:28

... тип. T - это типа. Ref - это просто ключевое слово, которое заставляет переменную передаваться самому методу, а не его копию. Это также имеет смысл для ссылочных типов, поскольку мы можем изменить переменная, то есть ссылка вне метода будет указывать на другой объект после изменения внутри метода. Поскольку ref T - это не тип, а способ передачи параметра метода, вы не можете поместить его в <>, потому что там могут быть помещены только типы. Так что это просто неверно. Может быть, это было бы удобно, возможно, команда C# могла бы сделать это для какой-то новой версии, но сейчас они работают над некоторыми ...

Sushi271 06.05.2015 11:46

... гораздо более важные вещи, такие как условный оператор null или инициализаторы авто-свойств. Что касается public ... Мне действительно нравится способ, которым это делает C#. На самом деле рефакторинг проще, потому что удаление / добавление одного паблика в одном месте для одного участника не влияет на всех остальных участников. В C++ мне часто приходится добавлять две метки (например, public: и private: снова) или перемещать метод в другую часть класса. Неудобно. Во всяком случае, упоминание этой темы заставило меня понять, что мы должны прекратить это обсуждение. Мы начинаем спорить о собственном мнении, и можем годами сражаться и не найти общего ..

Sushi271 06.05.2015 11:57

земля. Каждый имеет право на собственное мнение. Вы, сэр, кажется, более привязаны к C++, тогда как моим основным языком в последние 8 лет был C#. Конечно, познавать чью-то точку зрения по некоторым вопросам, однако сейчас мы далеко отклонились от основной темы. Так что спасибо за ваш вклад.

Sushi271 06.05.2015 12:02

Вот и мы в 2017 году, и изменяемые структуры вернулись! : D Как и всегда, все дело в знании того, как и когда их использовать. При этом я бы, наверное, сказал любому новичку «Держитесь подальше от структур или хотя бы сделайте их изменяемыми - потом вернитесь к ним позже, когда у вас будет больше опыта, они предлагают особые преимущества». (Понимание того, что ссылка - это особый тип значения адреса со специальными перегрузками операторов, решает все, но новичкам это слишком сложно пережевывать.)

AnorZaken 31.08.2017 18:31

Я согласен с тем, что изменяемые структуры @AnorZaken являются «злом» только в том случае, если вы не знаете разницы между типами значений и ссылочными типами, в противном случае можно использовать структуры, даже если они являются изменяемыми, если вы знаете, что делаете. (кстати, сам .NET имеет много изменяемых структур)

R1PFake 26.03.2018 22:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
508
11
84 086
16
Перейти к ответу Данный вопрос помечен как решенный

Ответы 16

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

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

This is yet another reason why mutable value types are evil. Try to always make value types immutable.

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

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, вы можете очень быстро прожечь большой объем памяти. С классом несколько вызывающих могут совместно использовать ссылку на один и тот же экземпляр (ссылки небольшие).

Ну да, но компилятор в этом смысле глуп. Запретить присваивание элементам структуры свойств было ИМХО глупым дизайнерским решением, потому что является разрешено для оператора ++. В этом случае компилятор просто сам пишет явное присвоение вместо того, чтобы толкать программиста.

Konrad Rudolph 14.01.2009 11:08

@Konrad: myObj.SomeProperty.Size = 22 изменит КОПИЮ myObj.SomeProperty. Компилятор избавляет вас от очевидной ошибки. И это НЕ разрешено для ++.

Lucas 15.05.2009 19:38

@Lucas: Что ж, компилятор Mono C# определенно позволяет это - поскольку у меня нет Windows, я не могу проверить компилятор Microsoft.

Konrad Rudolph 20.09.2010 14:33

@Konrad - компилятор MS отказывает: `class Test {public MyStruct Value {get; набор; } публичная структура MyStruct {публичная int Bar {получить; набор; }} public static void Main () {Test test = new Test (); test.Value.Bar ++; }} `- это работает с моно?

Marc Gravell 20.09.2010 14:38

@Marc: На самом деле я говорил об одном косвенном обращении меньше: test.Bar++ или test.Bar += 1. Это также изменяет значение - фактически, оно более или менее эквивалентно var tmp = test.Bar; tmp += 1; test.Bar = tmp;. Так что ситуация не полностью идентична, но я никогда не об этом говорил. Я хотел сказать, что компилятор может и действительно переписывает код там, где это имеет смысл. И полученная перезапись здесь почти полностью идентична ручной перезаписи, которую вы предложили в своем ответе. Единственное, что отличает bar.Size = 22; от bar = bar + 1.

Konrad Rudolph 20.09.2010 14:49

@Marc: Ага. Я забираю это назад - здесь есть существенная разница, мой код всегда создает новый объект, он также работает с неизменяемыми объектами.

Konrad Rudolph 20.09.2010 14:54

@Konrad - с одним косвенным обращением должно работать; это «изменение значения чего-то, что существует только как временное значение в стеке и которое вот-вот испарится в ничто», что и является заблокированным случаем.

Marc Gravell 20.09.2010 15:13

@Marc Gravell: В предыдущем фрагменте кода вы получаете «Foo» с именем «abc» и другими атрибутами, принадлежащими List [0], не нарушая List [0]. Если бы Foo был классом, его необходимо было бы клонировать, а затем изменить копию. На мой взгляд, большая проблема, связанная с различием типа значения и класса, заключается в использовании символа "." оператор для двух целей. Если бы у меня были мои ученики, классы могли бы поддерживать и то, и другое ». и "->" для методов и свойств, но обычная семантика для "." properties будет создавать новый экземпляр с соответствующим измененным полем.

supercat 10.10.2010 00:52

Почему myObj.SomeProperty.Size = должен модифицировать копию myObj.SomeProperty, а bar.Size = модифицировать bar, а не копию bar? Вот где кажется плохое решение ...

NetMage 09.11.2017 00:21

Я пробовал myObj.SomeProperty.Size = 22; и он компилируется и работает нормально.

David Klempfner 03.04.2018 14:23

@Backwards_Dave, тогда вы можете сравнивать другой сценарий; либо SomeProperty на самом деле не является свойством (возможно, это поле?), либо типSomeProperty на самом деле не является struct. Вот минимальная репродукция, которая показывает CS1612: sharplab.io/…

Marc Gravell 03.04.2018 16:30

@MarcGravell, ты прав. Спасибо, что прояснили это. Как только вы поймете, что свойство - это просто метод, который возвращает значение, тогда станет очевидно, что возвращаемое значение является копией типа значения!

David Klempfner 04.04.2018 05:40

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

user2864740 21.01.2019 10:21

Может быть, что-то было потеряно после правок, но включение цитаты Эрика добавляет нулевой ценности при ответе на вопрос ОП. Я думаю, нам следует просто удалить цитату и сказать: «Блог Эрика Липперта подробно освещает это». С другой стороны, вы очень умный парень, а я нет, так что я, возможно, что-то упускаю.

Ash 22.08.2020 06:58

@Ash цитата была добавлена ​​примерно через 5 минут после исходного ответа, поэтому в общей схеме более 11 лет мы, вероятно, должны рассматривать ее как часть самого исходного ответа. В конечном счете, цитата была просто лаконичным заголовком, чтобы создать сцену для блога Эрика, и хотя «Блог Эрика широко освещает это» также достиг бы этого, мы должны оставлять в жизни место для небольшого артистического расцвета время от времени :)

Marc Gravell 22.08.2020 19:39

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

Это верно, если вы также используете класс в качестве ключа на карте.

Marc Gravell 14.01.2009 02:37

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

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

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

Эрик Липперт говорит, что они ... см. Мой ответ.

Marc Gravell 14.01.2009 02:37

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

Marc Gravell 14.01.2009 02:44

Как бы я ни уважал Эрика Липперта, он не Бог (по крайней мере, пока). Сообщение в блоге, на которое вы ссылаетесь, и ваше сообщение выше, конечно же, являются разумными аргументами в пользу того, чтобы сделать структуры неизменяемыми, но на самом деле они очень слабы в качестве аргументов для никогда с использованием изменяемых структур. Этот пост, однако, имеет +1.

Stephen Martin 14.01.2009 04:12

При разработке на C# вам обычно время от времени требуется изменчивость, особенно с вашей бизнес-моделью, где вы хотите, чтобы потоковая передача и т. д. Бесперебойно работала с существующими решениями. Я написал статью о том, как работать с изменяемыми И неизменяемыми данными, решив большинство проблем, связанных с изменчивостью (надеюсь): rickyhelgesson.wordpress.com/2012/07/17/…

Ricky Helgesson 31.07.2012 11:42

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

supercat 05.06.2013 03:40

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

supercat 05.06.2013 03:49

@StephenMartin: Рассмотрим, например, метод или свойство, которое должно возвращать шесть компонентов float графического преобразования. Если такой метод возвращает структуру открытого поля с шестью компонентами, очевидно, что изменение полей структуры не изменит графический объект, из которого он был получен. Если такой метод возвращает изменяемый объект класса, возможно, изменение его свойств изменит базовый графический объект, а, возможно, и нет - на самом деле никто не знает.

supercat 05.06.2013 03:51

@supercat: Я не понимаю, какую проблему это решит. Важность неизменяемого доступа к объекту заключается в том, что вы знаете, что он будет согласован между чтениями, и все свойства объекта, очевидно, всегда согласованы друг с другом. Читаемый интерфейс позволит вам читать данные из объекта, который может быть нестабильным и, возможно, даже опасным для чтения, если у вас есть свойства, которые не могут быть обновлены за одну операцию, что делает интерфейс опасным. Итак, если бы я добавил это, это должна была бы быть очень важная проблема, которую нужно было бы решить, чтобы представить эти опасности.

Ricky Helgesson 17.06.2013 01:46

@RickyHelgesson: код, которому требуется неизменяемый объект, должен требовать изменяемый объект. «Читаемый» интерфейс предназначен для кода, который хочет прочитать текущее состояние объекта, который может быть изменяемым. Среди прочего, если бы конструктор изменяемого типа был бы готов принять изменяемый объект, во многих случаях не было бы причин, по которым он также не был бы полностью счастлив, если бы ему был предоставлен объект, который содержал требуемую информацию, но был неизменным. Определение «читаемого» интерфейса позволит избежать необходимости перегрузки конструктора изменяемого объекта для изменяемых или неизменяемых исходных операндов.

supercat 17.06.2013 02:41

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

Ahmad 13.02.2015 16:03

@Ahmad Я привел причины этого в своем сообщении, и в том, что связано.

Konrad Rudolph 13.02.2015 17:48
Ответ принят как подходящий

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

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

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

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

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

Lucas 15.05.2009 19:43

@Lucas Я думаю, вы говорите о другом виде копии. Я говорю об автоматических копиях, сделанных в результате передачи по значению. Ваша «сознательно созданная копия» отличается от того, что вы сделали ее не по ошибке, а ее На самом деле это не копия, а преднамеренные новые моменты, содержащие разные данные.

trampster 27.05.2010 01:34

Ваша редакция (16 месяцев спустя) проясняет это. Я все еще поддерживаю «(неизменяемая структура) означает, что вас не поймают на изменении копии, думая, что вы изменяете оригинал».

Lucas 27.05.2010 19:00

@Lucas: опасность создания копии структуры, ее изменения и того, что кто-то думает, что кто-то модифицирует оригинал (когда тот факт, что кто-то записывает поле структуры, делает самоочевидный тем фактом, что кто-то пишет только свою копию) кажется довольно незначительной. по сравнению с опасностью того, что кто-то, кто держит объект класса как средство хранения содержащейся в нем информации, будет видоизменять объект для обновления своей собственной информации и в процессе искажать информацию, содержащуюся в каком-либо другом объекте.

supercat 28.10.2011 20:11

Итак, чтобы избежать «зла», нужно ли создавать обертки классов для каждой структуры (прямоугольник, размер, точка и т. д.), С которыми мы сталкиваемся? Разве это не громоздко?

Blip 29.12.2013 19:34

@VictorPrograss: сохраните структуру в одноэлементном массиве, и она будет работать как класс. В качестве альтернативы, если кто-то определяет класс public ExposedFieldHolder<T> { public T Value; public ExposedFieldHolder(T v) { Value = v; } }, то его можно использовать для превращения любой структуры в сущность (сделать тип общедоступным безвредно, поскольку весь цель типа должен иметь точно указанной семантики, и его раскрытие не приведет к раскрытию какой-либо экземпляры в код, который не должен их видеть).

supercat 12.03.2014 19:48

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

Joao Carlos 28.12.2014 18:43

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

Dave_cz 04.04.2016 16:16

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

Saeb Amini 22.05.2016 13:48

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

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

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

Структуры не имеют псевдонимов в C#. Каждое присвоение структуры - это копия.

recursive 24.09.2010 23:24

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

supercat 10.10.2010 00:44

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

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

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

Ну, они должен представляют неизменные концепции, по крайней мере ;-p

Marc Gravell 14.01.2009 02:54

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

Morten Christiansen 14.01.2009 02:58

Что ж, я полагаю, они могли бы сделать System.Drawing.Point неизменяемым, но это было бы серьезной ошибкой дизайна ИМХО. Я думаю, что точки на самом деле являются архетипическим типом значений, и они изменчивы. И они не доставляют никаких проблем никому, кроме действительно начинающих программистов 101.

Stephen Martin 14.01.2009 04:29

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

Morten Christiansen 14.01.2009 13:19

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

supercat 13.06.2013 22:51

@supercat Это лучшее мнение. Структура - это просто набор ценностей. Когда вы начинаете видеть это таким образом, вас не должны зацепить никакие угловые случаи.

IS4 12.11.2014 21:02

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

supercat 12.11.2014 21:33
«Типы значений в основном представляют собой неизменные концепции». Нет, не делают. Одним из старейших и наиболее полезных приложений переменной с типом значения является итератор int, который был бы совершенно бесполезен, если бы он был неизменным. Я думаю, вы объединяете «реализации компилятора / среды выполнения типов значений» с «переменными, типизированными для типа значения» - последний, безусловно, может быть изменен на любое из возможных значений.
Slipp D. Thompson 07.04.2015 03:37

По логике, которую вы указали в этом ответе, все типы неизменяемы. Классы хранятся как коллекции типов значений и ссылок (указатели / дескрипторы адресов памяти) - поэтому они также неизменяемы, поскольку вы не меняете адрес памяти, вы просто «Присвоить другое уникальное значение». Q явно относится к предлагаемому использованию структур данных структурной категории таким образом, чтобы с точки зрения высокоуровневых программистов, изменяются значения и ячейки памяти, которые они содержат в момент инициализации. Переключение обсуждения на оптимизацию компилятора делает этот A несущественным.

Slipp D. Thompson 07.04.2015 03:42

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

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

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

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

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

Хьюго

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

Jon Hanna 15.10.2010 18:20

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

supercat 05.06.2013 03:55

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

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

а не просто

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

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

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

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

Прямо к сути! Структуры - это организационные единицы без ограничений контроля доступа! К сожалению, C# сделал их бесполезными для этой цели!

ThunderGr 18.10.2013 15:16

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

vidstige 28.09.2015 22:19

C# сделал их бесполезными для этой цели, потому что это не предназначение структур

Luiz Felipe 10.02.2017 03:44

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

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

    // 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);

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

Что должно произойти, если бы языки .net имели немного лучшую поддержку типов значений, методам структуры следует запретить изменять 'this', если они явно не объявлены как делающие это, а методы, объявленные таким образом, должны быть запрещены в режиме только для чтения контексты. Массивы изменяемых структур предлагают полезную семантику, которую невозможно эффективно реализовать другими способами.

supercat 29.09.2011 03:09

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

Dave Cousineau 06.02.2012 15:54

@Sahuagin: К сожалению, не существует стандартного механизма, с помощью которого интерфейс может предоставлять ссылку. Есть способы .net сделать такие вещи безопасными и полезными (например, путем определения специальной структуры "ArrayRef <T>", содержащей T[] и целочисленный индекс, и при условии, что доступ к свойству типа ArrayRef<T> будет интерпретироваться в качестве доступа к соответствующему элементу массива) [если класс хочет предоставить ArrayRef<T> для какой-либо другой цели, он может предоставить метод - в отличие от свойства - для его извлечения]. К сожалению, таких положений нет.

supercat 02.06.2012 01:25

О боже ... это делает изменчивые структуры чертовски злыми!

nawfal 08.10.2013 10:40

Когда вы реорганизуете методы изменения в статические методы, требующие параметр ref: public static void IncrementI(ref Mutable m) { m.I++; }, тогда компилятор должен останавливать вас от совершения «неправильных» действий в большинстве случаев.

springy76 19.10.2016 14:43

Мне нравится этот ответ, потому что он содержит очень ценную информацию, которая неочевидна. Но на самом деле это не аргумент против изменяемых структур, как утверждают некоторые. Да, мы видим здесь «яму отчаяния», как сказал бы Эрик, но источник этого отчаяния не в изменчивости. Источником отчаяния являются самоизменяющиеся структуры методы. (Что касается того, почему массивы и списки ведут себя по-разному, это потому, что один в основном является оператором, который вычисляет адрес памяти, а другой - свойством. В общем, все становится ясно, когда вы понимаете, что «ссылка» - это адрес значение.)

AnorZaken 31.08.2017 17:54

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

Методы структуры (в отличие от средств установки свойств), которые изменяют 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. Предполагая, что метод не использует «небезопасный» код, может ли он изменить foo?
  2. Если до вызова метода не существует внешних ссылок на 'foo', может ли существовать внешняя ссылка после?

Answers:

Question 1:
Method1(): no (clear intent)
Method2(): yes (clear intent)
Method3(): yes (uncertain intent)
Question 2:
Method1(): no
Method2(): no (unless unsafe)
Method3(): yes

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 для типов значений, во многих случаях изменяемые типы значений подходят лучше, чем любые другие типы сущностей.

@ Рон Уорхолик: не очевидно, что SomeRect является прямоугольником. Это может быть какой-то другой тип, который может быть неявно приведен к типу Rectangle. Хотя единственный системный тип, который может быть неявно приведен к типу из Rectangle, - это RectangleF, и компилятор закричит, если кто-то попытается передать поля RectangleF в конструктор Rectangle (поскольку первые являются Single, а последние - Integer) могут существовать определяемые пользователем структуры, допускающие такое неявное приведение типов. Кстати, первый оператор будет работать одинаково хорошо, будь SomeRect Rectangle или RectangleF.

supercat 28.10.2011 18:59

Все, что вы показали, - это то, что на надуманном примере вы считаете, что один метод более ясен. Если мы возьмем ваш пример с Rectangle, я мог бы легко придумать обычную ситуацию, в которой вы получаете очень неясно поведение. Учтите, что WinForms реализует изменяемый тип Rectangle, используемый в свойстве формы Bounds. Если я хочу изменить границы, я бы хотел использовать ваш красивый синтаксис: form.Bounds.X = 10; Однако это меняет именно ничего такого в форме (и генерирует прекрасную ошибку, информирующую вас об этом). Непоследовательность - это проклятие программирования, и именно поэтому требуется неизменность.

Ron Warholic 28.10.2011 20:28

Что касается вашего второго пункта, я не понимаю, как это имеет отношение к делу. Возможность заменить Rectangle на RectangleF с немного меньшим количеством изменений вряд ли является распространенной ситуацией, и, тем более, все еще требует изменения объявленного типа на любых сайтах декларации. Я думаю, что будет преувеличением утверждать, что возможность немного изменить тип проще, стоит потери ясности, ясно показанной в ответах здесь.

Ron Warholic 28.10.2011 20:44

@ Рон Уорхолик: Тот факт, что код вида "form.Bounds.X = 10" не компилируется, делает очевидным, что он ничего не изменит. Моя точка зрения в отношении RectangleF заключалась просто в том, что объем информации, необходимый для определения эффекта второго оператора, намного больше, чем для первого. Если вы видели код «MyRect = New Rectangle (MyRect.Left + 10, MyRect.Right, MyRect.Width, MyRect.Height);» Можете ли вы с уверенностью сказать, что правильно определили бы, что он делает? Также предположим, что Rectangle - это класс. Что бы вы ожидали "someForm.Bounds.X = 10;" делать. Почему?

supercat 28.10.2011 21:03

@Ron Warholic: Кстати, я бы нравиться мог сказать "form.Bounds.X = 10;" и пусть он просто работает, но система не предоставляет никакого чистого способа сделать это. Соглашение о предоставлении свойств типа значения в качестве методов, принимающих обратные вызовы, может предложить гораздо более чистый, эффективный и достоверно правильный код, чем любой подход с использованием классов.

supercat 28.10.2011 21:11

давайте продолжить обсуждение в чате

Ron Warholic 28.10.2011 21:52

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

Eamon Nerbonne 06.05.2016 16:49

@EamonNerbonne: Я не думал об этой фразеологии, но вы совершенно правы, и это связано с тем, почему структуры с открытым полем часто являются правильным подходом: они поддерживают только эфемерные псевдонимы. Жаль, что в языках .NET нет хорошего шаблона для раскрытия эфемерных псевдонимов; Я бы хотел, чтобы был такой шаблон, что "foo.Bar (57) .X = boz;" может автоматически переводиться как «foo.access_Bar (57, (ref Point it, ref int p1) => it.X = p1, ref boz)», что позволяет коллекциям, отличным от массивов, разрешать доступ к внутренним компонентам структур, содержащихся в них .

supercat 06.05.2016 17:41

@supercat: Кто знает, возможно, эта функция ref-return, о которой они говорят для C# 7, может охватывать эту основу (я на самом деле не рассматривал ее подробно, но на первый взгляд это похоже).

Eamon Nerbonne 06.05.2016 18:00

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

supercat 06.05.2016 18:10

@supercat посмотрите, хотите ли вы обновить этот ответ с помощью 7.2: blogs.msdn.microsoft.com/seteplia/2018/03/07/…

Alexei Levenkov 02.02.2019 06:23

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

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

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

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

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

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

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

supercat 04.06.2013 22:41

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

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 и изменять его), но, поскольку это не идиоматично в ООП, это все равно будет неинтуитивно для всех, кто работает с ваш код.

Ваше мнение о личность - хорошее; возможно, стоит отметить, что идентичность актуальна только тогда, когда на что-то существует несколько ссылок. Если foo содержит единственную ссылку на свою цель где-либо во вселенной, и ничто не захватило значение хэша идентичности этого объекта, то изменение поля foo.X семантически эквивалентно тому, что foo указывает на новый объект, который точно такой же, как тот, на который он ранее ссылался. , но с X, содержащим желаемое значение. С типами классов обычно трудно узнать, существует ли несколько ссылок на что-либо, но со структурами это просто: их нет.

supercat 03.10.2013 23:59

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

supercat 04.10.2013 00:15

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

IS4 12.11.2014 20:35

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

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

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

supercat 13.11.2013 00:59

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

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

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

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

supercat 19.12.2013 20:24

... который явно не имеет семантики, кроме «каждое поле содержит последнее, что в него записано», проталкивая всю семантику в код, который использует является структурой, чем пытаться заставить структуру делать больше. Учитывая, например, тип Range<T> с полями членов Minimum и Maximum типа T и код Range<double> myRange = foo.getRange();, любые гарантии относительно того, что содержат Minimum и Maximum, должны исходить от foo.GetRange();. Если Range будет структурой с открытым полем, это даст понять, что он не собирается добавлять какое-либо собственное поведение.

supercat 19.12.2013 20:39

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

  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 Нажмите любую клавишу для продолжения . . .

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

supercat 13.05.2015 00:27

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

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

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

point.x = point.x + 1

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

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

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

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

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

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

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

Другие вопросы по теме