Общее правило, которое следует соблюдать, состоит в том, что структуры должны быть небольшими, простыми (одноуровневыми) коллекциями связанных свойств, которые являются неизменяемыми после создания; для всего остального используйте класс.
C # хорош тем, что структуры и классы не имеют явных различий в объявлении, кроме ключевого слова определения; поэтому, если вы чувствуете, что вам необходимо «обновить» структуру до класса или, наоборот, «понизить» класс до структуры, это в основном простой вопрос изменения ключевого слова (есть несколько других ошибок; структуры не могут быть получены из любого другого класса или типа структуры, и они не могут явно определить конструктор по умолчанию без параметров).
Я говорю «в основном», потому что более важная вещь, которую нужно знать о структурах, заключается в том, что, поскольку они являются типами значений, их обращение с классами (ссылочными типами) может закончиться болью и половиной. В частности, изменение свойств структуры может вызвать неожиданное поведение.
Например, скажем, у вас есть класс SimpleClass с двумя свойствами, A и B. Вы создаете копию этого класса, инициализируете A и B, а затем передаете экземпляр другому методу. Этот метод дополнительно изменяет A и B. Возвращаясь к вызывающей функции (той, которая создала экземпляр), A и B вашего экземпляра будут иметь значения, данные им вызываемым методом.
Теперь вы делаете это структурой. Свойства все еще изменчивы. Вы выполняете те же операции с тем же синтаксисом, что и раньше, но теперь новые значения A и B отсутствуют в экземпляре после вызова метода. Что случилось? Ну, теперь ваш класс - это структура, то есть тип значения. Если вы передаете тип значения методу, по умолчанию (без ключевого слова out или ref) передается «по значению»; поверхностная копия экземпляра создается для использования методом, а затем уничтожается, когда метод завершается, оставляя исходный экземпляр без изменений.
Это становится еще более запутанным, если вы должны иметь ссылочный тип в качестве члена вашей структуры (не запрещено, но крайне плохая практика практически во всех случаях); класс не будет клонирован (только ссылка на структуру), поэтому изменения в структуре не будут влиять на исходный объект, но изменения в подклассе структуры будут влиять на экземпляр из вызывающего кода. Это может очень легко привести изменчивые структуры в очень противоречивые состояния, которые могут привести к ошибкам далеко от того места, где находится настоящая проблема.
По этой причине практически каждый авторитет в C # говорит, что всегда нужно делать ваши структуры неизменяемыми; позволяют потребителю указывать значения свойств только при построении объекта и никогда не предоставляют никаких средств для изменения значений этого экземпляра. Поля только для чтения, или свойства только для получения, являются правилом. Если потребитель хочет изменить значение, он может создать новый объект на основе значений старого, с желаемыми изменениями, или он может вызвать метод, который будет делать то же самое. Это заставляет их рассматривать один экземпляр вашей структуры как одну концептуальную «ценность», неделимую и отличную от других (но, возможно, приравненную к ней). Если они выполняют операцию над «значением», сохраненным вашим типом, они получают новое «значение», которое отличается от их начального значения,
Для хорошего примера посмотрите на тип DateTime. Вы не можете назначить любое из полей экземпляра DateTime напрямую; Вы должны либо создать новый, либо вызвать метод для существующего, который создаст новый экземпляр. Это потому, что дата и время являются «значением», таким как число 5, а изменение числа 5 приводит к новому значению, которое не равно 5. Просто потому, что 5 + 1 = 6 не означает, что 5 теперь 6 потому что вы добавили 1 к нему. DateTimes работают так же; 12:00 не «становится» 12:01, если вы добавите минуту, вместо этого вы получите новое значение 12:01, отличное от 12:00. Если для вашего типа это логичное положение вещей (хорошими концептуальными примерами, которые не встроены в .NET, являются «Деньги», «Расстояние», «Вес» и другие объемы UOM, где операции должны учитывать все части значения), затем используйте структуру и спроектируйте ее соответственно. В большинстве других случаев, когда подпункты объекта должны быть независимо изменяемыми, используйте класс.