Почему мы не обязаны создавать экземпляр структуры с "новым", как при использовании класса?
Когда вы «новый» тип ссылки, происходят три вещи. Во-первых, диспетчер памяти выделяет пространство из долгосрочного хранилища. Во-вторых, ссылка на это пространство передается конструктору, который инициализирует экземпляр. В-третьих, эта ссылка передается вызывающей стороне.
Когда вы «новый» тип значения, происходят три вещи. Во-первых, диспетчер памяти выделяет пространство из краткосрочного хранилища. Во-вторых, конструктору передается ссылка на место кратковременного хранения. После запуска конструктора значение, которое находилось в кратковременном хранилище, копируется в хранилище для значения, где бы оно ни находилось. Помните, что переменные типа значения хранят фактическое значение .
(Обратите внимание, что компилятору разрешено оптимизировать эти три шага за один шаг, если компилятор может определить, что при этом никогда не подвергается частично построенная структура пользовательскому коду. То есть компилятор может генерировать код, который просто передает ссылку в хранилище final для конструктора, тем самым сохраняя одно выделение и одну копию.)
Так что теперь мы можем ответить на ваш вопрос, который вы на самом деле задавали задом наперед. Было бы лучше спросить:
Почему мы вынуждены размещать класс с "новым", вместо того, чтобы просто инициализировать поля как со структурой?
Вы должны назначить класс "новым" из-за этих трех вещей в списке. Вам нужно новой памяти, выделенной из долговременного хранилища , и вам нужно передать ссылку на это хранилище в конструктор. «new» - это оператор, который знает, как это сделать.
Вам не нужно вызывать «new» в структуре, потому что нет необходимости выделять «окончательное» хранилище; окончательное хранилище уже существует . Новое значение будет куда-то идти * , и вы уже получили это хранилище другими способами. Типам значений не требуется новое распределение; все, что им нужно, это инициализация. Все, что вам нужно сделать, это убедиться, что хранилище правильно инициализировано , и вы часто можете сделать это без вызова конструктора. Конечно, это означает, что вы рискуете иметь переменную типа значения, которая может быть частично инициализирована с помощью кода пользователя.
Подводя итог: вызов ctor необязателен для типов значений, потому что не требуется выделять новую память при инициализации экземпляра типа значения , а поскольку пропуск вызова конструктора означает, что вы получаете пропустить краткосрочное выделение и копию . Цена, которую вы платите за это повышение производительности, заключается в том, что пользовательский код может видеть частично инициализированную структуру.