Wyrażenia lambda w praktyce – MethodWrappers

4

Kiedyś już wspominałem o fajnym wykorzystaniu wyrażeń lambda w poście Wyrażenia lambda i extension methods – aspektejszyn. Dzisiaj przytoczę kolejne przykłady takiego ich zastosowania, które potrafią znacząco ograniczyć ilość powtarzalnego kodu w kodzie (badaniem ilości cukru w cukrze zajął się kto inny).

Całość wrzuciłem sobie do statycznej klasy MethodWrappers, przyjrzyjmy się jej zawartości…

IgnoreExceptions()

Celem tej metody jest maksymalne skrócenie takiego potwora:

  1:  try
  2:  {
  3:  	CanThrow();
  4:  }
  5:  catch
  6:  {
  7:  }
  8:  try
  9:  {
 10:  	WillPossiblyThrowException();
 11:  }
 12:  catch
 13:  {
 14:  
 15:  }
 16:  try
 17:  {
 18:  	ShouldNotThrowButWhoKnows();
 19:  }
 20:  catch
 21:  {
 22:  
 23:  }

Chcemy, aby wykonały się WSZYSTKIE metody, niezależnie od wyrzucanych wyjątków, które zignorujemy. Tak, wiem, łykanie wszystkiego w try/catch jest praktyką NIEPOLECANĄ, jednak skądś takie brzydale znamy, prawda?

Pierwszy upraszczacz skraca kod do:

  1:  MethodWrappers.IgnoreExceptions(
  2:  	CanThrow,
  3:  	WillPossiblyThrowException,
  4:  	ShouldNotThrowButWhoKnows
  5:  	);

Prawda że ładniej? Oto kod podspodowy:

  1:  /// <summary>
  2:  /// Executes given operations catching all exceptions.
  3:  /// Exceptions thrown by the operations are ignored and do not bubble up to the caller.
  4:  /// Exceptions do not prevent other operations from being executed.
  5:  /// </summary>
  6:  public static void IgnoreExceptions(params Action[] operations)
  7:  {
  8:  	foreach (var operation in operations)
  9:  	{
 10:  		try
 11:  		{
 12:  			operation();
 13:  		}
 14:  		catch
 15:  		{
 16:  		}
 17:  	}
 18:  }

ExecuteTryCatch()

Druga z metod również tyczy się wyjątków. Ona z kolei ma skrócić taki kod:

  1:  try
  2:  {
  3:  	CanThrow();
  4:  }
  5:  catch
  6:  {
  7:  	Hilfe();
  8:  }
  9:  try
 10:  {
 11:  	WillPossiblyThrowException();
 12:  }
 13:  catch
 14:  {
 15:  	Rollback();
 16:  }
 17:  try
 18:  {
 19:  	ShouldNotThrowButWhoKnows();
 20:  }
 21:  catch
 22:  {
 23:  	Log();
 24:  }

W efekcie uzyskamy tylko 3 linijki – pozbywamy się ohydnej “rozwlekłości” nie tracąc jednocześnie czytelności! (z tą czytelnością pewnie nie wszyscy się zgodzą, ale… to kwestia przyzwyczajenia do lamd, zawsze można wstawić dodatkowy ENTER tu czy tam):

  1:  MethodWrappers.ExecuteTryCatch(CanThrow, Hilfe);
  2:  MethodWrappers.ExecuteTryCatch(WillPossiblyThrowException, Rollback);
  3:  MethodWrappers.ExecuteTryCatch(ShouldNotThrowButWhoKnows, Log);

Kod ową rozwlekłość w sobie bohatersko zatrzymujący:

  1:  /// <summary>
  2:  /// Executes the <paramref name="tryOperation"/> and performs the <paramref name="catchOperation"/> in case of exception.
  3:  /// </summary>
  4:  /// <param name="tryOperation">Operation to be executed.</param>
  5:  /// <param name="catchOperation">Operation to be executed in case of exception.</param>
  6:  public static void ExecuteTryCatch(Action tryOperation, Action catchOperation)
  7:  {
  8:  	try
  9:  	{
 10:  		tryOperation();
 11:  	}
 12:  
 13:  	catch
 14:  	{
 15:  		catchOperation();
 16:  	}
 17:  }

AggregateResults()

