Oto praktyczny przykład użycia wyrażeń lambda i metod rozszerzających, który lekko i prymitywnie “ociera się” o programowanie aspektowe i imituje jego podstawowe założenia:
Najpierw tworzymy klasę zawierającą roszerzenia dla windowsowych formatek (ewentualnie naszej klasy bazowej):
1: public static class FormExtensions
2: {
3: public static void SetWaitingCursorFor(this Form instance, Action operation)
4: {
5: Cursor currentCursor = instance.Cursor;
6: instance.Cursor = Cursors.WaitCursor;
7: try
8: {
9: operation();
10: }
11: finally
12: {
13: instance.Cursor = currentCursor;
14: }
15: }
16: }
Zwracamy uwagę na try/finally – nie zależy nam na ukryciu błędów, ale powinniśmy wrócić do stanu sprzed wywołania metody (czyli poprzedniego kursora) niezależnie od okoliczności.
Teraz w naszych formach możemy wygodnie opakować każdą długotrwałą operację w kursorową klepsydrę, na przykład:
this.SetWaitingCursorFor(() => Thread.Sleep(2000));
Idąc tym tropem mamy możliwość stworzenia całej biblioteki podobnych “aspektów” oferujących więcej niż banalną zmianę kursora, ale także logowanie czy obsługę wyjątków (czyli najbardziej wyświechtane przykłady jeżeli chodzi o aspect-oriented programming). Można je dowolnie zagnieżdżać i uzupełniać o dodatkowe parametry. Zobaczmy:
Dodajmy kolejną metodę rozszerzającą. Tym razem wypiszemy na konsolę tekst przed wykonaniem operacji, po jej wykonaniu, oraz zażądamy komunikatu, który ma się pojawić w razie wyjątku:
1: public static void LogToConsole(this Form instance, string beforeMessage, string afterMessage, string exceptionMessage, Action operation)
2: {
3: Console.WriteLine(beforeMessage);
4: try
5: {
6: operation();
7: Console.WriteLine(afterMessage);
8: }
9: catch
10: {
11: Console.WriteLine(exceptionMessage);
12: throw;
13: }
14: }
Wywołanie podobne jak poprzednio – z uwzględnieniem dodatkowych parametrów:
this.LogToConsole(“przed wykonaniem”, “po wykonaniu”, “O Wszechmocny Mahomecie, wyjatek!!!”, () => Thread.Sleep(2000));
I teraz najfajniejsze, czyli jak to połączyć? A – normalnie – po prostu jedno z tych wywołań przekazujemy w parametrze do drugiego. Dla przykładu: zmianę kursora “opakujemy” jeszcze w logowanie:
1: this.LogToConsole(“przed wykonaniem”, “po wykonaniu”, “O Wszechmocny Mahomecie, wyjatek!!!”,
2: () => this.SetWaitingCursorFor(
3: () => Thread.Sleep(2000)
4: )
5: );
Bardzo przyjemny mechanizm. Nie dajmy sie jednak zwariować i nie zapominajmy, że to wartstwa UI. Zaserwowany tu sposób (szczególnie logowanie!) jest akurat w tym przypadku nie do przyjęcia! Miałem na celu jedynie przybliżenie takich możliwości.
Podobne sztuczki można oczywiście stosować wszędzie. Szczególnie polecam analizę klasycznej warstwy dostępu do danych, z jej każdorazowym otwieraniem i zamykaniem połączenia, wypełnianiem DataSeta, czytaniem z DataReadera, obsługą wyjątków, logowaniem, transformacją wyników do własnego modelu obiektowego i O’Bóg wie co jeszcze… Tutaj jest naprawdę duże pole do popisu. Zachęcam do wypróbowania i komentarzy!
Dodatkowo mały tip, a właściwie tipik:
Standardowa konstrukcja wyrażenia lambda:
(arg1, arg2) => arg1 + arg2
Konstrukcja wyrażenia lambda z wieloma instrukcjami:
1: (arg1, arg2) =>
2: {
3: Console.WriteLine(“666”);
4: return arg1 + arg2;
5: }
Jak można zauważyć – w tym przypadku wymagane jest jawne zwrócenie konkretnej wartości przy użyciu return.