我不明白为什么我们需要'new'关键字

use*_*398 36 c# language-design

我是C#的新手,来自C++背景.在C++中,您可以这样做:

class MyClass{
....
};
int main()
{
   MyClass object; // this will create object in memory
   MyClass* object = new MyClass(); // this does same thing
}
Run Code Online (Sandbox Code Playgroud)

而在C#中:

class Program
{
    static void Main(string[] args)
    {
        Car x;
        x.i = 2;
        x.j = 3;
        Console.WriteLine(x.i);
        Console.ReadLine();

    }
}
class Car
{
    public int i;
    public int j;


}
Run Code Online (Sandbox Code Playgroud)

你不能这样做.我想知道为什么Car x不做它的工作.

Eri*_*ert 47

这里存在很多误解,无论是在问题本身还是在几个答案中.

让我首先研究一下这个问题的前提.问题是"为什么我们需要newC#中的关键字?" 问题的动机是C++的这个片段:

 MyClass object; // this will create object in memory
 MyClass* object = new MyClass(); // this does same thing
Run Code Online (Sandbox Code Playgroud)

我基于两个理由批评这个问题.

首先,这些在C++中没有做同样的事情,所以问题是基于对C++语言的错误理解.理解C++中这两件事之间的区别非常重要的,所以如果你不清楚什么是差异,找一个可以教你如何知道差异是什么以及何时使用每个东西的导师.

其次,问题预设 - 错误地 - 这两个语法在C++中做同样的事情,然后,奇怪的是,问"为什么我们需要new在C#中?" 当然,在这个问题上提出正确的问题 - 再次,错误 - 预设是"为什么我们需要new用C++?" 如果这两个语法做同样的事情 - 他们没有 - 那么为什么首先有两个语法?

所以这个问题都是基于错误的前提,而关于C#的问题实际上并没有遵循 - 误解 - C++的设计.

这是一团糟.让我们抛出这个问题并提出一些更好的问题.让我们问一下关于C#qua C#的问题,而不是在C++的设计决策环境中.

new X运算符在C#中做了什么,其中X是类或结构类型?(为了讨论的目的,让我们忽略代理和数组.)

新运营商:

  • 导致分配给定类型的新实例; 新实例将其所有字段初始化为默认值.
  • 导致执行给定类型的构造函数.
  • 如果对象是引用类型,则生成对已分配对象的引用 ;如果对象是类型,则生成对值本身的引用.

好吧,我已经可以听到C#程序员的反对意见,所以让我们解雇他们.

异议:如果类型是值类型,则不会分配新的存储空间,我听到你说.好吧,C#规范不同意你的看法.当你说

S s = new S(123);
Run Code Online (Sandbox Code Playgroud)

对于某些结构类型S,规范说新的临时存储在短期池上分配,初始化为其默认值,构造函数运行时使用thisset来引用临时存储,然后将生成的对象复制s.但是,允许编译器使用复制省略优化,前提是它可以证明在安全程序中无法观察到优化.(练习:在什么情况下不能执行复制省略;如果有或没有使用elision,请举例说明会有不同行为的程序.)

异议:可以使用以下方式生成值类型的有效实例default(S); 没有构造函数被调用,我听你说.那是对的.我没有说这new是创建值类型实例的唯一方法.

事实上,对于价值型new S()default(S)有同样的事情.

异议:new S()我听到你说,如果构造函数真的被执行过,如果不存在于C#6的源代码中.这是"如果一棵树落在森林里,没有人听到它,它会发出声音吗?" 题.对没有做任何事情的构造函数的调用与根本没有调用之间是否有区别?这不是一个有趣的问题.编译器可以自由地忽略它知道什么都不做的调用.

假设我们有一个值类型的变量.我们必须使用由new?生成的实例初始化变量吗?

否.自动初始化的变量(如字段和数组元素)将初始化为默认值 - 即结构的值,其中所有字段本身都是其默认值.

显然,正式参数将使用参数初始化.

需要值类型的局部变量与被明确分配的东西被读领域之前,但它不一定是一个new表达式.

如此有效,值类型的变量会自动初始化,相当于default(S),除非它们是本地人?

是.

为什么不为当地人做同样的事呢?

