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!
Tak patrze i zaraz kojarzy mi sie pasqdne “On Error Resume Next”. Brrr…
;-)
Pro przykłady. :) Przyzwyczajenia do lambd są w dalszej perspektywie czymś co naprawdę może wyjść każdemu na dobre. ;)
Maćku,(this T subject, params Action [] actions)(this T subject, Func action) nie dało by się wyodrębnić i opakować w try/cach wszystkich wywołań z czegoś takiego:
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
{
foreach (var action in actions)
{
try
{
action.Invoke(subject);
}
catch(Exception ex)
{
//log, show message box, do whatever you like
}
}
}
public static R IgnoreExceptions
{
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
sc.IgnoreException(o=>{
o.DoSomething(param+”1″);
o.DoSomething(param+”2″);
o.DoSomething(param+”3″);
});