为什么我们没有被强制实例化一个结构,比如在使用类时?
答案 0 :(得分:48)
为什么我们没有被强制用“new”实例化一个结构,就像使用类一样?
当你“新”引用类型时,会发生三件事。首先,内存管理器从长期存储分配空间。其次,对该空间的引用被传递给构造函数,该构造函数初始化实例。第三,该引用被传递回调用者。
当你“新”一个值类型时,会发生三件事。首先,内存管理器从短期存储分配空间。其次,构造函数传递对短期存储位置的引用。构造函数运行后,短期存储位置中的值将被复制到值的存储位置,无论发生在何处。请记住,值类型的变量存储实际值。
(请注意,如果编译器可以确定这样做从未将部分构造的结构暴露给用户代码,则允许编译器将这三个步骤优化为一步。也就是说,编译器可以生成只传递引用的代码到最终存储位置到构造函数,从而节省了一个分配和一个副本。)
所以现在我们可以解决您实际上向后提出的问题。最好问:
为什么我们被迫用“new”分配一个类,而不是像结构一样初始化字段?
由于列表中的这三项内容,您必须使用“new”分配一个类。您需要从长期存储分配新内存,并且需要将对该存储的引用传递给构造函数。 “新”是知道如何做到这一点的运营商。
您不必在结构上调用“new”,因为不需要分配“最终”存储; 最终存储已存在。新值将某处,并且您已经通过其他方式获得了该存储。 值类型不需要新的分配;他们只需要初始化。您需要做的就是确保存储正确初始化,并且您通常可以在不调用构造函数的情况下执行此操作。 这样做当然意味着您冒着使用值类型变量的风险,可以通过用户代码观察到它处于部分初始化状态。
总结:调用ctor对于值类型是可选的,因为在初始化值类型的实例时不需要分配新内存,并且因为跳过构造函数调用意味着你得到跳过短期分配和副本。您为该性能增益支付的价格是用户代码可以看到部分初始化的结构。
答案 1 :(得分:16)
为什么只是 - 因为规范如此。 如何是确保整个内存块“明确分配”的问题,这意味着:为结构的每个字段分配一个值。然而,这需要2件令人讨厌的事情:
所以在大多数最佳实践案例中,你做需要使用new(...)
语法来调用构造函数(或者将内存归零,对于无参数构造函数)正确的类型。
答案 2 :(得分: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
}
答案 3 :(得分:10)
因为结构是值类型而类是引用类型。因此结构与int,double等属于同一类别。
答案 4 :(得分:1)
一年半之后......
struct
通常按值传递,而class
始终通过引用传递。您可能很好地理解了通过引用传递对象时会发生什么。当一个对象按值传递时,它的内容而不是对象本身会被传递。对于程序员来说,好像是对象的浅表副本。更改一个实例不会改变另一个实例。
所有变量(包括字段和属性)只要存在,就始终为它们分配空间。重要的是要注意,在较新版本的C#中为其分配值之前, local 变量不存在。对于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);
}
或内在值。那是{{1}}运算符。例如:
{{1}}
答案 5 :(得分:0)
正如 David Heffernan 和 Henk Holterman 所说,因为结构是值类型,因此在声明它时进行实例化。为了更好地了解ValueType和ReferenceType,请查看this link P Daddy 已经很好地解释了它。
答案 6 :(得分:0)
除了发布的内容之外:请注意,结构体不能具有无参数构造函数或者具有任何实例字段的初始化程序。默认值是将所有值类型字段设置为其默认值(例如,对于int为0,对于bool为false),并且所有引用类型字段为null。
其次,初始化结构,例如通过调用构造函数或使用default()
。