Przejdź do treści

DevStyle - Strona Główna
Wyrażenia lambda w praktyce – MethodWrappers

Wyrażenia lambda w praktyce – MethodWrappers

Maciej Aniserowicz

12 listopada 2008

Backend

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!

Zobacz również