wake-up-neo.com

Ich verstehe nicht, warum wir das "neue" Keyword benötigen

Ich bin neu in C # und komme aus C++. In C++ können Sie dies tun:

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

Während in 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;


}

das kannst du nicht machen Ich wundere mich warum Car x macht seine Arbeit nicht.

36
user6528398

Hier gibt es viele Missverständnisse, sowohl in der Frage selbst als auch in den verschiedenen Antworten.

Lassen Sie mich zunächst die Prämisse der Frage untersuchen. Die Frage ist "Warum brauchen wir das new Schlüsselwort in C #?" Die Motivation für die Frage ist dieses Fragment von C++:

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

Ich kritisiere diese Frage aus zwei Gründen.

Erstens diese tun in C++ nicht dasselbe, sodass die Frage auf einem fehlerhaften Verständnis der C++ - Sprache beruht. Es ist sehr wichtig, den Unterschied zwischen diesen beiden Dingen in C++ zu verstehen. Wenn Sie also den Unterschied nicht genau verstehen, suchen Sie sich einen Mentor, der Ihnen beibringt, wie Sie den Unterschied erkennen können. und wann man sie benutzt.

Zweitens setzt die Frage fälschlicherweise voraus, dass diese beiden Syntaxen in C++ dasselbe tun, und fragt dann seltsamerweise: "Warum brauchen wir new in C #?" Sicherlich ist die richtige Frage bei dieser - wiederum falschen - Annahme: "Warum brauchen wir new in C++?" Wenn diese beiden Syntaxen dasselbe tun - was sie nicht tun - dann warum haben sie überhaupt zwei Syntaxen?

Die Frage basiert also auf einer falschen Prämisse, und die Frage nach C # ergibt sich eigentlich nicht aus dem - missverstandenen - Design von C++.

Das ist ein Chaos. Lassen Sie uns diese Frage wegwerfen und einige bessere Fragen stellen. Stellen wir die Frage nach C # qua C # und nicht im Kontext der Entwurfsentscheidungen von C++.

Was macht der Operator new X In C #, wobei X ein Klassen- oder Strukturtyp ist? (Lassen Sie uns die Delegierten und Arrays für die Zwecke dieser Diskussion ignorieren.)

Der neue Betreiber:

  • Bewirkt, dass eine neue Instanz des angegebenen Typs zugewiesen wird. Bei neuen Instanzen werden alle Felder mit Standardwerten initialisiert.
  • Bewirkt, dass ein Konstruktor des angegebenen Typs ausgeführt wird.
  • Erzeugt eine Referenz auf das zugewiesene Objekt, wenn das Objekt ein Referenztyp ist, oder den Wert selbst Wenn das Objekt ein Werttyp ist.

Okay, ich kann die Einwände von C # -Programmierern bereits hören, also lasst uns sie abweisen.

Einwand: Es wird kein neuer Speicher zugewiesen, wenn es sich bei dem Typ um einen Werttyp handelt. Nun, die C # -Spezifikation stimmt nicht mit Ihnen überein. Wenn du sagst

S s = new S(123);

für einige Strukturtypen S wird in der Spezifikation angegeben, dass neuer temporärer Speicher für den Kurzzeitpool zugewiesen wird, der auf seinen Standardwert initialisiert wurde Werte, der Konstruktor wird mit this ausgeführt, um auf den temporären Speicher zu verweisen, und dann wird das resultierende Objekt nach s. Dem Compiler ist es jedoch gestattet , eine Kopieroptimierung zu verwenden, sofern er nachweisen kann, dass die Optimierung in einem sicheren Programm nicht beobachtet werden kann . (Übung: Ermitteln Sie, unter welchen Umständen eine Kopierentfernung nicht durchgeführt werden kann. Nennen Sie ein Beispiel für ein Programm, das sich anders verhält, wenn elision verwendet wurde oder nicht.)

Einwand: Mit default(S) kann eine gültige Instanz eines Wertetyps erzeugt werden; Es wird kein Konstruktor aufgerufen, höre ich Sie sagen. Das ist richtig. Ich habe nicht gesagt, dass new die einzige Möglichkeit ist, eine Instanz eines Werttyps zu erstellen.

Tatsächlich sind für einen Werttyp new S() und default(S) dasselbe.

Einwand: Wird ein Konstruktor wirklich für Situationen wie new S() ausgeführt, wenn er nicht im Quellcode in C # 6 vorhanden ist, höre ich Sie sagen. Dies ist ein "Wenn ein Baum in den Wald fällt und niemand es hört, macht es dann ein Geräusch?" Frage. Gibt es einen Unterschied zwischen einem Aufruf eines Konstruktors, der nichts tut, und überhaupt keinem Aufruf? Dies ist keine interessante Frage. Dem Compiler steht es frei, Aufrufe zu ignorieren, von denen er weiß, dass sie nichts tun.

