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

问题描述 投票:21回答:7

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

c# .net struct instantiation
7个回答
17
投票

原因很简单 - 因为规范是这么说的。如何确保整个内存块“明确分配”,这意味着:为结构的每个字段赋值。但是,这需要2件令人讨厌的事情:

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

因此,在大多数最佳实践案例中,您需要使用new(...)语法,以正确地调用类型的构造函数(或零 - 内存,对于无参数构造函数)。


48
投票

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

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

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

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

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

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

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

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

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


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
}

10
投票

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


2
投票

一年半之后......

struct通常按值传递,而class总是通过引用传递。您可能很好地理解了通过引用传递对象时会发生什么。当一个对象按值传递时,它的内容而不是对象的引用将被传递。对于程序员来说,好像是对象的浅表副本。更改一个实例不会改变另一个实例。

只要它们存在,所有变量(包括字段和属性)总是为它们分配空间。重要的是要注意,在较新版本的C#中为其分配值之前,本地变量不存在。对于class类型变量,分配的空间将包含对对象内容的引用。对于struct类型变量,分配的空间将包含对象的实际内容。

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

这也是structs不能拥有默认构造函数的原因。正如你无法覆盖classnull时的样子,你无法覆盖struct归零时的样子。

有一个未充分利用的运算符用于获取任何类型的默认值 - classstruct或intrinsic。那是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
投票

正如David Heffernan和Henk Holterman所说,因为结构是值类型,因此在声明它时会实例化。为了更好地理解ValueType和ReferenceType,请检查this link P Daddy已经很好地解释了它。


0
投票

除了发布的内容:请注意,结构不能具有无参数构造函数或者具有任何实例字段的初始化程序。默认值是将所有值类型字段设置为其默认值(例如,对于int为0,对于bool为false),并且所有引用类型字段为null。

其次,初始化结构,例如通过调用构造函数或使用default()

© www.soinside.com 2019 - 2024. All rights reserved.