Oczywiście zalecenia zaleceniami, a życie życiem :) Jednym z koronnych przykładów złamania tego zalecenia jest klasa ChannelFactory<TChannel> która pozwala na komunikację z webserwisem WCF. Jeżeli spojrzymy do źródeł tej klasy zobaczymy wiele miejsc, w których metoda Dispose rzuca wyjątkami związanymi np. z połączeniem do serwisu. Dlaczego jest to dla nas takie istotne?
Aby uświadomić sobie problem z którym mamy tutaj do czynienia spójrzmy na kawałek przykładowego kodu który implementuje interfejs IDisposable:
class Program { static void Main(string[] args) { try { ShowSomethingOnScreen(); } catch (Exception ex) { Console.WriteLine(ex); throw; } } private static void ShowSomethingOnScreen() { using (var dc = new DisposableClass()) { dc.DoSomething(); throw new InvalidOperationException("Using block"); } } } public class DisposableClass : IDisposable { public void DoSomething() { Console.WriteLine("I'm doing some work!"); } public void Dispose() { throw new InvalidOperationException("DisposeMethod"); } }
Powyższy przykład rzuca dwa wyjątki:
- jeden z wyjątków zdefiniowany jest w metodzie ShowSomethingOnScreen
- rugi z wyjątków znajduje się w metodzie Dispose klasy DisposableClass
Zadajmy sobie pytanie: po uruchomieniu aplikacji, który z wyjątków zostanie przechwycony w metodzie Main i dlaczego?
Na pierwszy rzut oka wydawałoby się, że zostanie przechwycony wyjątek z treścią "Using block". Stanie się jednak inaczej - zostanie
Aby odpowiedź na pytanie dlaczego przechwycimy wyjątek z metody Dispose a nie z wewnątrz bloku using należy zobaczyć w jaki sposób kompilator przetłumaczy nasz kod. Blok using w rzeczywistości jest blokiem try finally:
.method private hidebysig static void ShowSomethingOnScreen () cil managed { // Method begins at RVA 0x2084 // Code size 37 (0x25) .maxstack 1 .locals init ( [0] class UsingKeyWord.DisposableClass dc ) // (no C# code) IL_0000: nop // using (DisposableClass disposableClass = new DisposableClass()) IL_0001: newobj instance void UsingKeyWord.DisposableClass::.ctor() // (no C# code) IL_0006: stloc.0 .try { IL_0007: nop // disposableClass.DoSomething(); IL_0008: ldloc.0 IL_0009: callvirt instance void UsingKeyWord.DisposableClass::DoSomething() // (no C# code) IL_000e: nop // throw new InvalidOperationException("Using block"); IL_000f: ldstr "Using block" IL_0014: newobj instance void [System.Runtime]System.InvalidOperationException::.ctor(string) // (no C# code) IL_0019: throw } // end .try finally { IL_001a: ldloc.0 IL_001b: brfalse.s IL_0024 IL_001d: ldloc.0 IL_001e: callvirt instance void [System.Runtime]System.IDisposable::Dispose() IL_0023: nop IL_0024: endfinally } // end handler } // end of method Program::ShowSomethingOnScreen
Możemy więc rozpisać w jaki sposób zostanie przetworzony program:
- Program startuje od metody Main i wywołuje metodę ShowSomethingOnScreen w bliku try catch
- Otwarcie bolku using w metodzie ShowSomethingOnScreen
- Wywołanie metody DoSomething która wypisuje zdanie w konsoli
- Rzucenie wyjątku throw new InvalidOperationException("Using block");
- Wywołanie bloku finally, który wywołuje metodę Dispose
- Metoda Dispose rzuca wyjątek
- Wyjątek rzucony w metodzie Dispose przekazywany jest do bloku catch metody Main
Teraz zostaje już tylko odpowiedzieć pytanie - jak prawidłowo obsłużyć oba wyjątki aby nie pominąć żadnego z nich?
Niestety, ale aby było możliwe przechwycenie obu wyjątków musimy porzucić blok using i skorzystać ze zwykłego bolku try catch finally:
class Program { static void Main(string[] args) { try { ShowSomethingOnScreen(); } catch (Exception ex) { Console.WriteLine(ex); throw; } } private static void ShowSomethingOnScreen() { DisposableClass dc = null; try { dc = new DisposableClass(); throw new InvalidOperationException("Using block"); } catch (Exception ex) { Console.WriteLine(ex); } finally { dc?.Dispose(); } } } public class DisposableClass : IDisposable { public void DoSomething() { Console.WriteLine("I'm doing some work!"); } public void Dispose() { throw new InvalidOperationException("DisposeMethod"); } }
Wydawałoby się, że problem raczej niezbyt popularnych, jednak można z nim się spotkać i warto wiedzieć skąd bierze się jego źródło :)
Brak komentarzy:
Prześlij komentarz