这里有很多误解,既包括问题本身,也包括几个答案。
让我从检查问题的前提开始。问题是“为什么在C#中需要使用new
关键字?”问题的动机是C++中的这个片段:
MyClass object; // this will create object in memory
MyClass* object = new MyClass(); // this does same thing
我有两个批评意见。首先,这两个在C++中并不相同,所以这个问题基于对C++语言的错误理解。非常重要的是要理解在C++中这两个事物的区别,如果你不清楚这个区别,请找一个导师教你如何了解这个区别,并在什么情况下使用每个东西。
其次,这个问题错误地预设了这两个语法在C++中做了相同的事情,然后奇怪地问“为什么我们需要在C#中使用new?”显然,根据这个错误的假设,正确的问题应该是“为什么我们需要在C++中使用new?”如果这两个语法执行相同的操作——而它们并不——那么为什么一开始就有两种语法呢?
因此,这个问题既基于错误的前提条件,关于C#的问题也没有真正从C++的误解设计中得出。
这是一团糟。让我们放弃这个问题,提出一些更好的问题。而且,让我们将关于C#的问题作为C#本身提出,而不是在C++设计决策的背景下提出。
在C#中,当X是类或结构类型时,new X
运算符有什么作用?(为了讨论的目的,我们忽略委托和数组。)
new运算符:
- 导致分配给给定类型的新实例;新的实例将所有字段初始化为默认值。
- 导致执行给定类型的构造函数。
- 如果对象是引用类型,则产生对分配对象的引用,否则产生该值本身。
好了,我已经能听到C#程序员的反对意见了,那么让我们驳回它们。
反对意见:我听到你说,如果类型是值类型,就不会分配新的存储空间。但是,C#规范与你的看法不同。当你说:
S s = new S(123);
对于某些结构类型
S
,规范说明会在短期内存池上分配
新的临时存储,并将其初始化为默认值,构造函数在
this
设置为引用临时存储的情况下运行,然后将生成的对象
复制到
s
。 但是,编译器
允许使用复制省略优化,前提条件是它可以证明,在安全程序中不可能观察到优化。 (练习:计算出不能执行复制省略的情况,并给出一个在省略或不省略时会有不同行为的程序示例。)
异议:您说可以使用
default(S)
生成有效的值类型实例;没有调用构造函数,我听到了您的声音。 这是正确的。我没有说
new
是创建值类型实例的
唯一方法。
事实上,对于值类型
new S()
和
default(S)
是相同的东西。
异议:在C#6中是否真的会执行构造函数,例如
new S()
的情况,如果没有在源代码中存在。 这是一个“如果树倒在森林里而没有人听到它,它会发出声音吗?”问题。调用不执行任何操作的构造函数和根本不调用之间有区别吗?这不是一个有趣的问题。编译器可以省略它知道不会执行任何操作的调用。
假设我们有一个值类型的变量。我们必须使用
new
创建的实例初始化变量吗?
不需要。自动初始化的变量(例如字段和数组元素)将被初始化为默认值-即,结构中所有字段本身都是它们的默认值。
形式参数显然将用参数初始化。
值类型的局部变量需要在读取字段之前明确分配
某些内容,但它不必是
new
表达式。
因此,在实际上,值类型的变量会自动初始化为
default(S)
的等价物,除非它们是局部变量?
是的。
为什么不对局部变量进行相同的处理?
未初始化的局部变量的使用与错误的代码密切相关。 C#语言禁止这样做,因为这样做会发现错误。
假设我们有一个引用类型的变量。我们必须使用
new
创建的实例初始化
S
吗?
不会。自动初始化的变量将使用null进行初始化。局部变量可以用任何引用类型进行初始化,包括null,并且在读取之前必须明确定义。
因此,引用类型的变量实际上会自动初始化为null,除非它们是局部变量?
是的。
为什么不对局部变量做同样的事情呢?
同样的原因。这可能会导致错误。
为什么不通过自动调用默认构造函数来自动初始化引用类型的变量?也就是说,为什么不让R r;
和R r = new R();
一样?
首先,许多类型没有默认构造函数,或者干脆没有任何可访问的构造函数。其次,对于未初始化的局部变量或字段有一个规则,对于形式参数有另一个规则,对于数组元素有另一个规则,这似乎很奇怪。第三,现有的规则非常简单:变量必须被初始化为一个值;该值可以是任何你喜欢的值,为什么认为需要新的实例?如果这样做,那就太奇怪了。
R r;
if (x) r = M(); else r = N();
导致构造函数运行以初始化 r
。
暂且不谈 new
运算符的语义,为什么必须从语法上使用这种运算符呢?
其实不必要。有许多其他的语法形式也可以合法地表示同样的意思。最显然的方法是完全取消 new
。如果我们有一个类 C
,其中有一个构造函数 C(int)
,那么我们可以直接使用 C(123)
而不是 new C(123)
。或者我们可以使用类似于 C.construct(123)
这样的语法。有许多种方法可以在没有 new
运算符的情况下完成这个操作。
那么为什么还要使用它呢?
首先,C# 的设计初衷是使熟悉使用 new
来表示正在初始化对象的新存储空间的 C++、Java、JavaScript 和其他语言的用户能够立即感到熟悉。
其次,恰当的语法冗余度非常可取。对象的创建是特殊的;我们希望用它自己的运算符来标识它的发生。
Car x; x.j = 3; Console.WriteLine(x.i);
。 - user253751