使用未初始化的本地与错误代码密切相关.C#语言不允许这样做,因为这样做会发现错误.

假设我们有一个引用类型的变量.我们必须S使用由...生成的实例进行初始化new吗?

不会.自动初始化变量将使用null初始化.可以使用任何引用初始化本地,包括null,并且必须在读取之前明确分配.

如此有效,引用类型的变量会自动初始化null,除非它们是本地人?

是.

为什么不为当地人做同样的事呢?

同样的道理.一个可能的错误.

为什么不通过自动调用默认构造函数自动初始化引用类型的变量?也就是说,为什么不做R r;同样的R r = new R();

嗯,首先,许多类型没有默认构造函数,或者就此而言,根本没有任何可访问的构造函数.其次,对于未初始化的本地或字段有一个规则,对于正式的另一个规则,以及对于数组元素的另一个规则,似乎很奇怪.第三,现有规则非常简单:必须将变量初始化为一个值; 这个价值可以是你喜欢的任何东西; 为什么假设需要一个新实例?如果这样,那将是奇怪的

R r;
if (x) r = M(); else r = N();
Run Code Online (Sandbox Code Playgroud)

导致构造函数运行以初始化r.

撇开语义中的new运营商,为什么是必要的语法能有这样的操作?

不是.有许多可以语法化的替代语法.最明显的是简单地new完全消除.如果我们有一个C带有构造函数的类,C(int)那么我们可以简单地说C(123)而不是new C(123).或者我们可以使用类似C.construct(123)或类似的语法.没有new操作员,有很多方法可以做到这一点.

那为什么呢?

首先,C#被设计为C++,Java,JavaScript和其他语言的用户立即熟悉,这些语言new用于指示正​​在为对象初始化新存储.

其次,非常需要正确的句法冗余水平.对象创建很特别; 我们希望在它自己的运营商发生时呼叫.

  • @vapcguy:因为问题是关于C#语言的设计,而不是请求教程来消除他们对C++的错误信念的原始海报.我试图回答这里提出的问题,这是关于`new`运算符的设计以及它与变量声明的关系.关于存储生命周期如何在C++中工作的冗长教程将不在话题. (5认同)
  • -1。@Eric,与其批评OP的问题并说“找一位导师”来说明语法上的差异是什么,为什么不向观众解释一下呢?看来你显然知道。对我来说,您可以在两种语法中将类引用为对象,这就是OP问题中的两个语句所做的。对我来说,这似乎是一个完全有效的问题,而你的冗长解释并没有解决这一点。“为什么用两种语言可以做的事情有两种不同的语法?” 是一个合理的问题。 (3认同)
  • “对象创建很特别”。我不知道,现在创建对象并没有那么特别。尽管如此,很好的答案。 (2认同)
  • "在C#6中,您可以定义一个在调用`new S()`时调用的默认构造函数,但不能在使用`default(S)`时调用. - 这个功能最终被删除了 - 因为它引起了太多的麻烦,我记得.(你当然可以在IL中执行它,并且调用它的情况并不总是很明显......) (2认同)
  • 另一件可能有用的注意事项是,由于C#命名约定正如他们现在所说的那样,如果我们没有`new`,那么告诉使用构造函数和调用一个方法之间的区别并不容易.与调用者在同一范围内.使用`new`有助于消除歧义,而不需要完全限定方法调用. (2认同)

Dmi*_*nko 28

在C#中你可以做类似的事情:

  // please notice "struct"
  struct MyStruct {
    ....
  }

  MyStruct sample1; // this will create object on stack
  MyStruct sample2 = new MyStruct(); // this does the same thing
Run Code Online (Sandbox Code Playgroud)

回想一下像int,doublebool,也是类型的原语struct,所以即使它是常规的写

  int i;
Run Code Online (Sandbox Code Playgroud)

我们也可以写

  int i = new int(); 
Run Code Online (Sandbox Code Playgroud)

与C++不同,C#不使用指针(在安全模式下)到实例,但是C#具有classstruct声明:

  • class:你有引用实例,内存是在堆上分配的, new强制性的 ; 类似于MyClass*C++

  • struct:你有价值,内存(通常)在堆栈上分配, new可选的 ; 类似于MyClassC++

在你的特殊情况下,你可以Car变成struct

