Wyrażenia lambda i extension methods – aspektejszyn

0

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.

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Share.

About Author

Programista, trener, prelegent, pasjonat, blogger. Autor podcasta programistycznego: DevTalk.pl. Jeden z liderów Białostockiej Grupy .NET i współorganizator konferencji Programistok. Od 2008 Microsoft MVP w kategorii .NET. Więcej informacji znajdziesz na stronie O autorze. Napisz do mnie ze strony Kontakt. Dodatkowo: Twitter, Facebook, YouTube.

Comments are closed.