it-swarm.com.de

Warum kann ich keinen Standardkonstruktor für eine Struktur in .NET definieren?

In .NET kann ein Werttyp (C # struct) keinen Konstruktor ohne Parameter enthalten. Nach dieser Stelle ist dies durch die CLI-Spezifikation vorgeschrieben. Was passiert, ist, dass für jeden Werttyp (vom Compiler?) Ein Standardkonstruktor erstellt wird, der alle Elemente auf Null (oder null) initialisiert hat.

Warum ist es nicht zulässig, einen solchen Standardkonstruktor zu definieren?

Eine triviale Verwendung ist für rationale Zahlen:

public struct Rational {
    private long numerator;
    private long denominator;

    public Rational(long num, long denom)
    { /* Todo: Find GCD etc. */ }

    public Rational(long num)
    {
        numerator = num;
        denominator = 1;
    }

    public Rational() // This is not allowed
    {
        numerator = 0;
        denominator = 1;
    }
}

Bei Verwendung der aktuellen Version von C # ist der Standardcode von Rational 0/0, was nicht so cool ist.

PS: Werden Standardparameter zur Lösung dieses Problems für C # 4.0 beitragen, oder wird der durch CLR definierte Standardkonstruktor aufgerufen?


Jon Skeet antwortete:

Um Ihr Beispiel zu verwenden, was würden Sie tun, wenn jemand es getan hätte:

 Rational[] fractions = new Rational[1000];

Sollte es 1000 Mal durch Ihren Konstruktor laufen?

Sicher sollte es, deshalb habe ich den Standardkonstruktor in erster Linie geschrieben. Die CLR sollte den Konstruktor Standard-Nullung verwenden, wenn kein expliziter Standardkonstruktor definiert ist. Auf diese Weise bezahlen Sie nur für das, was Sie verwenden. Wenn ich einen Container mit 1000 nicht standardmäßigen Rationals (und die 1000 Konstruktionen wegoptimieren möchte) möchte, verwende ich einen List<Rational> anstelle eines Arrays.

Dieser Grund ist meines Erachtens nicht stark genug, um die Definition eines Standardkonstruktors zu verhindern.

218
Motti

Anmerkung: Die Antwort unten wurde lange vor C # 6 geschrieben. In diesem Zusammenhang ist geplant, parameterlose Konstruktoren in Strukturen zu deklarieren. Sie werden jedoch nicht in allen Situationen aufgerufen (z. B. zur Array-Erstellung). (am Ende wurde diese Funktion nicht zu C # 6 hinzugefügt).


EDIT: Ich habe die Antwort unten bearbeitet, da Grauenwolf Einblick in die CLR hat.

Die CLR ermöglicht, dass Werttypen parameterlose Konstruktoren haben, C # jedoch nicht. Ich glaube, dass dies die Erwartung ist, dass der Konstruktor aufgerufen wird, wenn dies nicht der Fall wäre. Betrachten Sie zum Beispiel Folgendes:

MyStruct[] foo = new MyStruct[1000];

Die CLR ist in der Lage, dies sehr effizient zu tun, indem einfach der entsprechende Speicher zugewiesen und auf Null gesetzt wird. Wenn der MyStruct-Konstruktor 1000 Mal ausgeführt werden müsste, wäre das wesentlich weniger effizient. (Tatsächlich nicht - wenn Sie do über einen parameterlosen Konstruktor verfügen, wird er nicht ausgeführt, wenn Sie ein Array erstellen oder wenn Sie eine nicht initialisierte Instanzvariable haben.)

Die Grundregel in C # lautet "Der Standardwert für jeden Typ kann sich nicht auf eine Initialisierung verlassen". Nun hatten sie könnten die Definition parameterloser Konstruktoren zugelassen, aber nicht zwingend, dass der Konstruktor in allen Fällen ausgeführt wurde - aber das hätte zu mehr Verwirrung geführt. (Oder zumindest glaube ich, dass das Argument zutrifft.)

BEARBEITEN: Um dein Beispiel zu verwenden, was würdest du tun, wenn jemand es getan hätte:

Rational[] fractions = new Rational[1000];

Sollte es 1000 Mal durch Ihren Konstruktor laufen?

  • Wenn nicht, erhalten wir 1000 ungültige Rationals
  • Wenn dies der Fall ist, haben wir möglicherweise eine Menge Arbeit verschwendet, wenn wir das Array mit echten Werten füllen.

EDIT: (Beantwortet die Frage etwas mehr) Der parameterlose Konstruktor wird nicht vom Compiler erstellt. Werttypen müssen keine Konstruktoren haben, was die CLR betrifft - obwohl es sich als can herausstellt, wenn Sie sie in IL schreiben. Wenn Sie "new Guid()" in C # schreiben, wird eine andere IL ausgegeben als bei einem normalen Konstruktor. Siehe diese SO - Frage , um mehr über diesen Aspekt zu erfahren.

Ich suspect dass es im Framework keine Werttypen mit parameterlosen Konstruktoren gibt. Kein Zweifel, NDepend könnte mir sagen, ob ich es nett gefragt hätte ... Die Tatsache, dass C # es verbietet, ist ein Hinweis, der groß genug ist, um zu glauben, dass dies wahrscheinlich eine schlechte Idee ist.

173
Jon Skeet

Eine Struktur ist ein Werttyp, und ein Werttyp muss einen Standardwert haben, sobald er deklariert ist.

MyClass m;
MyStruct m2;

Wenn Sie zwei Felder wie oben deklarieren, ohne eines zu instanziieren, dann brechen Sie den Debugger, m ist null, aber m2 nicht. In Anbetracht dessen würde ein parameterloser Konstruktor keinen Sinn machen. In der Tat gibt jeder Konstruktor in einer Struktur Werte zu. Die Sache selbst existiert bereits durch das Deklarieren. M2 könnte in dem obigen Beispiel durchaus gerne verwendet werden und seine Methoden, falls vorhanden, und deren Felder und Eigenschaften manipulieren lassen!

40
user42467

Sie können eine statische Eigenschaft erstellen, die die Standardzahl "rational" initialisiert und zurückgibt:

public static Rational One => new Rational(0, 1); 

Und benutze es wie:

var rat = Rational.One;
14
Skasel

Obwohl die CLR dies zulässt, erlaubt C # nicht, dass Strukturen einen standardmäßigen Konstruktor ohne Parameter haben. Der Grund ist, dass Compiler standardmäßig für einen Werttyp weder einen Standardkonstruktor generieren noch einen Aufruf an den Standardkonstruktor generieren. Selbst wenn Sie einen Standardkonstruktor definiert haben, wird dieser nicht aufgerufen, und das wird Sie nur verwirren.

Um solche Probleme zu vermeiden, lässt der C # -Compiler die Definition eines Standardkonstruktors durch den Benutzer nicht zu. Und da es keinen Standardkonstruktor generiert, können Felder bei der Definition nicht initialisiert werden.

Oder der Hauptgrund ist, dass eine Struktur ein Werttyp ist und Werttypen mit einem Standardwert initialisiert werden und der Konstruktor für die Initialisierung verwendet wird.

Sie müssen Ihre Struktur nicht mit dem Schlüsselwort new instanziieren. Es funktioniert stattdessen wie ein Int. Sie können direkt darauf zugreifen.

Strukturen können keine expliziten parameterlosen Konstruktoren enthalten. Strukturelemente werden automatisch mit ihren Standardwerten initialisiert.

Ein Standardkonstruktor (parameterloser) für eine Struktur kann andere Werte als den Zustand mit Nullstellen festlegen, was ein unerwartetes Verhalten wäre. Die .NET-Laufzeitumgebung verbietet daher Standardkonstruktoren für eine Struktur.

14
Adiii

Kürzere Erklärung: 

In C++ waren struct und class nur zwei Seiten derselben Medaille. Der einzige wirkliche Unterschied besteht darin, dass einer standardmäßig öffentlich und der andere privat war.

In .NET gibt es einen viel größeren Unterschied zwischen einer Struktur und einer Klasse. Die Hauptsache ist, dass struct eine Werttyp-Semantik bereitstellt, während Class eine Referenztyp-Semantik bereitstellt. Wenn Sie über die Auswirkungen dieser Änderung nachdenken, werden auch andere Änderungen sinnvoller, einschließlich des von Ihnen beschriebenen Konstruktorverhaltens.

13
Joel Coehoorn

Nur ein Sonderfall. Wenn Sie einen Zähler von 0 und einen Nenner von 0 sehen, tun Sie so, als ob er die gewünschten Werte hätte.

3
Jonathan Allen

Sie können keinen Standardkonstruktor definieren, da Sie C # verwenden.

Strukturen können in .NET über Standardkonstruktoren verfügen, obwohl mir keine Sprache bekannt ist, die dies unterstützt.

1
Jonathan Allen

Hier ist meine Lösung für das Konstruktordilemma "no default". Ich weiß, dass dies eine späte Lösung ist, aber ich denke, dass dies eine Lösung ist. 

public struct Point2D {
    public static Point2D NULL = new Point2D(-1,-1);
    private int[] Data;

    public int X {
        get {
            return this.Data[ 0 ];
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 0 ] = value;
            }
        }
    }

    public int Z {
        get {
            return this.Data[ 1 ];
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new int[ 2 ];
            } finally {
                this.Data[ 1 ] = value;
            }
        }
    }

    public Point2D( int x , int z ) {
        this.Data = new int[ 2 ] { x , z };
    }

    public static Point2D operator +( Point2D A , Point2D B ) {
        return new Point2D( A.X + B.X , A.Z + B.Z );
    }

    public static Point2D operator -( Point2D A , Point2D B ) {
        return new Point2D( A.X - B.X , A.Z - B.Z );
    }

    public static Point2D operator *( Point2D A , int B ) {
        return new Point2D( B * A.X , B * A.Z );
    }

    public static Point2D operator *( int A , Point2D B ) {
        return new Point2D( A * B.Z , A * B.Z );
    }

    public override string ToString() {
        return string.Format( "({0},{1})" , this.X , this.Z );
    }
}

