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!