In .NET kann ein Werttyp (C # struct
) keinen Konstruktor ohne Parameter haben. Laut diesem Beitrag ist dies in der CLI-Spezifikation vorgeschrieben. Was passiert ist, dass für jeden Werttyp ein Standardkonstruktor erstellt wird (vom Compiler?), Der alle Mitglieder auf Null (oder null
) initialisiert .
Warum ist es nicht erlaubt, einen solchen Standardkonstruktor zu definieren?
Eine triviale Verwendung sind 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 ein Standard-Rational 0/0
nicht so cool.
PS : Helfen Standardparameter bei der Lösung dieses Problems für C # 4.0 oder wird der CLR-definierte Standardkonstruktor aufgerufen?
Jon Skeet antwortete:
Um Ihr Beispiel zu verwenden: Was möchten Sie tun, wenn jemand Folgendes tut:
Rational[] fractions = new Rational[1000];
Sollte es 1000 Mal durch Ihren Konstruktor laufen?
Sicher sollte es, deshalb habe ich zuerst den Standardkonstruktor geschrieben. Die CLR sollte den Standard-Nullungskonstruktor verwenden, wenn kein expliziter Standardkonstruktor definiert ist. Auf diese Weise zahlen Sie nur für das, was Sie verwenden. Wenn ich dann einen Container mit 1000 nicht standardmäßigen Rational
s möchte (und die 1000 Konstruktionen optimieren möchte), verwende ich List<Rational>
eher ein als ein Array.
Dieser Grund ist meiner Meinung nach nicht stark genug, um die Definition eines Standardkonstruktors zu verhindern.
Rational()
der parameterlose ctor anstelle des aufgerufen wirdRational(long num=0, long denom=1)
.new Rational()
wird der Konstruktor aufrufen , wenn es vorhanden ist , aber wenn es nicht existiertnew Rational()
wird äquivalentdefault(Rational)
. In jedem Fall werden Sie aufgefordert, die Syntax zu verwenden,default(Rational)
wenn Sie den "Nullwert" Ihrer Struktur wünschen (was eine "schlechte" Zahl bei Ihrem vorgeschlagenen Design von istRational
). Der Standardwert für einen WerttypT
ist immerdefault(T)
. Sonew Rational[1000]
wird nie invoke struct Konstrukteure.denominator - 1
in der Struktur speichern , sodass der Standardwert 0/1Then if I want a container of 1000 non-default Rationals (and want to optimize away the 1000 constructions) I will use a List<Rational> rather than an array.
Warum sollte ein Array einen anderen Konstruktor für eine Liste für eine Struktur aufrufen?Antworten:
Hinweis: Die folgende Antwort wurde lange vor C # 6 geschrieben, das die Möglichkeit einführen soll, parameterlose Konstruktoren in Strukturen zu deklarieren. Sie werden jedoch nicht in allen Situationen (z. B. zur Array-Erstellung)(am Ende) aufgerufenDiese Funktion wurde nicht zu C # 6 hinzugefügt .BEARBEITEN: Ich habe die Antwort unten aufgrund von Grauenwolfs Einblick in die CLR bearbeitet.
Die CLR ermöglicht es Werttypen, parameterlose Konstruktoren zu haben, C # jedoch nicht. Ich glaube, das liegt daran, dass dadurch die Erwartung entsteht, dass der Konstruktor aufgerufen wird, wenn dies nicht der Fall ist. Betrachten Sie zum Beispiel Folgendes:
Die CLR kann dies sehr effizient tun, indem sie den entsprechenden Speicher zuweist und alles auf Null setzt. Wenn der MyStruct-Konstruktor 1000 Mal ausgeführt werden müsste, wäre dies viel weniger effizient. (In der Tat tut es nicht - wenn Sie tun einen parameterlosen Konstruktor haben, ist es nicht laufen lassen , wenn Sie ein Array zu erstellen, oder wenn Sie eine nicht initialisierte Instanz - Variable.)
Die Grundregel in C # lautet "Der Standardwert für einen Typ kann sich nicht auf eine Initialisierung stützen". Jetzt sind sie konnten erlaubt parameterlos Bauer definiert werden, stellen jedoch dann nicht erforderlich , dass Konstruktor in allen Fällen ausgeführt werden - aber das würde zu mehr Verwirrung geführt haben. (Oder zumindest, also glaube ich, dass das Argument geht.)
BEARBEITEN: Um Ihr Beispiel zu verwenden, was möchten Sie tun, wenn jemand Folgendes tat:
Sollte es 1000 Mal durch Ihren Konstruktor laufen?
EDIT: (Beantwortet ein bisschen mehr der Frage) Der parameterlose Konstruktor wird nicht vom Compiler erstellt. Werttypen müssen für die CLR keine Konstruktoren haben - obwohl sich herausstellt, dass dies möglich ist, wenn Sie sie in IL schreiben. Wenn Sie "
new Guid()
" in C # schreiben , wird eine andere IL ausgegeben als beim Aufruf eines normalen Konstruktors. In dieser SO-Frage finden Sie weitere Informationen zu diesem Aspekt.Ich vermute, dass es im Parameter mit parameterlosen Konstruktoren keine Werttypen gibt. Zweifellos könnte NDepend mir sagen, ob ich es nett genug gefragt habe ... Die Tatsache, dass C # es verbietet, ist ein Hinweis, der groß genug ist, um zu glauben, dass es wahrscheinlich eine schlechte Idee ist.
quelle
Rational[] fractions = new Rational[1000];
auch eine Last von Arbeit verschwenden , wennRational
eine Klasse anstelle einer Struktur ist? Wenn ja, warum haben Klassen einen Standard-Ctor?Rational
es sich um eine Klasse handelt, erhalten Sie ein Array mit 1000 Nullreferenzen.Eine Struktur ist ein Werttyp und ein Werttyp muss einen Standardwert haben, sobald er deklariert wird.
Wenn Sie zwei Felder wie oben deklarieren, ohne sie zu instanziieren, brechen Sie den Debugger,
m
ist null, aberm2
nicht. In Anbetracht dessen würde ein parameterloser Konstruktor keinen Sinn ergeben. Tatsächlich weist jeder Konstruktor einer Struktur nur Werte zu. Das Ding selbst existiert bereits, indem es nur deklariert wird. Tatsächlich könnte m2 im obigen Beispiel sehr gerne verwendet werden und seine Methoden, falls vorhanden, aufgerufen und seine Felder und Eigenschaften manipuliert werden!quelle
new
It, und dann zu versuchen, ihre Mitglieder zu verwendenObwohl die CLR dies zulässt, erlaubt C # nicht, dass Strukturen einen standardmäßigen parameterlosen Konstruktor haben. Der Grund dafür ist, dass Compiler für einen Werttyp standardmäßig weder einen Standardkonstruktor generieren noch einen Aufruf des Standardkonstruktors generieren. Selbst wenn Sie einen Standardkonstruktor definiert haben, wird dieser nicht aufgerufen, und das verwirrt Sie nur.
Um solche Probleme zu vermeiden, lässt der C # -Compiler die Definition eines Standardkonstruktors durch den Benutzer nicht zu. Und da kein Standardkonstruktor generiert wird, können Sie beim Definieren keine Felder initialisieren.
Oder der große Grund ist, dass eine Struktur ein Werttyp ist und Werttypen durch einen Standardwert initialisiert werden und der Konstruktor für die Initialisierung verwendet wird.
Sie müssen Ihre Struktur nicht mit dem
new
Schlüsselwort instanziieren . Es funktioniert stattdessen wie ein Int; Sie können direkt darauf zugreifen.Strukturen dürfen keine expliziten parameterlosen Konstruktoren enthalten. Strukturelemente werden automatisch auf ihre Standardwerte initialisiert.
Ein Standardkonstruktor (ohne Parameter) für eine Struktur kann andere Werte als den Status "Null" festlegen, was ein unerwartetes Verhalten wäre. Die .NET-Laufzeit verbietet daher Standardkonstruktoren für eine Struktur.
quelle
MyStruct s;
indem Sie nicht den von Ihnen bereitgestellten Standardkonstruktor aufrufen.Sie können eine statische Eigenschaft erstellen, die eine standardmäßige "rationale" Nummer initialisiert und zurückgibt:
Und benutze es wie:
quelle
Rational.Zero
könnte etwas weniger verwirrend sein.Kürzere Erklärung:
In C ++ waren Struktur und Klasse 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 Semantik vom Werttyp bereitstellt, während class eine Semantik vom Referenztyp bereitstellt. Wenn Sie über die Auswirkungen dieser Änderung nachdenken, sind auch andere Änderungen sinnvoller, einschließlich des von Ihnen beschriebenen Konstruktorverhaltens.
quelle
new
wirklich geschrieben werden muss, um einen Konstruktor aufzurufen. In C ++ werden Konstruktoren auf versteckte Weise aufgerufen, bei der Deklaration oder Instanziierung von Arrays. In C # ist entweder alles ein Zeiger, also fangen Sie bei null an, oder es ist eine Struktur und muss bei etwas beginnen, aber wenn Sie nicht schreiben könnennew
... (wie Array-Init), würde dies eine starke C # -Regel brechen.Ich habe kein Äquivalent zu einer späten Lösung gesehen, die ich geben werde, also hier ist es.
Verwenden Sie Offsets, um Werte von Standard 0 in einen beliebigen Wert zu verschieben. Hier müssen Eigenschaften verwendet werden, anstatt direkt auf Felder zuzugreifen. (Möglicherweise definieren Sie mit der möglichen Funktion c # 7 besser Felder mit Eigenschaftsbereich, damit sie nicht direkt im Code aufgerufen werden können.)
Diese Lösung funktioniert für einfache Strukturen mit nur Werttypen (kein Ref-Typ oder nullfähige Struktur).
Dies ist anders als diese Antwort, dieser Ansatz nicht besonders Gehäuse ist aber die Verwendung eines Offset , die für alle Bereiche arbeiten.
Beispiel mit Aufzählungen als Feld.
Wie gesagt, dieser Trick funktioniert möglicherweise nicht in allen Fällen, auch wenn struct nur Wertefelder enthält. Nur Sie wissen, ob er in Ihrem Fall funktioniert oder nicht. einfach untersuchen. aber Sie bekommen die allgemeine Idee.
quelle
Nur Sonderfall. Wenn Sie einen Zähler von 0 und einen Nenner von 0 sehen, tun Sie so, als hätte er die Werte, die Sie wirklich wollen.
quelle
Nullable<T>
(zBint?
).new Rational(x,y)
wo x und y zufällig 0 sind?Was ich benutze, ist der Null-Koaleszenz-Operator (??) in Kombination mit einem Hintergrundfeld wie diesem:
Hoffe das hilft ;)
Hinweis: Die Null-Koaleszenzzuweisung ist derzeit ein Funktionsvorschlag für C # 8.0.
quelle
Sie können keinen Standardkonstruktor definieren, da Sie C # verwenden.
Strukturen können Standardkonstruktoren in .NET haben, obwohl ich keine bestimmte Sprache kenne, die dies unterstützt.
quelle
Hier ist meine Lösung für das Dilemma ohne Standardkonstruktor. Ich weiß, dass dies eine späte Lösung ist, aber ich denke, es ist erwähnenswert, dass dies eine Lösung ist.
Ignorieren der Tatsache, dass ich eine statische Struktur namens null habe (Hinweis: Dies gilt nur für alle positiven Quadranten), mit get; set; In C # können Sie try / catch / finally ausführen, um Fehler zu beheben, bei denen ein bestimmter Datentyp nicht vom Standardkonstruktor Point2D () initialisiert wird. Ich denke, dies ist eine schwer fassbare Lösung für einige Leute bei dieser Antwort. Das ist hauptsächlich der Grund, warum ich meine hinzufüge. Wenn Sie die Getter- und Setter-Funktionalität in C # verwenden, können Sie diesen unsinnigen Standardkonstruktor umgehen und versuchen, das zu initialisieren, 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 ein Numerator / Nenner-Setup wünschen, kann dieser Code hilfreich sein. Ich möchte nur wiederholen, dass diese Lösung nicht gut aussieht, unter Effizienzgesichtspunkten wahrscheinlich noch schlechter funktioniert, 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:
quelle
quelle