Sollten wir immer einen Standardkonstruktor in die Klasse aufnehmen?

75

Mir wurde diese Frage von einem Kollegen gestellt, ob wir immer einen Standardkonstruktor in eine Klasse aufnehmen sollen. Wenn ja warum? Wenn nein, warum nicht?

Beispiel

public class Foo {

    Foo() { }

    Foo(int x, int y) {
        ...
    } 

}

Ich bin auch daran interessiert, dies von Experten zu beleuchten.

Mond
quelle
20
Wie immer - nur wenn es nötig ist.
Arnis Lapsa

Antworten:

120

Sie müssen berücksichtigen, dass der Compiler einen Standardkonstruktor für Sie generiert, wenn Sie keinen überladenen Konstruktor bereitstellen. Das heißt, wenn Sie nur haben

public class Foo
{ 
} 

Der Compiler generiert dies wie folgt:

public class Foo
{ 
    public Foo() { }  
} 

Sobald Sie jedoch den anderen Konstruktor hinzufügen

public class Foo
{ 
    public Foo(int x, int y)
    { 
        // ... 
    }  
} 

Der Compiler generiert den Standardkonstruktor nicht mehr automatisch für Sie. Wenn die Klasse bereits in einem anderen Code verwendet wurde, der auf dem Vorhandensein eines Standardkonstruktors beruhte Foo f = new Foo();, würde dieser Code jetzt beschädigt.

Wenn Sie nicht möchten, dass jemand die Klasse ohne Angabe von Daten initialisieren kann, sollten Sie einen Standardkonstruktor erstellen private, der explizit darauf hinweist, dass Sie verhindern, dass Instanzen ohne Eingabedaten erstellt werden.

Es gibt jedoch Situationen, in denen ein Standardkonstruktor (öffentlich oder privat) bereitgestellt werden muss. Wie bereits erwähnt, erfordern einige Serialisierungstypen einen Standardkonstruktor. Es gibt auch Zeiten, in denen eine Klasse mehrere parametrisierte Konstruktoren hat, aber auch eine Initialisierung auf "niedrigerer Ebene" erfordert. In diesem Fall kann ein privater Standardkonstruktor verwendet werden, der von den parametrisierten Konstruktoren verkettet wird.

public class Foo
{
   private Foo()
   {
      // do some low level initialization here
   }

   public Foo(int x, int y)
      : this()
   {
      // ...
   }

   public Foo(int x, int y, int z)
      : this()
   {
      // ...
   }
}
Scott Dorman
quelle
Ich denke, Sie vermissen ein paar publicModifikatoren (Klassenmitglieder sind privatestandardmäßig) ...
Dan Tao
@ Dan Tao: Ja, das war ich. Danke @Anthony Pegram, dass du sie für mich hinzugefügt hast.
Scott Dorman
1
@ Jon Hanna: Das () nach den Klassennamen wurde behoben. Ihr Kommentar über die falsche Antwort ist jedoch ... nun, falsch. Der erste Code wird kompiliert und ermöglicht es Ihnen, Code wie z Foo f = new Foo();. Sobald Sie jedoch die Klasse in die im dritten Beispiel gezeigte ändern, Foo f = new Foo();wird ein Compilerfehler verursacht - das war mein Punkt. Solange Sie keine Konstruktoren haben, fügt der Compiler den Standardkonstruktor für Sie hinzu. Sobald Sie fügen alle anderen Konstruktor, fügt der Compiler nicht mehr den Standard - Konstruktor und Code, der auf sie vorhanden Pausen verlassen zu sein.
Scott Dorman
2
+1 dies. Das Erstellen leerer Standardkonstruktoren ist IMO ärgerlich, und Tools wie ReSharper markieren es einfach als toten Code.
Womp
1
@ Scott. Ja, nur ein falsches Lesen beim zu schnellen Scannen. Mein Fehler.
Jon Hanna
19

Einige Dinge (wie die Serialisierung) erfordern einen Standardkonstruktor. Darüber hinaus sollte ein Standardkonstruktor jedoch nur hinzugefügt werden, wenn dies sinnvoll ist.

Wenn beispielsweise die Eigenschaften Foo.Xund Foo.Ynach der Erstellung unveränderlich sind, ist ein Standardkonstruktor nicht wirklich sinnvoll. Selbst wenn es für ein "leeres" Foo verwendet würde, wäre ein statischer EmptyAccessor besser auffindbar.