Angenommen, wir haben eine Variable vom Werttyp. Müssen wir die Variable mit einer von new erzeugten Instanz initialisieren?

Nein. Variablen, die automatisch initialisiert werden , wie z. B. Felder und Array-Elemente, werden auf den Standardwert, dh den Wert der Struktur, initialisiert wobei alle Felder selbst ihre Standardwerte sind.

Formale Parameter werden natürlich mit dem Argument initialisiert.

Lokale Variablen des Wertetyps müssen vor dem Lesen der Felder definitiv mit etwas zugewiesen werden, es muss sich jedoch nicht um ein new handeln. Ausdruck.

So effektiv werden Variablen des Wertetyps automatisch mit dem Äquivalent von default(S) initialisiert, es sei denn, es handelt sich um lokale Variablen?

Ja.

Warum nicht das Gleiche für Einheimische tun?

Die Verwendung eines nicht initialisierten lokalen Codes ist stark mit fehlerhaftem Code verbunden. Die C # -Sprache lässt dies nicht zu, da dies zu Fehlern führt.

Angenommen, wir haben eine Variable vom Referenztyp. Müssen wir S mit einer von new erzeugten Instanz initialisieren?

Nein. Variablen für die automatische Initialisierung werden mit Null initialisiert. Einheimische können mit jedem Verweis initialisiert werden, einschließlich null, und müssen vor dem Lesen eindeutig zugewiesen werden.

So effektiv werden Variablen des Referenztyps automatisch mit null initialisiert, es sei denn, es handelt sich um lokale Variablen?

Ja.

Warum nicht das Gleiche für Einheimische tun?

Gleicher Grund. Ein wahrscheinlicher Fehler.

Warum nicht automatisch Variablen des Referenztyps initialisieren, indem der Standardkonstruktor automatisch aufgerufen wird? Das heißt, warum sollte R r; Nicht mit R r = new R(); identisch sein?

Nun, zuallererst haben viele Typen keinen Standardkonstruktor oder überhaupt irgendein zugänglicher Konstruktor. Zweitens erscheint es seltsam, eine Regel für ein nicht initialisiertes lokales Element oder Feld, eine andere Regel für ein formales Element und eine weitere Regel für ein Array-Element zu haben. Drittens ist die bestehende Regel sehr einfach: Eine Variable muss mit einem Wert initialisiert werden. Dieser Wert kann beliebig sein. warum ist die Annahme, dass eine neue Instanz gewünscht wird, gerechtfertigt? Es wäre bizarr, wenn das so wäre

R r;
if (x) r = M(); else r = N();

veranlasste einen Konstruktor, ausgeführt zu werden, um r zu initialisieren.

Abgesehen von der Semantik des Operators new, warum ist es notwendig syntaktisch einen solchen Operator haben?

Es ist nicht. Es gibt eine beliebige Anzahl von alternativen Syntaxen, die grammatikalisch sein können. Am naheliegendsten wäre es, das new einfach ganz zu eliminieren. Wenn wir eine Klasse C mit einem Konstruktor C(int) haben, können wir einfach C(123) anstelle von new C(123) sagen. Oder wir könnten eine Syntax wie C.construct(123) oder etwas Ähnliches verwenden. Es gibt eine beliebige Anzahl von Möglichkeiten, dies ohne den Operator new zu tun.

Warum also?

Zunächst wurde C # entwickelt, um Benutzern von C++, Java, JavaScript und anderen Sprachen, die new verwenden, um anzuzeigen, dass ein neuer Speicher für ein Objekt initialisiert wird, sofort vertraut zu sein.

Zweitens ist das richtige Maß an syntaktischer Redundanz äußerst wünschenswert. Objekterstellung ist etwas Besonderes; wir möchten anrufen, wenn es mit seinem eigenen Operator geschieht.

47
Eric Lippert

In C # können Sie die ähnliche Sache tun:

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

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

Denken Sie daran, dass Primitive wie int, double und bool ebenfalls vom Typ struct sind, obwohl das Schreiben konventionell ist

  int i;

wir können auch schreiben

  int i = new int(); 

im Gegensatz zu C++ verwendet C # keine Zeiger (im abgesicherten Modus) auf Instanzen, C # verfügt jedoch über class - und struct -Deklarationen:

  • class: Sie haben Verweis auf Instanz, Speicher wird zugewiesen auf Heap, new ist obligatorisch; ähnlich zu MyClass* in C++

  • struct: Sie haben Wert, Speicher wird (normalerweise) auf Stapel zugewiesen, new ist optional; ähnlich wie MyClass in C++