ich ignoriere die Tatsache, dass ich eine statische Struktur namens null habe (Anmerkung: Dies gilt nur für alle positiven Quadranten). In C # können Sie try/catch/finally verwenden, um Fehler zu behandeln, bei denen ein bestimmter Datentyp nicht vom Standardkonstruktor Point2D () initialisiert wird. Ich denke, das ist schwer zu lösen, als Lösung für einige Antwortende. Deshalb füge ich meins hinzu. Wenn Sie die Getter- und Setter-Funktion in C # verwenden, können Sie diesen Standardkonstruktor ignorieren und einen Versuch machen, was Sie nicht initialisiert haben. Für mich funktioniert das gut, für jemand anderen möchten Sie vielleicht einige if-Anweisungen hinzufügen. In dem Fall, in dem Sie einen Numerator/Nenner-Setup wünschen, kann dieser Code hilfreich sein. Ich möchte nur wiederholen, dass diese Lösung nicht schön aussieht, wahrscheinlich vom Standpunkt der Effizienz aus noch schlechter ist, aber für jemanden, der aus einer älteren Version von C # stammt, bietet die Verwendung von Array-Datentypen diese Funktionalität. Wenn Sie nur etwas möchten, das funktioniert, versuchen Sie Folgendes:

public struct Rational {
    private long[] Data;

