Top 10 Fehler–#2: Default-Werte und uninitialisierte Wert

4. Juni 2014

Dies ist Teil 2 der kleinen Serie, die ich als Kommentar zum Blog-Beitrag Top 10 Mistakes that C# Programmers Make begonnen habe.

Heute geht es um…

 

Common Mistake #2: Misunderstanding default values for uninitialized variables

In C#, value types can’t be null. By definition, value types have a value, and even uninitialized variables of value types must have a value. This is called the default value for that type.  This leads to the following, usually unexpected result when checking if a variable is uninitialized:

   1: class Program {

   2:     static Point point1;

   3:     static Pen pen1;

   4:     static void Main(string[] args) {

   5:         Console.WriteLine(pen1 == null);      // True

   6:         Console.WriteLine(point1 == null);    // False (huh?)

   7:     }

   8: }

[…]

When you’re checking to see if a variable has been initialized or not, make sure you know what value an uninitialized variable of that type will have by default and don’t rely on it being null.

Auch dieses Problem halte ich eher für akademisch als praxisrelevant. Zum einen meldet sich der Compiler im obigen Beispiel ohnehin mit dem Hinweis, dass die Variablen nie zugewiesen wurden (was Patrick in seinen Ausführungen unterschlägt). Bei einer Zuweisung wird aber null nicht für Werttypen akzeptiert, so dass spätestens hier der Unterschied auffällt.

Zum zweiten dürften lokale Variablen häufiger sein als statische Member. In diesem Falle werden die Variablen aber nicht implizit initialisiert, sondern der Compiler erzwingt eine explizite Initialisierung vor der Verwendung – und bei dieser wird wieder null nicht akzeptiert:

1>d: est…Program.cs(16,34,16,38): error CS0037: Cannot convert null to ‘System.Drawing.Point’ because it is a non-nullable value type

Ergo: Wie schon beim letzten “Mistake” ist auch dieses nicht wirklich praxisrelevant. Wer bei Initialisierung, Parameterübergabe etc. gezwungen wird, ein Null-Objekt (etwa Point.Empty) zu übergeben, der wird nicht plötzlich gegen null vergleichen. In Einzelfällen mag das vorkommen, aber das qualifiziert kein “Common Mistake”.

Anmerkung: Ausnahme ist vielleicht der Einsatz von Generics. Aber für diese wurde default(T) geschaffen, was nach meiner Erfahrung auch verwendet wird.

 

Es gibt allerdings beim Thema Initialisierung etwas, dass ich öfter zu Gesicht bekomme…

Unnötige Initialisierung

   1: Size size1= Size.Empty;

   2: string title = "";

   3: size1 = form.Bounds.Size;

   4: title = form.Text;

Ich habe noch keinen Entwickler in flagranti dabei ertappt, solchen Code zu schreiben. Daher kann ich nur die Vermutung anstellen, dass mancher die Zuweisung bei der Deklaration tatsächlich für nötig hält. Was natürlich nicht der Fall ist! Variablen müssen vor der Benutzung initialisiert werden, aber nicht bei der Deklaration.

In vielen Fällen ist die einfache (und kürzere) Variante die, mit der Deklaration auch die Initialisierung mit dem korrekten Wert durchzuführen:

   1: Size size1= form.Bounds.Size;

   2: string title = form.Text;

Falls die Deklaration und die Initialisierung tatsächlich getrennt sein müssen, etwa wenn eine Bedingung ins Spiel kommt, dann ist die überflüssige Initialisierung sogar schädlich:

   1: string title= "";

   2: if (isModified)

   3: {

   4:     title = form.Text + " *";

   5: }

   6: else

   7: {

   8:     [...]

   9: }

  10: Console.WriteLine(title);

Hätte man title nicht mit einem Leerstring initialisiert (der hier ohnehin wieder überschrieben werden muss) dann würde einen der Compiler darauf hinweisen, dass die Zuweisung im else-Zweig vergessen wurde (error CS0165: Use of unassigned local variable ‘title’). So wird der Fehler überdeckt und bleibt je nach Konstellation womöglich lange unentdeckt.