In Ihrem speziellen Fall können Sie Car einfach in struct verwandeln.

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

und so das fragment

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

wird richtig sein

28
Dmitry Bychenko

In C # werden Objekte vom Typ class immer auf dem Heap zugeordnet, d. H. Variablen dieses Typs sind immer Referenzen ("Zeiger"). Das Deklarieren einer Variablen dieses Typs führt nicht zur Zuweisung eines Objekts. Das Zuweisen eines class -Objekts auf dem Stapel, wie es in C++ üblich ist, ist (im Allgemeinen) keine Option in C #.

Lokale Variablen jeglichen Typs, die nicht zugewiesen wurden, gelten als nicht initialisiert und können erst gelesen werden, wenn sie zugewiesen wurden. Dies ist eine Design-Wahl (eine andere Möglichkeit wäre gewesen, jeder Variablen zum Zeitpunkt der Deklaration default(T) zuzuweisen), was eine gute Idee zu sein scheint, da es Sie vor einigen Programmierfehlern schützen sollte.

Ähnlich wie in C++ wäre es nicht sinnvoll, SomeClass *object; Zu sagen und ihm niemals etwas zuzuweisen.

Da in C # alle Variablen vom Typ class Zeiger sind, führt die Zuweisung eines leeren Objekts bei der Deklaration der Variablen zu ineffizientem Code, wenn Sie der Variablen später tatsächlich nur einen Wert zuweisen möchten, beispielsweise in folgenden Situationen:

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

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

f.Bar();

Oder

Foo f;

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

ignorieren des Stacks im Vergleich zum Heap:

weil C # die falsche Entscheidung getroffen hat, C++ zu kopieren, wenn sie gerade die Syntax hätten treffen sollen

Car car = Car()

(oder etwas ähnliches). 'Neu' zu haben ist überflüssig.

12
zacaj

Wenn Sie referenzierte Typen verwenden, dann in dieser Anweisung

Car c = new Car();

es werden zwei Entitäten erstellt: ein Verweis mit dem Namen c auf ein Objekt vom Typ Car im Stapel und das Objekt vom Typ Car selbst im Heap.

Wenn du nur schreibst

Car c;

anschließend erstellen Sie eine nicht initialisierte Referenz (vorausgesetzt, c ist eine lokale Variable), die auf nichts verweist.

Tatsächlich entspricht es C++ - Code, bei dem anstelle von Referenzen Zeiger verwendet werden.

Beispielsweise

Car *c = new Car();

oder nur

Car *c;

Der Unterschied zwischen C++ und C # besteht darin, dass C++ Instanzen von Klassen im Stapel erstellen kann

Car c;

In C # bedeutet dies, eine Referenz vom Typ Auto zu erstellen, die, wie gesagt, nirgendwo hinweist.

7

Aus dem Microsoft-Programmierhandbuch:

Wenn Sie zur Laufzeit eine Variable eines Referenztyps deklarieren, enthält die Variable den Wert null, bis Sie mit dem Operator new explizit eine Instanz des Objekts erstellen oder ihr mit new ein Objekt zuweisen, das an anderer Stelle erstellt wurde

Eine Klasse ist ein Referenztyp. Wenn ein Objekt der Klasse erstellt wird, enthält die Variable, der das Objekt zugewiesen ist, nur einen Verweis auf diesen Speicher. Wenn die Objektreferenz einer neuen Variablen zugewiesen wird, bezieht sich die neue Variable auf das ursprüngliche Objekt. Änderungen, die über eine Variable vorgenommen wurden, werden in der anderen Variablen wiedergegeben, da beide auf dieselben Daten verweisen.

Eine Struktur ist ein Werttyp. Wenn eine Struktur erstellt wird, enthält die Variable, der die Struktur zugewiesen ist, die tatsächlichen Daten der Struktur. Wenn die Struktur einer neuen Variablen zugewiesen wird, wird sie kopiert. Die neue Variable und die ursprüngliche Variable enthalten daher zwei separate Kopien derselben Daten. An einer Kopie vorgenommene Änderungen wirken sich nicht auf die andere Kopie aus.

Ich denke, dass Sie in Ihrem C # -Beispiel effektiv versuchen, einem Nullzeiger Werte zuzuweisen. In c ++ Übersetzung würde dies so aussehen:

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

Dies würde natürlich kompilieren, aber abstürzen.

2
Peter Respondek