    public long Numerator {
        get {
            try {
                return this.Data[ 0 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 0 ];
            }
        }
        set {
            try {
                this.Data[ 0 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 0 ] = value;
            }
        }
    }

    public long Denominator {
        get {
            try {
                return this.Data[ 1 ];
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                return this.Data[ 1 ];
            }
        }
        set {
            try {
                this.Data[ 1 ] = value;
            } catch( Exception ) {
                this.Data = new long[ 2 ] { 0 , 1 };
                this.Data[ 1 ] = value;
            }
        }
    }

    public Rational( long num , long denom ) {
        this.Data = new long[ 2 ] { num , denom };
        /* Todo: Find GCD etc. */
    }

    public Rational( long num ) {
        this.Data = new long[ 2 ] { num , 1 };
        this.Numerator = num;
        this.Denominator = 1;
    }
}
1
G1xb17

Ich habe kein Äquivalent zu einer späten Lösung gesehen, die ich geben werde.

verwenden Sie Offsets, um Werte von der Standardeinstellung 0 in einen beliebigen Wert zu verschieben. Hier müssen Eigenschaften verwendet werden, anstatt direkt auf Felder zuzugreifen. (Vielleicht definieren Sie mit einer möglichen C # 7-Funktion Felder mit Eigenschaftenbereich besser, damit sie vor dem direkten Zugriff im Code geschützt bleiben.)

Diese Lösung funktioniert für einfache Strukturen mit nur Werttypen (kein Referenztyp oder nullfähige Struktur).

public struct Tempo
{
    const double DefaultBpm = 120;
    private double _bpm; // this field must not be modified other than with its property.

    public double BeatsPerMinute
    {
        get => _bpm + DefaultBpm;
        set => _bpm = value - DefaultBpm;
    }
}

Dies ist anders als diese Antwort, dieser Ansatz ist keine spezielle Groß-/Kleinschreibung, sondern die Verwendung von Offset, die für alle Bereiche funktioniert.

beispiel mit Aufzählungen als Feld.

public struct Difficaulty
{
    Easy,
    Medium,
    Hard
}

public struct Level
{
    const Difficaulty DefaultLevel = Difficaulty.Medium;
    private Difficaulty _level; // this field must not be modified other than with its property.

    public Difficaulty Difficaulty
    {
        get => _level + DefaultLevel;
        set => _level = value - DefaultLevel;
    }
}

Wie ich bereits sagte, funktioniert dieser Trick möglicherweise nicht in allen Fällen, auch wenn struct nur Wertfelder enthält. Nur Sie wissen, ob er in Ihrem Fall funktioniert oder nicht. überprüfe es einfach. aber Sie bekommen die allgemeine Vorstellung.

1
M.kazem Akhgary
public struct Rational 
{
    private long numerator;
    private long denominator;

    public Rational(long num = 0, long denom = 1)   // This is allowed!!!
    {
        numerator   = num;
        denominator = denom;
    }
}
0
eMeL