Ich habe kürzlich in C # und Java programmiert und bin gespannt, wo ich meine Klassenfelder am besten initialisieren kann.
Soll ich es bei der Erklärung tun?:
public class Dice
{
private int topFace = 1;
private Random myRand = new Random();
public void Roll()
{
// ......
}
}
oder in einem Konstruktor?:
public class Dice
{
private int topFace;
private Random myRand;
public Dice()
{
topFace = 1;
myRand = new Random();
}
public void Roll()
{
// .....
}
}
Ich bin wirklich neugierig, was einige von euch Veteranen für die beste Praxis halten. Ich möchte konsequent sein und mich an einen Ansatz halten.
Antworten:
Meine Regeln:
null
,false
,0
,0.0
...).quelle
default(T)
ist immer der Wert, für den eine interne binäre Darstellung vorliegt0
.foreach
Schleife mit "Dies wiederholt das Folgende für alle Elemente in der Liste" kommentieren , müssen wir die Standardwerte von C # nicht ständig anpassen. Wir müssen auch nicht so tun, als hätte C # eine nicht initialisierte Semantik. Da das Fehlen eines Wertes eine klare Bedeutung hat, ist es in Ordnung, ihn wegzulassen. Wenn es ideal ist, explizit zu sein, sollten Sie immernew
neue Delegaten erstellen (wie in C # 1 erforderlich). Aber wer macht das? Die Sprache ist für gewissenhafte Programmierer konzipiert.In C # spielt es keine Rolle. Die beiden von Ihnen angegebenen Codebeispiele sind absolut gleichwertig. Im ersten Beispiel erstellt der C # -Compiler (oder ist es die CLR?) Einen leeren Konstruktor und initialisiert die Variablen so, als wären sie im Konstruktor (dies hat eine leichte Nuance, die Jon Skeet in den Kommentaren unten erklärt). Wenn es bereits einen Konstruktor gibt, wird jede Initialisierung "oben" in den oberen Bereich verschoben.
In Bezug auf bewährte Verfahren ist das erstere weniger fehleranfällig als das letztere, da jemand leicht einen anderen Konstruktor hinzufügen und vergessen könnte, ihn zu verketten.
quelle
Die Semantik von C # unterscheidet sich hier geringfügig von Java. In C # wird die Zuweisung in der Deklaration durchgeführt, bevor der Superklassenkonstruktor aufgerufen wird. In Java erfolgt dies unmittelbar danach, wodurch die Verwendung von 'this' ermöglicht wird (besonders nützlich für anonyme innere Klassen), und bedeutet, dass die Semantik der beiden Formen wirklich übereinstimmt.
Wenn Sie können, machen Sie die Felder endgültig.
quelle
Ich denke, es gibt eine Einschränkung. Ich habe einmal einen solchen Fehler begangen: Innerhalb einer abgeleiteten Klasse habe ich versucht, die von einer abstrakten Basisklasse geerbten Felder bei der Deklaration zu "initialisieren". Das Ergebnis war, dass es zwei Sätze von Feldern gab, eines ist "Basis" und eines ist das neu deklarierte, und das Debuggen hat mich einige Zeit gekostet.
Die Lektion: Um geerbte Felder zu initialisieren , müssen Sie dies im Konstruktor tun.
quelle
derivedObject.InheritedField
, bezieht es sich dann auf Ihr Basisfeld oder das abgeleitete?Wenn Sie den Typ in Ihrem Beispiel annehmen, ziehen Sie es definitiv vor, Felder im Konstruktor zu initialisieren. Die Ausnahmefälle sind:
Ich denke immer an die Feldliste oben in einer Klasse als Inhaltsverzeichnis (was hier enthalten ist, nicht wie es verwendet wird) und den Konstruktor als Einführung. Methoden sind natürlich Kapitel.
quelle
Was ist, wenn ich es dir sage, es kommt darauf an?
Ich initialisiere im Allgemeinen alles und mache es auf konsistente Weise. Ja, es ist zu explizit, aber es ist auch etwas einfacher zu warten.
Wenn wir uns Sorgen um die Leistung machen, initialisiere ich nur das, was getan werden muss, und platziere es in den Bereichen, in denen es das Beste für das Geld gibt.
In einem Echtzeitsystem frage ich mich, ob ich überhaupt die Variable oder Konstante brauche.
Und in C ++ mache ich oft so gut wie keine Initialisierung an beiden Stellen und verschiebe sie in eine Init () - Funktion. Warum? Wenn Sie in C ++ etwas initialisieren, das während der Objektkonstruktion eine Ausnahme auslösen kann, öffnen Sie sich für Speicherlecks.
quelle
Es gibt viele und verschiedene Situationen.
Ich brauche nur eine leere Liste
Die Situation ist klar. Ich muss nur meine Liste vorbereiten und verhindern, dass eine Ausnahme ausgelöst wird, wenn jemand der Liste ein Element hinzufügt.
Ich kenne die Werte
Ich weiß genau, welche Werte ich standardmäßig haben möchte oder ich muss eine andere Logik verwenden.
oder
Leere Liste mit möglichen Werten
Manchmal erwarte ich standardmäßig eine leere Liste mit der Möglichkeit, Werte über einen anderen Konstruktor hinzuzufügen.
quelle
In Java bedeutet ein Initialisierer mit der Deklaration, dass das Feld immer auf die gleiche Weise initialisiert wird, unabhängig davon, welcher Konstruktor verwendet wird (wenn Sie mehr als einen haben) oder die Parameter Ihrer Konstruktoren (wenn sie Argumente haben), obwohl ein Konstruktor möglicherweise später Ändern Sie den Wert (falls er nicht endgültig ist). Die Verwendung eines Initialisierers mit einer Deklaration legt dem Leser nahe, dass der initialisierte Wert der Wert ist, den das Feld in allen Fällen hat , unabhängig davon, welcher Konstruktor verwendet wird und unabhängig von den Parametern, die an einen Konstruktor übergeben werden. Verwenden Sie daher einen Initialisierer mit der Deklaration nur, wenn und immer dann, wenn der Wert für alle erstellten Objekte gleich ist.
quelle
Das Design von C # legt nahe, dass die Inline-Initialisierung bevorzugt wird oder nicht in der Sprache erfolgt. Jedes Mal, wenn Sie einen Querverweis zwischen verschiedenen Stellen im Code vermeiden können, sind Sie im Allgemeinen besser dran.
Es gibt auch die Frage der Konsistenz mit der statischen Feldinitialisierung, die für eine optimale Leistung inline sein muss. Die Richtlinien für das Framework-Design für das Konstruktordesign lauten wie folgt:
"Überlegen" bedeutet in diesem Zusammenhang, dies zu tun, es sei denn, es gibt einen guten Grund, dies nicht zu tun. Bei statischen Initialisierungsfeldern ist ein guter Grund, wenn die Initialisierung zu komplex ist, um inline codiert zu werden.
quelle
Konsequent zu sein ist wichtig, aber das ist die Frage, die Sie sich stellen sollten: "Habe ich einen Konstruktor für irgendetwas anderes?"
Normalerweise erstelle ich Modelle für Datenübertragungen, die die Klasse selbst nur als Gehäuse für Variablen verwendet.
In diesen Szenarien habe ich normalerweise keine Methoden oder Konstruktoren. Es wäre für mich albern, einen Konstruktor für den ausschließlichen Zweck der Initialisierung meiner Listen zu erstellen, zumal ich sie gemäß der Deklaration initialisieren kann.
Wie viele andere gesagt haben, hängt es von Ihrer Verwendung ab. Halten Sie es einfach und machen Sie nichts extra, was Sie nicht müssen.
quelle
Betrachten Sie die Situation, in der Sie mehr als einen Konstruktor haben. Wird die Initialisierung für die verschiedenen Konstruktoren unterschiedlich sein? Wenn sie gleich sind, warum dann für jeden Konstruktor wiederholen? Dies steht im Einklang mit der kokos-Anweisung, kann jedoch nicht mit Parametern zusammenhängen. Angenommen, Sie möchten ein Flag behalten, das zeigt, wie das Objekt erstellt wurde. Dann würde dieses Flag für verschiedene Konstruktoren unabhängig von den Konstruktorparametern unterschiedlich initialisiert. Wenn Sie andererseits für jeden Konstruktor dieselbe Initialisierung wiederholen, besteht die Möglichkeit, dass Sie (unbeabsichtigt) den Initialisierungsparameter in einigen Konstruktoren ändern, in anderen jedoch nicht. Das Grundkonzept hier ist also, dass gemeinsamer Code einen gemeinsamen Speicherort haben und möglicherweise nicht an verschiedenen Speicherorten wiederholt werden sollte.
quelle
Das Festlegen des Werts in der Deklaration hat einen leichten Leistungsvorteil. Wenn Sie es im Konstruktor festlegen, wird es tatsächlich zweimal festgelegt (zuerst auf den Standardwert, dann im ctor zurückgesetzt).
quelle
Normalerweise versuche ich, dass der Konstruktor nichts anderes tut, als die Abhängigkeiten abzurufen und die zugehörigen Instanzmitglieder damit zu initialisieren. Dies erleichtert Ihnen das Leben, wenn Sie Ihre Klassen einem Unit-Test unterziehen möchten.
Wenn der Wert, den Sie einer Instanzvariablen zuweisen möchten, von keinem der Parameter beeinflusst wird, die Sie an Ihren Konstruktor übergeben, weisen Sie ihn zur Deklarationszeit zu.
quelle
Keine direkte Antwort auf Ihre Frage nach der Best Practice, aber ein wichtiger und verwandter Auffrischungspunkt ist, dass Sie im Fall einer generischen Klassendefinition entweder den Compiler mit Standardwerten initialisieren lassen oder eine spezielle Methode zum Initialisieren von Feldern verwenden müssen auf ihre Standardwerte (wenn dies für die Lesbarkeit des Codes absolut notwendig ist).
Die spezielle Methode zum Initialisieren eines generischen Felds auf seinen Standardwert lautet wie folgt:
quelle
Wenn Sie keine Logik oder Fehlerbehandlung benötigen:
Wenn Sie eine Logik oder Fehlerbehandlung benötigen:
Von https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html .
quelle