СоХабр закрыт.
С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.
С этой статьей я продолжаю публиковать целую серию статей, результатом которой будет книга по работе .NET CLR, и .NET в целом. Тема IDisposable была выбрана в качестве разгона, пробы пера. Теперь коснемся разныцы между типами. Вся книга будет доступна на GitHub: DotNetBook. Так что Issues и Pull Requests приветствуются :)
Давайте подумаем об особенностях обоих типов, об их достоинствах и недостатках и решим, где ими лучше пользоваться. Тут, конечно же стоит вспомнить классиков, дающих утверждение что выбор в сторону значимых типов, стоит дать если у нас тип не планирует быть наследуемым, он не станет меняться в течении своей жизни, а его размер не превышает 16 байт. Но не все так очевидно. Чтобы сделать полноценное сравнение нам необходимо задуматься о выборе типа с разных сторон, мысленно продумав сценарии его будущего использования. Разделить критерии выбора я предлагаю на три группы:
Каждая сущность, которая проектируется вами должна в полной мере отражать ее назначение. И это касается не только её названия или интерфейса взаимодействия (методы, свойства), но даже выбор между значимым и ссылочным типом может быть сделан из архитектурных соображений. Давайте порассуждаем, почему с точки зрения архитектуры системы типов может быть выбрана структура, а не класс:
Если наш проектируемый тип будет обладать инвариантностью по отношению к смысловой нагрузке своего состояния, то это будет значить что его состояние полностью отражает некоторый процесс или является значением чего-либо. Другими словами, экземпляр типа полностью константен и не может быть изменен по своей сути. Мы можем создать на основе этой константы другой экземпляр типа, указав некоторое смещение, либо создать с нуля, указав его свойства. Но изменять его мы не имеем права. Я прошу заметить, что я не имею ввиду что структура является неизменяемым типом. Вы можете менять поля, как хотите. Мало того вы можете отдать ссылку на структуру в метод через ref
параметр и получить измененные поля по выходу из метода. Однако, я про смысл с точки зрания архитектуры. Поясню на примерах:
uint
, однако предоставляет доступ к отдельным характеристикам момента времени. Например: год, месяц, день, час, минуты, секунды, миллисекунды и даже процессорные тики. Однако исходя из того что она инкапсулирует — она не может быть изменяемой по своей природе. Мы не можем изменить конкретный момент времени чтобы он стал другим. Я не могу прожить следующую минуту своей жизни в лучший день рождения своего детства. Время неизменно. Именно поэтому выбор для типа данных может стать либо класс с readonly интерфейсом взаимодействия (который на каждое изменение свойств отдает новый экземпляр) либо структура, которая несмотря на возможность изменения полей своих экземпляров делать этого не должна: описание момента времени является значением. Как число. Вы же не можете залезть в структру числа и поменять его? Если вы хотите получить другой момент времени, который является смещением относительно оригинального на один день, вы просто получаете новый экземпляр структуры;Если наш проектируемый тип является неотъемлимой частью внешнего типа. Но при этом он структурно неотъемлим. Т.е. было бы некорректным сказать, что внешний тип ссылается на экземпляр инкапсулируемого, но совершенно корректно — что инкапсулируемый является полноправной частью внешнего вместе со всеми своими свойствами. Как правило это используется при проектировании структур, которые являются частью другой структуры.
header.txt
. Это было бы уместно при вставке документа в некий другой, но не вживанием файла, а по относительной ссылке на файловой системе. Хороший пример — файл ярлыка ОС Windows. Однако если мы говорим о заголовке файла (например, о заголовке JPEG файла, в котором указаны размер изображения, методика сжатия, параметры съемки, коодинаты GPS и прочая метаинформация), то при проектировании типов, которые будут использованы для парсинга заголовка будет крайне полезно использовать структуры. Ведь, описав все заголовки в структурах вы получите в памяти абсолютно такое же положение всех полей как в файле. И через простое unsafe преобразование *(Header *)readedBuffer
без каких-либо десериализаций — полностью заполненные структуры данных.
комментарии (17)