Структуры с открытыми изменяемыми полями или свойствами не являются злыми.
Методы структуры (в отличие от установщиков свойств), которые видоизменяют «это», являются несколько злыми, только потому, что .net не предоставляет средства отличить их от методов, которые этого не делают. Методы структуры, которые не изменяют «это», должны вызываться даже в структурах только для чтения без необходимости защитного копирования. Методы, которые изменяют "this", вообще не должны вызываться в структурах только для чтения. Поскольку .net не хочет запрещать использование структурных методов, которые не изменяют «this», для структур, доступных только для чтения, но не хочет, чтобы структуры, доступные только для чтения, были изменены, он защищенно копирует структуры в режиме чтения. только контексты, возможно, получая худшее из обоих миров.
Несмотря на проблемы с обработкой самопереключающихся методов в контекстах только для чтения, изменяемые структуры часто предлагают семантику, намного превосходящую изменяемые типы классов. Рассмотрим следующие три сигнатуры метода:
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);
Для каждого метода ответьте на следующие вопросы:
- Предполагая, что метод не использует "небезопасный" код, может ли он изменить foo?
- Если до вызова метода не существует внешних ссылок на 'foo', может ли существовать внешняя ссылка после?
Ответы:
Method1 не может изменить foo и никогда не получает ссылку. Method2 получает кратковременную ссылку на foo, которую он может использовать для изменения полей foo любое количество раз в любом порядке, пока не вернется, но он не может сохранить эту ссылку. Перед возвратом Method2, если он не использует небезопасный код, все копии, которые могли быть сделаны из его ссылки 'foo', исчезнут. Метод 3, в отличие от метода 2, получает беспорядочную ссылку на 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 для типов значений, во многих случаях изменяемые типы значений подходят лучше, чем любые другие типы объектов.