>

fredag 20 maj 2011

Förstå lambda

Lambda kan vara ett smidigt sätt att få koden kortare och mera lättläst, förutsatt att du förstår lambda, vilket jag hoppas du skall efter att ha läst detta.

Utan Lambda
Följande exempel använder en delegat (countSelection) som inparameter för att avgöra vilka filer som skall räknas i en given mapp. Delegaten innehåller alltså en funktion som körs när variabelnamnet används (countSelection(file))

  1. public int CountFiles(String path, FileFilter countSelection)
  2. {
  3.     DirectoryInfo dir = new DirectoryInfo(path);
  4.     FileInfo[] allFiles = dir.GetFiles();
  5.     int fileCount = 0;
  6.     foreach (var file in allFiles)
  7.         if (countSelection(file))
  8.             fileCount++;
  9.     return fileCount;
  10. }

För att kunna använda funktionen behöver jag veta hur delegaten FileFilter ser ut:
  1. public delegate bool FileFilter(FileInfo file);

Och sedan skapa en funktion som har samma signatur:
  1. public bool TxtFilter(FileInfo fileToTest)
  2. {
  3.     if (fileToTest.Extension == ".txt")
  4.         return true;
  5.     return false;
  6. }

Nu kan jag anropa funktionen genom att ange att det är TxtFilter som skall användas:
  1. CountFiles("c:\\tmp", TxtFilter);

Nästa gång vill jag använda CountFiles med ett annat filter får jag skapa en ny funktion:
  1. public bool LastDayFilter(FileInfo fileToTest)
  2. {
  3.     if (fileToTest.LastAccessTime > DateTime.Now.AddDays(-1))
  4.         return true;
  5.     return false;
  6. }

För att sedan ange den som delegat till anropet:
  1. CountFiles("c:\\tmp", LastDayFilter);

Infoga Lambdat

Koden bli nu inte så lättläst och funktionerna TxtFilter och LastDayFilter skapas alltså bara för att kunna anropa CountFiles funktionen. Det vi kan göra med Lambda är att ta bort dessa "delegat-funktioner" (TxtFilter och LastDayFilter) och istället skriva dessa funktioner direkt i CountFiles anropet. Ungefär så här:

  1. CountFiles("c:\\tmp",  
  2.     public bool TxtFilter(FileInfo fileToTest)
  3.     {
  4.         if (fileToTest.Extension == ".txt")
  5.             return true;
  6.         return false;
  7.     }
  8. );

Detta är dock inte giltig kod, låt oss fixa det. När du skriver siffran 3 som inparameter till en funktion anger du ju inte vad den skall heta vid anropet t.ex. foo(3) detta gäller även för lambda, vi tar bort ordet TxtFilter. Access nivån (public) har inte heller nån betydelse liksom att specificera returvärdet. Att funktionen/lambdat skall returnera en bool är redan definierat av inparameter typen (FileFilter).

Så låt oss ta bort dessa tre. Jag skall också lägga till en avskiljare mellan inparametrar och funktionskroppen i formen av en pil:

  1. CountFiles("c:\\tmp" ,  
  2.     (FileInfo fileToTest)
  3.     =>
  4.     {  
  5.         if (fileToTest.Extension == ".txt")   
  6.             return true ;  
  7.         return false ;
  8.     }
  9. );

Detta är ett giltigt lambda (liksom de resterande i posten). Dock är idén med lambda att det skall vara kort och lättläst så låt oss korta ner lite, vi börjar med inparameterdelen. Funktionen som skall skickas in i CountFiles måste ha en inparamter av typen FileInfo (definierat av FileFilter), därför behöver vi inte skriva ut det. När funktionen består av bara en inparameter behövs inte heller parenteserna och det sista vi kan göra för att korta ner ordentligt är att använda ett kort namn, t.ex. f:

  1. CountFiles("c:\\tmp",  
  2.    f
  3.    =>
  4.     {
  5.         if (f.Extension == ".txt")
  6.             return true;
  7.         return false;
  8.     }
  9. );

Funktionsdelen kan vi också korta ner, låt oss börja med att skriva om if-satsen:

  1. CountFiles("c:\\tmp",
  2.    f
  3.    =>
  4.    {
  5.        return f.Extension == ".txt";
  6.    }
  7. );

När funktionsdelen består av enbart en rad kan vi stryka {  } och ; samt även return nyckelordet (det är redan givet från FileFilter att vi skall returnera en bool). Låt oss även ta bort några radbrytningar:

  1. CountFiles("c:\\tmp", f => f.Extension == ".txt");

Och där har vi nu ett snyggt lambda. Lambdat är en funktion som är namnlös och skickas direkt in i inparametern till CountFiles. f är inparameter i denna funktion och det som står till höger om => är själva funktionen. Funktionsanropet till CountFiles är nu mer lätt läst kod och kan utläsas ungefär som: "Räkna alla filer i tmp katalogen där filerna har txt ändelsen.".