Kolejny kodoskracacz służy już do czegoś z wyjątkami niezwiązanego. Bardziej nawet kładę w nim nacisk na czytelność kodu niż na jego ilość. Zobaczmy PRZED:

  1:  List<int> positiveResults = new List<int>();
  2:  int temp = FirstOperationReturningInt();
  3:  if (temp >= 0)
  4:  	positiveResults.Add(temp);
  5:  temp = Sum(1, 2);
  6:  if (temp >= 0)
  7:  	positiveResults.Add(temp);

Cóż nas zatem interesuje? Chcemy mieć wyniki działania wszystkich metod zebrane w jednej kolekcji. ALE! Wyniki ujemne odrzucamy. Zbyt wiele razy miałem do czynienia z wyżej pokazanym kodem, czy nie ładniej tak?:

  1:  List<int> positiveResults = MethodWrappers.AggregateResults(
  2:  	number => number >= 0,
  3:  	FirstOperationReturningInt,
  4:  	() => Sum(1, 2)
  5:  	);

Najpierw definiujemy warunek, czyli JAKIE wartości mają być zapamiętywane, a potem listujemy operacje do wykonania. Nie wiem jak wam, ale mi się podoba. Kod:

  1:  /// <summary>
  2:  /// Executes each operation and creates an array of their results if.
  3:  /// Only results that satisfy a given condition are aggregated.
  4:  /// </summary>
  5:  /// <typeparam name="T">Type of a value returned by each of the actions.</typeparam>
  6:  /// <param name="condition">Condition that must be satisfied for the result to be aggregated.</param>
  7:  /// <param name="operations">Array of aggregated results of the operations.</param>
  8:  public static List<T> AggregateResults<T>(Predicate<T> condition, params Func<T>[] operations)
  9:  {
 10:  	List<T> results = new List<T>(operations.Length);
 11:  
 12:  	foreach (var operation in operations)
 13:  	{
 14:  		T result = operation();
 15:  		if (condition == null || condition(result))
 16:  			results.Add(result);
 17:  	}
 18:  
 19:  	return results;
 20:  }

Przytoczona lista nie jest imponująco długa, nie o to jednak chodzi. Chodzi o ideę, o eksperymentowanie, o estetykę… Niechaj nasz kod nie powoduje chęci odwiedzin u porcelanowego bożka!
Mam nadzieję, że w komentarzach zobaczymy więcej przykładów. Mam również nadzieję, że moja krucjata o nauczenie się C# 3.0 przez tych, którzy się go boją, przynosi efekty!

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.

4 Comments

  1. Wojciech Gebczyk on

    Tak patrze i zaraz kojarzy mi sie pasqdne “On Error Resume Next”. Brrr…
    ;-)

  2. Pro przykłady. :) Przyzwyczajenia do lambd są w dalszej perspektywie czymś co naprawdę może wyjść każdemu na dobre. ;)

  3. Maćku,
    W Twoich pierwszych dwóch przykładach nie widzę zastosowania lambda expressions. Używasz prostych delegatów (choć lambda to po prostu anonimowe delegaty w innym zapisie). Używając trochę generycznych typów i extension methods przykład mógłby wyglądać ciekawiej i być bardziej przydatny (np. przyjmować parametry i może coś zwracać):
       public static class MethodHelpers
       {
           public static void IgnoreExceptions(this T subject, params Action[] actions)
           {
               foreach (var action in actions)
               {
                   try
                   {
                       action.Invoke(subject);
                   }
                   catch(Exception ex)
                   {
                       //log, show message box, do whatever you like
                   }
               }
           }
           public static R IgnoreExceptions
    (this T subject, Func action)
           {
               try
               {
                   return action.Invoke(subject);
               }
               catch
               {
                   //log, show message box, do whatever you like
                   return default(R);
               }
           }
       }
    a użycie:
               SomeClass sc = new SomeClass();
               string param = “WooBoo”;
               sc.IgnoreExceptions(o => o.DoSomething(param+”1″)
                   ,o => o.DoSomething(param+”2″)
                   ,o => o.DoSomething(param+”3″)
                   ,o => o.DoSomething(param+”4″)
                   ,o => o.DoSomething(param+”5″));
               string result = sc.IgnoreExceptions(o => o.SaySomething(param));
    Zastanawiam się czy bawiąc się klasą Expression
    nie dało by się wyodrębnić i opakować w try/cach wszystkich wywołań z czegoś takiego:
    sc.IgnoreException(o=>{
    o.DoSomething(param+”1″);
    o.DoSomething(param+”2″);
    o.DoSomething(param+”3″);
    });