После обсуждений здесь SO я уже несколько раз читал замечание о том, что изменяемые структуры являются «злыми» (как в ответе на это question).
В чем проблема изменчивости и структур в C #?
После обсуждений здесь SO я уже несколько раз читал замечание о том, что изменяемые структуры являются «злыми» (как в ответе на это question).
В чем проблема изменчивости и структур в C #?
Структуры - это типы значений, что означает, что они копируются при передаче.
Таким образом, если вы меняете копию, вы меняете только эту копию, а не оригинал и не любые другие копии, которые могут быть поблизости.
Если ваша структура неизменна, тогда все автоматические копии, полученные в результате передачи по значению, будут одинаковыми.
Если вы хотите изменить его, вы должны сделать это сознательно, создав новый экземпляр структуры с измененными данными. (не копия)
public ExposedFieldHolder<T> { public T Value; public ExposedFieldHolder(T v) { Value = v; } }
, то можно использовать его для превращения любой структуры в сущность (сделать тип общедоступным безвредно, поскольку вся цель типа иметь в точности указанную семантику, и ее раскрытие не приведет к открытию каких-либо экземпляров для кода, который не должен их видеть).
- person supercat; 12.03.2014
System.Drawing
(Point, Rectangle, ...).
- person Dave_cz; 04.04.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 Konrad Rudolph; 14.01.2009
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
myObj.SomeProperty.Size =
следует изменять копию myObj.SomeProperty
, но bar.Size =
изменять bar
, а не копию bar
? Вот где кажется плохое решение ...
- person NetMage; 09.11.2017
SomeProperty
на самом деле не является свойством (возможно, это поле?), либо тип SomeProperty
на самом деле не является struct
. Вот минимальная репродукция, что показывает CS1612: sharplab.io/
- person Marc Gravell; 03.04.2018
Я бы не сказал зло, но изменчивость часто является признаком чрезмерного стремления программиста обеспечить максимум функциональности. На самом деле в этом часто нет необходимости, и это, в свою очередь, делает интерфейс меньше, проще в использовании и труднее использовать неправильно (= более надежно).
Одним из примеров этого являются конфликты чтения / записи и записи / записи в условиях гонки. Это просто не может происходить в неизменяемых структурах, поскольку запись не является допустимой операцией.
Кроме того, я утверждаю, что изменчивость практически никогда не нужна strong>, программист просто думает, что это может произойти в будущем. Например, менять дату просто не имеет смысла. Лучше создать новую дату на основе старой. Это дешевая операция, поэтому производительность не принимается во внимание.
float
компонентов графического преобразования. Если такой метод возвращает структуру открытого поля с шестью компонентами, очевидно, что изменение полей структуры не приведет к изменению графического объекта, от которого она была получена. Если такой метод возвращает изменяемый объект класса, возможно, изменение его свойств изменит базовый графический объект, а, возможно, и нет - на самом деле никто не знает.
- person supercat; 05.06.2013
Rectangle
, которое может быть моим размером экрана, и я хотел бы изменить его вне класса.
- person Ahmad; 13.02.2015
Изменяемые структуры не являются злом.
Они абсолютно необходимы в условиях высокой производительности. Например, когда строки кэша и / или сборка мусора становятся узким местом.
Я бы не назвал использование неизменяемой структуры в этих совершенно правильных сценариях использования «злом».
Я вижу, что синтаксис C # не помогает отличить доступ к члену типа значения или ссылочного типа, поэтому я полностью за предпочитаю неизменяемые структуры, которые обеспечивают неизменность, а не изменяемые структуры.
Однако вместо того, чтобы просто обозначать неизменяемые структуры как «зло», я бы посоветовал принять этот язык и отстаивать более полезные и конструктивные правила большого пальца руки.
Например: "структуры - это типы значений, которые копируются по умолчанию. Вам нужна ссылка, если вы не хотите их копировать" или "попробуйте сначала поработать со структурами только для чтения" < / em>.
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:
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 типов значений, во многих случаях изменяемые типы значений подходят лучше, чем любые другие типы сущностей.
Rectangle
, я могу легко придумать типичную ситуацию, в которой вы получаете крайне неясное поведение. Учтите, что WinForms реализует изменяемый тип Rectangle
, используемый в свойстве формы Bounds
. Если я хочу изменить границы, я бы хотел использовать ваш красивый синтаксис: form.Bounds.X = 10;
Однако это точно ничего в форме не меняет (и генерирует прекрасную ошибку, информирующую вас об этом). Непоследовательность - это проклятие программирования, и именно поэтому требуется неизменность.
- person Ron Warholic; 28.10.2011
Rectangle
на RectangleF
с немного меньшим количеством изменений вряд ли является распространенной ситуацией, и, тем более, все еще требует изменения объявленного типа на любых сайтах объявления. Я думаю, что утверждать, что возможность немного изменить тип проще, стоит потери ясности, ясно показанной в ответах здесь.
- person Ron Warholic; 28.10.2011
Есть еще пара угловых случаев, которые могут привести к непредсказуемому поведению с точки зрения программиста.
// 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);
Итак, изменяемые структуры не являются злом, если вы и остальная часть команды четко понимаете, что делаете. Но существует слишком много угловых случаев, когда поведение программы будет отличаться от ожидаемого, что может привести к возникновению трудноуловимых и трудных для понимания ошибок.
T[]
и целочисленный индекс, и при условии, что доступ к свойству типа ArrayRef<T>
будет интерпретироваться как доступ к соответствующему элементу массива) [если класс хочет предоставить ArrayRef<T>
для какой-либо другой цели, он может предоставить метод - в отличие от свойства - для его получения]. К сожалению, таких положений нет.
- person supercat; 02.06.2012
public static void IncrementI(ref Mutable m) { m.I++; }
, компилятор должен мешать вам делать неправильные вещи в большинстве случаев.
- person springy76; 19.10.2016
Типы значений в основном представляют собой неизменные концепции. Fx, нет смысла иметь математическое значение, такое как целое число, вектор и т. Д., А затем иметь возможность его изменять. Это было бы похоже на переопределение значения ценности. Вместо изменения типа значения имеет смысл присвоить другое уникальное значение. Подумайте о том, что типы значений сравниваются, сравнивая все значения его свойств. Дело в том, что если свойства одинаковы, то это одно и то же универсальное представление этого значения.
Как упоминает Конрад, нет смысла изменять дату, поскольку значение представляет собой этот уникальный момент времени, а не экземпляр объекта времени, который имеет какое-либо состояние или зависимость от контекста.
Надеюсь, это имеет для вас какой-то смысл. Конечно, это больше связано с концепцией, которую вы пытаетесь уловить с помощью типов значений, чем с практическими деталями.
int
итератор, который был бы совершенно бесполезен, если бы он был неизменным. Я думаю, вы объединяете «реализации компилятора / среды выполнения типов значений» с «переменными, типизированными для типа значения» - последний, безусловно, может быть изменен на любое из возможных значений.
- person Slipp D. Thompson; 07.04.2015
Если вы когда-либо программировали на таком языке, как C / C ++, структуры можно использовать как изменяемые. Просто передайте их с помощью ref, around, и ничего не может пойти не так. Единственная проблема, которую я обнаружил, - это ограничения компилятора C # и то, что в некоторых случаях я не могу заставить эту глупость использовать ссылку на структуру вместо копии (например, когда структура является частью класса C # ).
Итак, изменяемые структуры - это не зло, C # сделал их злыми. Я постоянно использую изменяемые структуры в C ++, они очень удобны и интуитивно понятны. Напротив, C # заставил меня полностью отказаться от структур как членов классов из-за того, как они обрабатывают объекты. Их удобство стоило нам нашего.
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, поэтому забываем об использовании двоичных файлов (и, как побочный эффект, об использовании структур). Но да: они существуют и имеют цель.
Они не злые. Просто используйте их по назначению.
Если вы думаете в терминах молотков, вы захотите относиться к винтам как к гвоздям, чтобы обнаружить, что винты труднее вонзить в стену, и это будет вина винта, а они будут плохими.
Представьте, что у вас есть массив из 1 000 000 структур. Каждая структура, представляющая капитал с такими вещами, как bid_price, offer_price (возможно, десятичные дроби) и т. Д., Создается C # / VB.
Представьте, что массив создается в блоке памяти, выделенной в неуправляемой куче, так что какой-то другой поток машинного кода может одновременно обращаться к массиву (возможно, какой-то высокопроизводительный код выполняет математические вычисления).
Представьте, что код C # / VB слушает рыночный поток изменений цен, этому коду может потребоваться доступ к некоторому элементу массива (для любой безопасности), а затем изменить некоторые поля цены.
Представьте, что это делается десятки или даже сотни тысяч раз в секунду.
Что ж, давайте посмотрим правде в глаза, в этом случае мы действительно хотим, чтобы эти структуры были изменяемыми, они должны быть такими, потому что они используются другим нативным кодом, поэтому создание копий не поможет; они должны быть такими, потому что создание копии какой-то 120-байтовой структуры с такой скоростью - безумие, особенно когда обновление может фактически повлиять только на один или два байта.
Хьюго
Когда что-то может быть изменено, оно приобретает чувство идентичности.
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
, содержащим желаемое значение. С типами классов обычно трудно узнать, существует ли несколько ссылок на что-либо, но со структурами это просто: их нет.
- person supercat; 03.10.2013
Thing
является изменяемым типом класса, Thing[]
будет инкапсулировать идентификаторы объектов - хочет кто-то этого или нет - если только никто не может гарантировать, что ни один Thing
в массиве, на который существуют какие-либо внешние ссылки, никогда не будет мутировать. Если кто-то не хочет, чтобы элементы массива инкапсулировали идентичность, обычно необходимо убедиться, что ни один элемент, на который он содержит ссылки, никогда не будет мутирован, или что никакие внешние ссылки никогда не будут существовать ни на какие элементы, которые он хранит [гибридные подходы также могут работать ]. Ни один из подходов не очень удобен. Если Thing
- это структура, Thing[]
инкапсулирует только значения.
- person supercat; 04.10.2013
Это не имеет ничего общего со структурами (и не с C # тоже), но в Java у вас могут возникнуть проблемы с изменяемыми объектами, когда они, например, ключи в хэш-карте. Если вы измените их после добавления на карту, и это изменит его хэш-код, могут случиться злые дела.
У изменяемых данных есть много преимуществ и недостатков. Недостаток в миллион долларов - псевдонимы. Если одно и то же значение используется в нескольких местах, и одно из них меняет его, то будет казаться, что оно волшебным образом изменилось на другие места, которые его используют. Это связано с условиями гонки, но не идентично им.
Преимущество в миллион долларов - иногда модульность. Изменяемое состояние может позволить вам скрыть изменяющуюся информацию от кода, которому не нужно об этом знать.
Искусство переводчика подробно рассматривает эти компромиссы и приводит некоторые примеры.
Лично, когда я смотрю на код, мне кажется, что следующее выглядит довольно неуклюже:
data.value.set (data.value.get () + 1);
а не просто
data.value ++; или data.value = data.value + 1;
Инкапсуляция данных полезна при передаче класса, и вы хотите, чтобы значение изменялось контролируемым образом. Однако, когда у вас есть общедоступные функции set и get, которые делают немного больше, чем устанавливают значение для того, что когда-либо было передано, как это улучшение по сравнению с простой передачей общедоступной структуры данных?
Когда я создаю частную структуру внутри класса, я создал эту структуру, чтобы организовать набор переменных в одну группу. Я хочу иметь возможность изменять эту структуру в рамках класса, а не получать копии этой структуры и создавать новые экземпляры.
Для меня это препятствует допустимому использованию структур, используемых для организации общедоступных переменных, если бы я хотел контроль доступа, я бы использовал класс.
С примером г-на Эрика Липперта есть несколько проблем. Это сделано для того, чтобы проиллюстрировать, что структуры копируются и как это может быть проблемой, если вы не будете осторожны. Глядя на пример, я вижу, что это результат плохой привычки программирования, а не проблема ни со структурой, ни с классом.
Предполагается, что структура имеет только открытые члены и не требует инкапсуляции. Если да, то это действительно должен быть тип / класс. Вам действительно не нужны две конструкции, чтобы сказать одно и то же.
Если у вас есть класс, включающий структуру, вы должны вызвать метод в классе, чтобы изменить структуру члена. Это то, что я сделал бы как хороший программист.
Правильная реализация будет следующей.
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 Нажмите любую кнопку, чтобы продолжить. . .
Я не верю, что они злые, если их правильно использовать. Я бы не стал помещать это в свой производственный код, но я бы хотел что-то вроде макетов структурированного модульного тестирования, где срок жизни структуры относительно невелик.
Используя пример Эрика, возможно, вы захотите создать второй экземпляр этого Эрика, но внесите изменения, так как это характер вашего теста (т. Е. Дублирование, а затем изменение). Не имеет значения, что происходит с первым экземпляром Эрика, если мы просто используем Эрика2 для оставшейся части тестового сценария, если только вы не планируете использовать его в качестве тестового сравнения.
Это было бы в основном полезно для тестирования или изменения устаревшего кода, который неглубоко определяет конкретный объект (точку структур), но, имея неизменяемую структуру, это предотвращает ее использование раздражающим образом.
Range<T>
с полями членов Minimum
и Maximum
типа T
и код Range<double> myRange = foo.getRange();
, любые гарантии относительно того, что содержат Minimum
и Maximum
, должны исходить от foo.GetRange();
. Если Range
будет структурой с открытым полем, это даст понять, что она не собирается добавлять какое-либо собственное поведение.
- person supercat; 19.12.2013
int
,bool
и все другие типы значений являются злом. Есть случаи изменчивости и неизменности. Эти случаи зависят от роли, которую играют данные, а не от типа распределения / совместного использования памяти. - person Slipp D. Thompson   schedule 21.07.2014int
иbool
неизменяемы .. - person Blorgbeard   schedule 05.03.2015.
-синтаксис, благодаря которому операции с данными с типизированными ссылками и данными с типизированными значениями выглядят одинаково, даже если они существенно отличаются. Это ошибка свойств 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.2015ref
в универсальном типе не является двусмысленным; это просто не поддерживается. Это ограничение. И да, C # во многих отношениях заставляет неудобный синтаксис выполнять свою работу. Самый классический пример неуклюжести C # - этоpublic
снова и снова и снова для каждого члена, а не префикс в стиле C ++public:
или отдельныйpublic FieldName1, FieldName2, MethodName1, MethodName2;
в стиле Ruby. - person Slipp D. Thompson   schedule 05.05.2015x
) эта единственная операция состоит из 4 инструкций:ldloc.0
(загружает 0-индексную переменную в ... - person Sushi271   schedule 06.05.2015T
- это тип. Ref - это просто ключевое слово, которое заставляет переменную передаваться самому методу, а не его копию. Это также имеет смысл для ссылочных типов, поскольку мы можем изменить переменную, т.е. ссылка вне метода будет указывать на другой объект после изменения в методе. Посколькуref T
не тип, а способ передачи параметра метода, вы не можете поместить его в<>
, потому что там могут быть помещены только типы. Так что это просто неверно. Может быть, это было бы удобно, возможно, команда C # могла бы сделать это для какой-то новой версии, но сейчас они работают над некоторыми ... - person Sushi271   schedule 06.05.2015