Почему можно создать экземпляр структуры без ключевого слова new? - PullRequest
21 голосов
/ 14 октября 2011

Почему мы не обязаны создавать экземпляры структуры, как при использовании класса?

Ответы [ 7 ]

48 голосов
/ 14 октября 2011

Почему мы не обязаны создавать экземпляр структуры с "новым", как при использовании класса?

Когда вы «новый» тип ссылки, происходят три вещи. Во-первых, диспетчер памяти выделяет пространство из долгосрочного хранилища. Во-вторых, ссылка на это пространство передается конструктору, который инициализирует экземпляр. В-третьих, эта ссылка передается вызывающей стороне.

Когда вы «новый» тип значения, происходят три вещи. Во-первых, диспетчер памяти выделяет пространство из краткосрочного хранилища. Во-вторых, конструктору передается ссылка на место кратковременного хранения. После запуска конструктора значение, которое находилось в кратковременном хранилище, копируется в хранилище для значения, где бы оно ни находилось. Помните, что переменные типа значения хранят фактическое значение .

(Обратите внимание, что компилятору разрешено оптимизировать эти три шага за один шаг, если компилятор может определить, что при этом никогда не подвергается частично построенная структура пользовательскому коду. То есть компилятор может генерировать код, который просто передает ссылку в хранилище final для конструктора, тем самым сохраняя одно выделение и одну копию.)

Так что теперь мы можем ответить на ваш вопрос, который вы на самом деле задавали задом наперед. Было бы лучше спросить:

Почему мы вынуждены размещать класс с "новым", вместо того, чтобы просто инициализировать поля как со структурой?

Вы должны назначить класс "новым" из-за этих трех вещей в списке. Вам нужно новой памяти, выделенной из долговременного хранилища , и вам нужно передать ссылку на это хранилище в конструктор. «new» - это оператор, который знает, как это сделать.

Вам не нужно вызывать «new» в структуре, потому что нет необходимости выделять «окончательное» хранилище; окончательное хранилище уже существует . Новое значение будет куда-то идти * , и вы уже получили это хранилище другими способами. Типам значений не требуется новое распределение; все, что им нужно, это инициализация. Все, что вам нужно сделать, это убедиться, что хранилище правильно инициализировано , и вы часто можете сделать это без вызова конструктора. Конечно, это означает, что вы рискуете иметь переменную типа значения, которая может быть частично инициализирована с помощью кода пользователя.

Подводя итог: вызов ctor необязателен для типов значений, потому что не требуется выделять новую память при инициализации экземпляра типа значения , а поскольку пропуск вызова конструктора означает, что вы получаете пропустить краткосрочное выделение и копию . Цена, которую вы платите за это повышение производительности, заключается в том, что пользовательский код может видеть частично инициализированную структуру.

17 голосов
/ 14 октября 2011

, почему это просто - , потому что спецификация так говорит . how заключается в том, чтобы гарантировать, что весь блок памяти «определенно назначен», что означает: присвоение значения каждому полю структуры.Тем не менее, это требует 2 неприятных вещей:

  • открытые поля (почти всегда плохие)
  • изменяемые поля (как правило, плохо в структуре)

так в большинстве примеров наилучшей практики , вам do необходимо использовать синтаксис new(...), чтобы вызвать конструктор (или обнулить память для конструктора без параметров) для типа правильно.

11 голосов
/ 14 октября 2011

Поскольку структура является типом значения. Когда вы объявляете ее переменную, экземпляр тут же появляется.

Конструктор (оператор new) поэтому является необязательным для структуры.

Рассмотрим

struct V { public int x; }
class  R { public int y = 0; }

void F() 
{
   V a;   // a is an instance of V, a.x is unassigned  
   R b;   // b is a reference to an R

   a.x = 1; // OK, the instance exists
 //b.y = 2; // error, there is no instance yet

   a = new V();  // overwrites the memory of 'a'. a.x == 0
   b = new R();  // allocates new memory on the Heap

   b.y = 2; // now this is OK, b points to an instance
}
10 голосов
/ 14 октября 2011

Поскольку структуры являются типами значений, а классы являются ссылочными типами.Таким образом, структуры попадают в ту же категорию, что и int, double и т. Д.

2 голосов
/ 24 апреля 2013

Через полтора года ...

A struct - это , обычно , передаваемое по значению, а class - , всегда , передаваемое по ссылке. Вы, вероятно, хорошо понимаете, что происходит, когда объект передается по ссылке. Когда объект передается по значению, его содержимое, а не ссылка на объект, передается. Программисту кажется, что сделана мелкая копия объекта. Изменение одного экземпляра не изменит другого.

Всем переменным (включая поля и свойства) всегда выделяется пространство для них, пока они существуют. Важно отметить, что локальные переменные не существуют до тех пор, пока им не будет присвоено значение в более новых версиях C #. В случае переменных типа class выделенное пространство будет содержать ссылку на содержимое объекта. В случае переменной типа struct выделенное пространство будет содержать фактическое содержимое объекта.

Итак, допустим, у вас есть «пустая» переменная типа class. Он будет иметь ссылку по умолчанию. Эта ссылка будет равна null. Однако переменная struct не является ссылкой: это фактическое содержимое объекта. Когда оставлено «пустым», все его поля (и автоматически реализуемые свойства, которые подкреплены полями за кулисами) содержат значения по умолчанию - другими словами, они также являются «пустыми». Если они являются ссылочными типами, они будут null; если они являются типами значений, они будут 0 или обнуляющими структурами (и цепочка продолжается).

Именно поэтому structs не может иметь конструктор по умолчанию. Точно так же, как вы не можете переопределить, как выглядит class, когда он равен null, вы не можете переопределить, как выглядит struct, когда он обнуляется.

Существует недостаточно используемый оператор для получения значения по умолчанию любого типа - class, struct или встроенного. Это оператор default(). Например:

class ClassType { }
struct StructType { }

//
// ...
//

var classA = default(ClassType);
var classB = (ClassType)null;

if (classA == classB)
{
    // This will execute, because both equal null.
}

var structA = default(StructType);
var structB = new StructType();

if (structA == structB)
{
    // This will execute, because both are zeroed.
}

//
// ...
//

/// <summary>
/// An example use case for the <c>default()</c> operator.
/// </summary>
/// <returns>
/// <c>null</c> if <c>T</c> is a reference type, a zeroed instance <c>T</c> is a 
/// <c>struct</c>, or <c>0</c> if <c>T</c> is an intrinsic type.
/// </returns>
private static T GetDefault<T>()
{
    // This line wouldn't compile, because T could be a value type.
    //return null;

    // This line wouldn't compile, because T could be a reference type without a default or accessible constructor.
    //return new T();

    // This will work!
    return default(T);

    // In newer versions of C#, when the type is known from the context, it can be omitted:
    //return default;
}
0 голосов
/ 14 октября 2011

В дополнение к тому, что было опубликовано: обратите внимание, что структура не может иметь конструктор без параметров или иметь инициализатор для любого из своих полей экземпляра.В значении по умолчанию для всех полей типа значения установлено значение по умолчанию (например, 0 для целых чисел, false для bool и т. Д.), А для всех полей ссылочного типа - ноль.

Во-вторых, структура инициализируется дляНапример, вызывая конструктор или используя default().

0 голосов
/ 14 октября 2011

Как сказали Дэвид Хеффернан и Хенк Холтерман , потому что структуры являются типами значений и, следовательно, создаются при его объявлении.Для лучшего понимания ValueType и ReferenceType, пожалуйста, проверьте эта ссылка P Папа хорошо объяснил это.

...