Richard Szalay
quelle
3
Für die Serialisierung ist kein Standardkonstruktor erforderlich. Wenn Sie eine Klasse haben, die serialisierbar sein soll, für die ein Standardkonstruktor keinen Sinn ergibt, implementieren ISerializableSie einen Konstruktor ( privatefalls versiegelt, protectedandernfalls) mit der Signatur ClassName(SerializationInfo info, StreamingContext context), die die Implementierung von widerspiegelt , und fügen Sie ihn hinzu ISerializable.GetObjectData.
Jon Hanna
12

Ich würde nein sagen , definitiv nicht immer . Angenommen, Sie haben eine Klasse mit einigen schreibgeschützten Feldern, die auf einen bestimmten Wert initialisiert werden müssen, und es gibt keine vernünftigen Standardeinstellungen (oder Sie möchten nicht, dass es solche gibt)? In diesem Szenario halte ich einen parameterlosen Konstruktor nicht für sinnvoll.

Dan Tao
quelle
4
Stimme voll und ganz zu. Klassen sollten immer in einem gültigen Zustand instanziiert werden. Wenn Sie einen Standard-Ctor haben und Eigenschaften nach der Erstellung festlegen, wann ist die Instanz gültig? Nach Einstellungen einige Eigenschaften? Nachdem Sie alle eingestellt haben? Wer weiß.
Cumbayah
6

Ein Standardkonstruktor ist nur dann eine gute Idee, wenn es sinnvoll ist, ein solches Objekt zu haben.

Wenn Sie von einem solchen Konstruktor ein Objekt erzeugen, das sich nicht in einem gültigen Zustand befindet, kann es nur einen Fehler einführen.

Jon Hanna
quelle
2

Ja Es ist besser, einen Standardkonstruktor zu haben, um Verwirrung zu vermeiden. Ich habe gesehen, dass Leute in einem Standardkonstruktor nichts tun (selbst in Microsoft-eigenen Klassen), es aber trotzdem gerne behalten, da Objekte automatisch Standard (Typ) erhalten. Die Klassen, die keinen Standardkonstruktor angeben, werden von .NET automatisch für Sie hinzugefügt.

Serialisierung benötigt Standardkonstruktor, wenn Sie vorhandene Serializer verwenden, da dies für Allzweck-Serializer sinnvoll ist. Andernfalls müssen Sie Ihre eigene Implementierung erstellen.

Abhishek
quelle
1
Für die Serialisierung ist kein Standardkonstruktor erforderlich. Wenn Sie eine Klasse haben, die serialisierbar sein soll, für die ein Standardkonstruktor keinen Sinn ergibt, implementieren ISerializableSie einen Konstruktor ( privatefalls versiegelt, protectedandernfalls) mit der Signatur ClassName(SerializationInfo info, StreamingContext context), die die Implementierung von widerspiegelt , und fügen Sie ihn hinzu ISerializable.GetObjectData.
Jon Hanna
Punkt gezählt. Eigentlich weiß ich es, aber im Allgemeinen wollen die Leute einfach sein. Aber Sie haben der Antwort einen Punkt hinzugefügt. Vielen Dank.
Abhishek
1

Beachten Sie bei der Verwendung von struct anstelle von class, dass es keine Möglichkeit gibt , den Standardkonstruktor wegzulassen, und dass Sie ihn auch nicht selbst definieren können. Stellen Sie daher unabhängig davon, welche Konstruktoren Sie definieren, sicher, dass der Standardstatus des struct (wenn alle Variablen auf ihren Standardzustand gesetzt sind (normalerweise 0 für Werttypen und null für Referenztypen), wird Ihre Implementierung der Struktur nicht unterbrochen.

Øyvind Bråthen
quelle
0

In den meisten Fällen ist ein Standardkonstruktor eine gute Idee. Da Sie jedoch das Wort "immer" verwenden, ist nur ein Gegenbeispiel erforderlich. Wenn Sie sich das Framework ansehen, werden Sie viel finden. Zum Beispiel System.Web.HttpContext.

Joe
quelle
0

Ein generischer Typ kann nur mit C # -Mitteln (ohne Reflexion) instanziiert werden, wenn er einen Standardkonstruktor hat. Außerdem muss die new()generische Typbeschränkung angegeben werden:

void Construct<T>()
    where T : new()
{
    var t = new T();
    ...
}

Das Aufrufen dieser Methode unter Verwendung eines Typs als generisches Typargument ohne Standardkonstruktor führt zu einem Compilerfehler.

Olivier Jacot-Descombes
quelle