LINQ Coding Guidelines #7–Listen nicht von Hand befüllen

2. März 2015

Noch eine Empfehlung, die man der Kategorie “bau’s Dir halt selber” zuordnen könnte.

Empfehlung: LINQ-Ausdrücke sollten über Extension-Methoden in Collections überführt werden.

 

Im Grunde ganz einfach: Man nutze ToArray() oder ToList():

   1: int[] numbers = { 5, 10, 8, 3, 6, 12};

   2: var numQuery2 = numbers

   3:     .Where(num => num % 2 == 0)

   4:     .OrderBy(n => n)

   5:     .ToArray();

Soweit wird das ja auch gerne gemacht. Solange es sich um ToArray() oder ToList() handelt…

 

Wenn es sich aber um einen anderen Collection-Typ handelt sehe ich oft Code wie folgenden:

   1: public Collection<CashTransaction> GetTransactions(CashTransactionSearchCriteria searchCriteria, long accountId)

   2: {

   3:     Collection<CashTransaction> result = new Collection<CashTransaction>();

   4:  

   5:     using (DataContext entities = new DataContext())

   6:     {

   7:         var transactions = from t in entities.Transaction

   8:                            where t.TransactionID == searchCriteria.TransactionId && t.Account.AccountID == accountId

   9:                            select t;

  10:  

  11:         foreach (var item in transactions)

  12:         {

  13:             var transBE = new CashTransaction();

  14:             transBE.Amount = item.Amount;

  15:             transBE.Timestamp = item.Date;

  16:             transBE.TransactionId = item.TransactionID;

  17:  

  18:             result.Add(transBE);

  19:         }

  20:     }

  21:     return result;

  22: }

Ähnliches hat man bei WPF (oder dieser anderen Plattform, die Microsoft in die Tonne getreten hat), wenn es um ObservableCollection<> geht.

Warum das Ganze zu Fuß erledigen? Warum nicht analog ToCollection() oder ToObservableCollection() aufrufen?

Gibt’s nicht? Kann man sich aber ganz einfach bauen:

   1: public static class LinqExtensions

   2: {

   3:     public static Collection<T> ToCollection<T>(this IEnumerable<T> source)

   4:     {

   5:         var result= new Collection<T>();

   6:         foreach (var item in source)

   7:             result.Add(item);

   8:         return result;

   9:     }

  10: }

ObservableCollection<> macht das noch einfacher, weil es dort einen passenden Konstruktor gibt.

Und schon wird aus obigem Code-Beispiel ein schöner, einfacher LINQ-Ausdruck, ohne foreach-Schleife:

   1: public Collection<CashTransaction> GetTransactions3(CashTransactionSearchCriteria searchCriteria, long accountId)

   2: {

   3:     using (DataContext entities = new DataContext())

   4:     {

   5:         var transactions = from t in entities.Transaction

   6:                            where t.TransactionID == searchCriteria.TransactionId && t.Account.AccountID == accountId

   7:                            select new CashTransaction()

   8:                            {

   9:                                Amount = t.Amount,

  10:                                Timestamp = t.Date,

  11:                                TransactionId = t.TransactionID

  12:                            };

  13:  

  14:         var result = transactions.ToCollection();

  15:         return result;

  16:     }

  17: }

Besser lesbar (noch besser wird es, wenn auch die anderen Guidelines noch befolgt werden ;-)), weil es die Intention deutlich macht, anstatt der Mechanik. Weniger Zeilen Code, also auch weniger Fehlerquellen. Konsistenter, weil alles deklarativ dargestellt ist, und keine Mischung mit imperativem Code entsteht. Weniger Möglichkeiten, Sonderfälle in der Schleife abzuhandeln, wo sie nicht hingehören und die Komplexität erhöhen.

 

Gleiches gilt übrigens auch für Dictionaries. Obwohl es hier in der Regel nicht mal nötig ist eine Hilfsmethode zu schreiben wird ToDictionary() sehr selten verwendet. Statt…

   1: public Dictionary<string, List<Report>> GetReportForDepartments(List<string> departments)

   2: {

   3:     var result = new Dictionary<string, List<Report>>();

   4:     foreach (var department in departments)

   5:     {

   6:         var report = GetReportForDepartment(department);

   7:         result.Add(department, report);

   8:     }

   9:     return result;

  10: }

kann man aber auch folgendes schreiben:

   1: public Dictionary<string, List<Report>> GetReportForDepartments(List<string> departments)

   2: {

   3:     return departments.ToDictionary(

   4:         department => department, 

   5:         department => GetReportForDepartment(department));

   6: }

Und so werden aus sieben Zeilen Code (die ich gelesen haben muss, um sicher zu sein, dass sich da keine Sonderbehandlung versteckt) ein Einzeiler!