LINQ Coding Guidelines #11 – Der Einsatz von “var”

4. Mai 2015

Es gibt Leute, die halten var für die Quelle des Bösen, andere haben eine ausgewogenere Haltung, der eher der Intention nahe kommt.

Empfehlung:
Bei LINQ-Ausdrücken ist der Einsatz von var zu empfehlen.

 

Man mag geteilter Ansicht darüber sein, ob es Sinn macht, einfache Datentypen wie int oder string durch var zu ersetzen. Aber sobald es um Templates geht rechtfertigt sich der Einsatz von var alleine durch den Schreibaufwand. Und spätestens im Zusammenhang mit LINQ wird type inference, also die implizite Typisierung, zum beherrschenden Aspekt.

Bei LINQ ergibt sich der Typ aus der Kette der Aufrufe – und er ändert sich potentiell mit jedem Select(). Dabei ist nicht nur der Datentyp der Elemente relevant – hier bekommt var durch anonyme Typen seine besondere Rechtfertigung – sondern auch beim Typ der Enumeration, also IEnumerable<> oder IQueryable<>.

Ich zumindest baue bei der Entwicklung gerade komplexe LINQ-Anweisungen oft in einzelnen Schritten auf, wodurch sich der Typ ständig ändert. Aber auch bei fertigem Code ist eine Aufteilung in mehrere Schritte keine Seltenheit, z.B. um deutlich zu machen, dass der LINQ-Provider gewechselt wird. Wenn hier Änderungen anstehen kann sich leicht eine falsche Deklaration einschleichen, ohne dass das offensichtlich wird.

Ein (konstruiertes) Beispiel:

   1: public IEnumerable<AllocationTimeSpan> GetAllTimeSpans()

   2: {

   3:     DateTime currentDate = DateTime.Today;

   4:     IEnumerable<AllocationTimeSpan> allTimeSpans = _context

   5:         .AllocationSet

   6:         .OfType<Allocation>()

   7:         .Where(a => a.AllocatedFrom <= currentDate && a.AllocatedTo >= currentDate)

   8:         .Select(a => new AllocationTimeSpan

   9:         {

  10:             From = a.AllocatedFrom,

  11:             To = a.AllocatedTo,

  12:             Status = (Status)(a.Status),

  13:         })

  14:         .Distinct()

  15:         .OrderBy(ts => ts.From)

  16:         .ToArray();

  17:     return allTimeSpans;

  18: }

Ein Aufruf gegen die Datenbank, samt Filterung, Sortierung, etc.. Keine große Auffälligkeit. Nur findet mir etwas zu viel auf einmal statt, also trenne ich das:

   1: public IEnumerable<AllocationTimeSpan> GetAllTimeSpans4b()

   2: {

   3:     // erstmal die daten...

   4:     IEnumerable<Allocation> allocations = _context

   5:         .AllocationSet

   6:         .OfType<Allocation>();

   7:     // dann selektion und projektion...

   8:     DateTime currentDate = DateTime.Today;

   9:     IEnumerable<AllocationTimeSpan> allTimeSpans = allocations

  10:         .Where(a => a.AllocatedFrom <= currentDate && a.AllocatedTo >= currentDate)

  11:         .Select(a => new AllocationTimeSpan

  12:         {

  13:             From = a.AllocatedFrom,

  14:             To = a.AllocatedTo,

  15:             Status = (Status)(a.Status),

  16:         })

  17:         .Distinct()

  18:         .OrderBy(ts => ts.From)

  19:         .ToArray();

  20:     return allTimeSpans;

  21: }

Eine Zwischenvariable eingeführt, nicht weiter nachgedacht, und schon geht die Abfrage ungefiltert gegen die Datenbank! Ein dummer Flüchtigkeitsfehler mit womöglich weitreichenden Auswirkungen. Vom Schreibaufwand mal ganz abgesehen.

 

Ergo: Der Einsatz von var reduziert sowohl den initialen Schreibaufwand, als auch den Änderungsaufwand. Und er eliminiert Fehlerquellen.