This is the twentieth fifth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#
Some time ago we saw how the using construct is broken in C#. I provided some hacks around that but we can take a completely different approach thanks to the “new” C# syntax. Let’s use exception filters to do so:
See this code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
using System; using System.Threading; using System.Runtime.ExceptionServices; public class Program { public static void Main() { var handle1 = new ManualResetEvent(false); var handle2 = new ManualResetEvent(false); var abortedThread = new Thread(() => { try{ Exception firstException = null; var resource = new Resource(); try{ Console.WriteLine("Using"); handle1.Set(); handle2.WaitOne(); // Interruption comes here }catch(Exception e) when (HandleFilter(e, ref firstException)) { // Empty as we handle in filter }finally{ try{ if(resource != null) resource.Dispose(); } catch(Exception e2) when (ModifyFilter(e2, firstException)){ // Empty as we modify in filter } } }catch(Exception e3){ Console.WriteLine("Swallowing everything! " + e3); } Console.WriteLine("This shouldn't appear"); }); abortedThread.Start(); handle1.WaitOne(); abortedThread.Abort(); handle2.Set(); abortedThread.Join(); } private static bool HandleFilter(Exception e, ref Exception target){ target = e; return false; } private static bool ModifyFilter(Exception e, Exception old){ if(old != null){ if(e.InnerException != null){ typeof(Exception).GetField("_innerException", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(e, new AggregateException(old, e.InnerException)); }else{ typeof(Exception).GetField("_innerException", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(e, old); } } return false; } } public class Resource : IDisposable { public void Dispose(){ Console.WriteLine("Disposing"); throw new Exception("Disposing failed"); } } |
We use the same code with aborting the thread as the last time. However, we do not catch any exceptions, we let them pass but we keep track of them via exception filters. Notice how first filter just stores the exception, and the second filter modifies the exception in place. We could obviously throw aggregated exception instead but then we would need to do it in the catch block. Keep in mind this is not necessarily safe as reflection may throw another exception. Also, regular considerations regarding access violations apply.
Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Using Disposing Swallowing everything! System.Exception: Disposing failed ---> System.Threading.ThreadAbortException: Thread was being aborted. at (wrapper managed-to-native) System.Threading.WaitHandle.Wait_internal(intptr*,int,bool,int) at System.Threading.WaitHandle.WaitOneNative (System.Runtime.InteropServices.SafeHandle waitableSafeHandle, System.UInt32 millisecondsTimeout, System.Boolean hasThreadAffinity, System.Boolean exitContext) [0x00044] in :0 at System.Threading.WaitHandle.InternalWaitOne (System.Runtime.InteropServices.SafeHandle waitableSafeHandle, System.Int64 millisecondsTimeout, System.Boolean hasThreadAffinity, System.Boolean exitContext) [0x00014] in :0 at System.Threading.WaitHandle.WaitOne (System.Int64 timeout, System.Boolean exitContext) [0x00000] in :0 at System.Threading.WaitHandle.WaitOne (System.Int32 millisecondsTimeout, System.Boolean exitContext) [0x00019] in :0 at System.Threading.WaitHandle.WaitOne () [0x00000] in :0 at Program+c__DisplayClass0_0.b__0 () [0x00022] in :0 --- End of inner exception stack trace --- at Resource.Dispose () [0x0000c] in :0 at Program+c__DisplayClass0_0.b__0 () [0x00060] in :0 |
Similar approach could be based on the app domain’s first chance exception event but identifying exceptions could be much harder in that case (as they may be handled deep inside the code or just thrown from completely other parts of the codebase).