web-dev-qa-db-ja.com

「新しい」キーワードが必要な理由がわかりません

私は、C++の背景から、C#を初めて使用します。 C++では、これを行うことができます。

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

一方、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;


}

これはできません。なぜCar xは動作しません。

36
user6528398

ここには、質問自体といくつかの回答の両方に多くの誤解があります。

質問の前提を調べることから始めましょう。問題は、「C#でnewキーワードが必要な理由」です。質問の動機は、C++の次のフラグメントです。

_ MyClass object; // this will create object in memory
 MyClass* object = new MyClass(); // this does same thing
_

この質問を2つの理由で批判します。

最初に、これらはC++では同じことをしませんなので、質問はC++言語の誤った理解に基づいています。 C++でこれら2つのことの違いを理解することは非常に重要ですですので、違いが何であるかを明確に理解できない場合は、違いを知る方法を教えることができるメンターを見つけてください。そして、それぞれをいつ使用するか。

次に、質問は、これらの2つの構文がC++で同じことを行うことを前提としますが、奇妙なことに、「C#でnewが必要なのはなぜですか?」確かに、これを考えると正しい質問です-再び、偽-前提条件は、「C++でnewが必要なのはなぜですか?」これらの2つの構文が同じことを行う場合、それらは同じではありませんが、最初に2つの構文があるのはなぜですか

したがって、質問はどちらも誤った前提に基づいており、C#についての質問は、実際にはC++の設計(誤解された設計)には従いません。

これは混乱です。この質問を捨てて、より良い質問をしてみましょう。そして、C++の設計決定のコンテキストではなく、C#quaC#について質問します。

Xがクラスまたは構造体型であるC#で_new X_演算子は何をしますか? (この議論の目的のために、デリゲートと配列を無視しましょう。)

新しい演算子:

  • 指定されたタイプの新しいインスタンスが割り当てられます。新しいインスタンスでは、すべてのフィールドがデフォルト値に初期化されています。
  • 指定されたタイプのコンストラクターを実行します。
  • オブジェクトが参照型の場合、割り当てられたオブジェクトへのreference、または値自体オブジェクトが値型の場合。

大丈夫、私はすでにC#プログラマーから異議を聞いているので、それらを却下しましょう。

異論:型が値型である場合、新しいストレージは割り当てられない、とあなたは言う。さて、C#の仕様はあなたに同意しません。あなたが言う時

_S s = new S(123);
_

いくつかの構造体タイプSについて、仕様では新しい一時ストレージが短期プールに割り当てられ、デフォルト値に初期化される、コンストラクタはthisを設定して一時ストレージを参照するように実行し、結果のオブジェクトはcopiedto s。ただし、コンパイラは、最適化が安全なプログラムで観察されることが不可能であることを証明できる場合、コピー回避最適化を使用することが許可されています 。 (演習:どのような状況でコピー省略を実行できないかを調べます。省略を使用した場合と使用しなかった場合に異なる動作をするプログラムの例を示します。)

異論:default(S);を使用して、値型の有効なインスタンスを生成できます。コンストラクタは呼び出されません、あなたが言うのを聞きます。そのとおりです。 newが値型のインスタンスを作成するonly方法であるとは言いませんでした。

実際、値型の場合、new S()default(S)は同じものです。

異論:C#6のソースコードに存在しない場合、new S()のような状況で実際に実行されるコンストラクターですか?これは、「木が森に落ちて誰も聞いていない場合、音がしますか?」です。質問。何もしないコンストラクターへの呼び出しと、まったく呼び出しに違いはありますか?これは興味深い質問ではありません。コンパイラは、何もしないことがわかっている呼び出しを自由に削除できます。

値型の変数があるとします。 newによって生成されたインスタンスで変数を初期化する必要がありますか?

いいえ。フィールドや配列要素など、自動的に初期化される変数は、デフォルト値、つまり構造体の値に初期化されますここで、すべてのフィールド自体がデフォルト値です。

明らかに、仮パラメータは引数で初期化されます。

値型のローカル変数には、フィールドを読み取る前にsomethingを確実に割り当てる必要がありますが、new式である必要はありません。

事実上、ローカルでない限り、値型の変数はdefault(S)と同等の値で自動的に初期化されますか?

はい。

地元の人にも同じことをしてみませんか?

初期化されていないローカルの使用は、バグのあるコードに強く関連付けられています。 C#言語はこれを許可しません。これを行うとバグが見つかるからです。

参照型の変数があるとします。 Sによって生成されたインスタンスでnewを初期化する必要がありますか?

いいえ。自動初期化変数はnullで初期化されます。ローカルは、nullを含む任意の参照で初期化でき、読み取る前に必ず割り当てる必要があります。

事実上、ローカルでない限り、参照型の変数はnullで自動的に初期化されますか?

はい。

地元の人にも同じことをしてみませんか?