struct Car
{
    public int i;
    public int j;
}
Run Code Online (Sandbox Code Playgroud)

所以片段

Car x; // since Car is struct, new is optional now 
x.i = 2;
x.j = 3;
Run Code Online (Sandbox Code Playgroud)

会是对的

  • 我反对通常的错误概念,它不断被抛出,并且正在创造一种令人惊讶的错误信念:类进入堆,结构进入堆栈.这绝对是假的.堆栈上的内容以及堆上的内容的决定取决于对象的预期生命周期,而不是其性质; 一个无法验证为短命的结构将进入堆:`class myClass {int i = 1; //这将在堆上,而不是堆栈.}`. (35认同)
  • 我真的不喜欢这个答案 - 它有太多不稳定的假设.我认为正确的答案应该是"在C#中,除非*你知道你在做什么*,你不应该真正关心内存管理." (10认同)
  • @InBetween:谢谢!我看到https://blogs.msdn.microsoft.com/ericlippert/2010/09/30/the-truth-about-value-types/ (5认同)
  • 我建议最接近`class foo {...}`的C++模拟将是`typedef class {...}*foo`; 一个人需要`new`,原因与C++中需要的一样(带有替换),尽管垃圾收集器意味着对最后一个现有指针的销毁将释放占用的存储而不必使用` delete`. (3认同)

Mat*_*nen 15

在C#中,class类型对象总是在堆上分配,即这些类型的变量总是引用("指针").只声明这种类型的变量不会导致对象的分配.class像在C++中常见的那样在堆栈上分配对象(通常)不是C#中的选项.

未分配的任何类型的局部变量都被视为未初始化,并且在分配给它们之前无法读取它们.这是一个设计选择(另一种方式是default(T)在声明时分配给每个变量),这似乎是一个好主意,因为它应该保护您免受某些编程错误的影响.

它类似于在C++中如何说出来SomeClass *object;并且从不为它分配任何东西是没有意义的.

因为在C#中所有class类型变量都是指针,所以在声明变量时分配空对象会导致代码效率低下,而实际上只是想稍后为变量赋值,例如在以下情况中:

// Needs to be declared here to be available outside of `try`
Foo f;

try { f = GetFoo(); }
catch (SomeException) { return null; }

f.Bar();
Run Code Online (Sandbox Code Playgroud)

要么

Foo f;

if (bar)
    f = GetFoo();
else
    f = GetDifferentFoo();
Run Code Online (Sandbox Code Playgroud)

  • 说"类类型是引用"有点草率.类类型是类类型.真实的是,用类型*声明的*变量表示对象的可空引用. (3认同)

zac*_*caj 12

忽略堆栈与堆栈的事情:

因为C#做出了错误的决定来复制C++,因为他们应该刚刚制作语法

Car car = Car()
Run Code Online (Sandbox Code Playgroud)

(或类似的东西).拥有'新'是多余的.

  • 当然,如果你在你试图调用构造函数的范围内有一个名为`Car()`的方法,那么现在会引入冲突.`new`部分清楚地表明你正在尝试调用构造函数.相反,我可以看到`Car.new()`的参数,但是对我来说,'Car()`似乎是一个坏主意. (5认同)
  • 到目前为止唯一的答案+1才能真正做到正确.C#中的`new`关键字根本不需要像C++那样,并且实际上还有其他CLR语言根本不使用它(或任何特定于语言的等价物). (4认同)

Vla*_*cow 7

当您在此语句中使用引用的类型时

Car c = new Car();
Run Code Online (Sandbox Code Playgroud)

创建了两个实体:一个名为c堆栈中Car类型的对象的引用,以及堆中类型为Car的对象.

如果你只是写

Car c;
Run Code Online (Sandbox Code Playgroud)

然后你创建一个未初始化的未初始化引用(假设c是一个局部变量).

实际上它等同于C++代码,而不是引用使用指针.

例如

Car *c = new Car();
Run Code Online (Sandbox Code Playgroud)

要不就

Car *c;
Run Code Online (Sandbox Code Playgroud)

C++和C#之间的区别在于C++可以在堆栈中创建类的实例

Car c;
Run Code Online (Sandbox Code Playgroud)

在C#中,这意味着创建Car类型的引用,就像我说的那样无处可寻.