为什么没有new关键字可以实例化一个struct?

Bir*_*man 21 .net c# struct instantiation

为什么我们没有被强制实例化一个结构,比如在使用类时?

Eri*_*ert 49

为什么我们没有被迫用"new"实例化一个结构,就像使用类一样?

当你"新"引用类型时,会发生三件事.首先,内存管理器从长期存储中分配空间.其次,对该空间的引用被传递给构造函数,该构造函数初始化实例.第三,该引用被传递回调用者.

当你"新"一个值类型时,会发生三件事.首先,内存管理器从短期存储中分配空间.其次,构造函数传递对短期存储位置的引用.构造函数运行后,短期存储位置中的值将被复制到值的存储位置,无论发生在何处.请记住,值类型的变量存储实际值.

(请注意,如果编译器可以确定这样做从未将部分构造的结构体暴露给用户代码,则允许编译器将这三个步骤优化为一步.也就是说,编译器可以生成简单地将引用传递给最终结构的代码存储位置到构造函数,从而节省一个分配和一个副本.)

所以现在我们可以解决你实际上向后提出的问题.最好问:

为什么我们被迫用"new"分配一个类,而不是简单地像结构一样初始化字段?

由于列表中的这三个东西,您必须使用"new"分配一个类.您需要从长期存储分配新内存,并且需要将对该存储的引用传递给构造函数."新"是知道如何做到这一点的运营商.

您不必在结构上调用"new",因为不需要分配"最终"存储; 最终存储已经存在.新值将会出现在某处,您已经通过其他方式获得了该存储.值类型不需要新的分配; 他们只需要初始化.您需要做的就是确保存储已正确初始化,并且您通常可以在不调用构造函数的情况下执行此操作.这样做当然意味着您冒着使用值类型变量的风险,可以通过用户代码观察到该变量值处于部分初始化状态.

总结:调用ctor对于值类型是可选的,因为在初始化值类型的实例时不需要分配新内存,并且跳过构造函数调用意味着您可以跳过短期分配和副本.您为该性能增益支付的价格是用户代码可以看到部分初始化的结构.

  • @JamesPoulose:C#或CLR的实现不需要*使用堆进行长期存储,也不需要使用堆栈进行短期存储.例如,一些短期存储存储在寄存器中; 寄存器既不是堆也不是堆栈.你为什么要做这个假设?更重要的是:如果违反了这个假设,你计划编写哪些代码*错误*? (4认同)

Mar*_*ell 17

为什么很简单- 因为规范是这么说的.的如何是确保的存储器的整个块是"在分配的",这意味着一个问题:将值分配给该结构的每个字段.但是,这需要2件令人讨厌的事情:

  • 公共领域(几乎总是坏的)
  • 可变字段(通常在结构中不好)

所以在大多数的最佳实践案例,你需要使用new(...)正确的语法,调用构造函数(或零内存,为参数的构造函数)的类型.

  • 不管您是否调用构造函数,内存都会自动归零。根据定义,“ new StructType()”与“ default(StructType)”相同。并不是真正的原因是“因为规格如此”。从规格中获取的重要信息是内存会自动归零。 (3认同)

Hen*_*man 11

因为struct是值类型.当你声明它的变量时,实例就是那里的.

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
}
Run Code Online (Sandbox Code Playgroud)

  • 好吧,只有*部分*那里; 如果你添加`Console.WriteLine(ax);`***高于***'ax = 1;`行,它将无法编译. (2认同)

Dav*_*nan 10

因为结构是值类型而类是引用类型.因此结构与int,double等属于同一类别.


Zen*_*xer 5

一年半后就来了...

Astruct通常按值传递,而 a始终按引用传递class。您可能很清楚通过引用传递对象时会发生什么。当对象按值传递时,传递的是其内容,而不是对该对象的引用。对于程序员来说,就好像创建了对象的浅表副本一样。更改一个实例不会更改另一个实例。

所有变量(包括字段和属性)只要存在,就始终会为其分配空间。请务必注意,在较新版本的 C# 中为局部变量赋值之前,局部变量并不存在。对于class- 类型变量,分配的空间将包含对对象内容的引用。对于struct- 类型变量,分配的空间将包含对象的实际内容。

因此,假设您有一个“空”class类型变量。它将有一个默认参考。该引用将等同于null. 但是,struct-type 变量不是引用:它是对象的实际内容。当保留“空”时,其所有字段(以及自动实现的属性,由幕后字段支持)都包含默认值 - 换句话说,它们也是“空”。如果它们是引用类型,则它们将是null;如果它们是值类型,则它们将为 0,或归零结构(并且链继续)。

这也是为什么 astructs不能有默认构造函数的原因。正如您无法覆盖 aclass为 时的样子一样null,您也无法覆盖 astruct归零时的样子。

有一个未充分使用的运算符用于获取任何类型的默认值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;
}
Run Code Online (Sandbox Code Playgroud)