同じ理由。おそらくバグ。

デフォルトのコンストラクタを自動的に呼び出して、参照型の変数を自動的に初期化しないのはなぜですか?つまり、_R r;_をR r = new R();と同じにしないのはなぜですか?

まあ、まず第一に、多くの型にはデフォルトのコンストラクターがありません。または、その点については、アクセス可能なコンストラクターです。第二に、初期化されていないローカルまたはフィールドに1つのルール、フォーマルに別のルール、配列要素にさらに別のルールがあるのは奇妙に思えます。第三に、既存のルールは非常に単純です。変数は値に初期化する必要があります。その値は、好きなものにすることができます。 新しいインスタンスが必要であるという前提はなぜですか?これなら奇妙だろう

_R r;
if (x) r = M(); else r = N();
_

コンストラクタを実行してrを初期化しました。

new演算子のsemanticsを残して、なぜ構文的にそのような演算子を使用するには?

そうではありません。文法的になる可能性のある代替構文はいくつもあります。最も明白なのは、単にnewを完全に削除することです。クラスCにコンストラクタC(int)がある場合、C(123)の代わりにnew C(123)と単純に言うことができます。または、C.construct(123)などの構文を使用することもできます。 new演算子なしでこれを行う方法はいくつもあります。

なぜそれを持っているのですか?

まず、C#は、C++、Java、JavaScript、およびnewを使用してオブジェクトの新しいストレージが初期化されていることを示す他の言語のユーザーにすぐに馴染むように設計されました。

第二に、適切なレベルの構文冗長性が非常に望ましい。オブジェクトの作成は特別です。独自の演算子で発生したときに呼び出したいと思います。

47
Eric Lippert

C#では、similarのことができます:

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

  MyStruct sample1; // this will create object on stack
  MyStruct sample2 = new MyStruct(); // this does the same thing

intdouble、およびboolなどのプリミティブもstruct型であることに注意してください。

  int i;

私たちも書きます

  int i = new int(); 

c ++とは異なり、C#は(セーフモードで)インスタンスへのポインターを使用しませんが、C#にはclassおよびstruct宣言があります。

  • class:インスタンスにreferenceがあり、メモリはheapに割り当てられ、new必須;に似ている MyClass* in C++

  • structvalueがあり、メモリは(通常)stackに割り当てられ、newoptional; C++MyClassと同様

あなたの特定のケースでは、Carstructに変えることができます

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

そしてその断片

Car x; // since Car is struct, new is optional now 
x.i = 2;
x.j = 3;

正しいでしょう

28
Dmitry Bychenko

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();

または

Foo f;

if (bar)
    f = GetFoo();
else
    f = GetDifferentFoo();
15
Matti Virkkunen

スタックとヒープの側面を無視する:

なぜなら、C#は、構文を作成すべきだったときにC++をコピーするという悪い決定をしたからです。

Car car = Car()

(または同様のもの)。 「新しい」ことは不要です。

12
zacaj

参照型を使用する場合、このステートメントで

Car c = new Car();

スタック内のCar型のオブジェクトへのcという名前の参照と、ヒープ内のCar型自体のオブジェクトの2つのエンティティが作成されます。

書くだけなら

Car c;

次に、どこにもポイントしない初期化されていない参照(cがローカル変数である場合)を作成します。

実際、参照の代わりに使用されるポインターがあるC++コードと同等です。

例えば

Car *c = new Car();

あるいは単に

Car *c;

C++とC#の違いは、C++が次のようにスタック内にクラスのインスタンスを作成できることです。

Car c;

C#では、これは、先ほど言ったようにどこにも指し示していないCar型の参照を作成することを意味します。

7

Microsoftプログラミングガイドから:

実行時に、参照型の変数を宣言すると、new演算子を使用してオブジェクトのインスタンスを明示的に作成するか、newを使用して他の場所で作成されたオブジェクトを割り当てるまで、変数には値nullが含まれます

クラスは参照型です。クラスのオブジェクトが作成されると、オブジェクトが割り当てられる変数はそのメモリへの参照のみを保持します。オブジェクト参照が新しい変数に割り当てられると、新しい変数は元のオブジェクトを参照します。 1つの変数を介して行われた変更は、両方が同じデータを参照するため、他の変数に反映されます。

構造体は値型です。構造体が作成されると、構造体が割り当てられる変数は、構造体の実際のデータを保持します。構造体が新しい変数に割り当てられると、コピーされます。したがって、新しい変数と元の変数には、同じデータの2つの別々のコピーが含まれます。 1つのコピーに加えた変更は、他のコピーには影響しません。

C#の例では、nullポインターに値を効果的に割り当てようとしていると思います。 C++翻訳では、これは次のようになります。

Car* x = null;
x->i = 2;
x->j = 3;

これは明らかにコンパイルされますが、クラッシュします。

2
Peter Respondek