C# Exception Handling

Understand Exceptions. Handle errors, use the try and catch keywords, and optimize exceptions.

Exceptions. All human endeavor has risk. Much like actions in our external reality, a computer's processes can fail. Nothing is safe.

An error can occur at almost any statement. Checking for all these errors becomes unbearably complex. Exception handling separates this logic. It simplifies control flow.

An example. We can throw exceptions with a throw statement. But an exception is often thrown automatically by the runtime. An instruction may cause an invalid state.Throw
Here: We divide by zero. Sadly this results in a DivideByZeroException. This operation cannot be continued.
Try: We use the try and catch blocks to structure our error handling. This may lead to cleaner code.
C# program that throws an exception using System; class Program { static void Main() { try { int value = 1 / int.Parse("0"); Console.WriteLine(value); } catch (Exception ex) { Console.WriteLine(ex.Message); } } } Output Attempted to divide by zero.

Properties. We next use the Exception type's properties. This program creates an exception by dividing by zero. Then it catches and displays the exception.
HelpLink: This is empty because it was not defined on the exception. HelpLink is a string property.
Message: This is a short description of the exception's cause. Message is a read-only string property.
Source: This is the application name. Source is a string property that can be assigned to or read from.
StackTrace: This is the path through the compiled program's method hierarchy that the exception was generated from.
TargetSite: This is the name of the method where the error occurred. This property helps simplify what part of the errors are recorded.
C# program that shows exception properties using System; class Program { static void Main() { try { int value = 1 / int.Parse("0"); } catch (Exception ex) { Console.WriteLine("HelpLink = {0}", ex.HelpLink); Console.WriteLine("Message = {0}", ex.Message); Console.WriteLine("Source = {0}", ex.Source); Console.WriteLine("StackTrace = {0}", ex.StackTrace); Console.WriteLine("TargetSite = {0}", ex.TargetSite); } } } Output: truncated HelpLink = Message = Attempted to divide by zero. Source = ConsoleApplication1 StackTrace = at Program.Main() in C:\...\Program.cs:line 9 TargetSite = Void Main()

Data. It is possible to store structured data on an exception that is thrown in one part of your program, and then later read in this data. With Data we store associate keys and values.
Here: In this example, we use a try construct. In the try block, a new exception is allocated. Next we assign to the Data property.
Tip: Data can be used as a Hashtable or Dictionary. The keys and values are represented by the object type.
Finally: The exception instance is thrown. And in the catch block we display the Data contents.
C# program that uses Data property using System; using System.Collections; class Program { static void Main() { try { // Create new exception. var ex = new DivideByZeroException("Message"); // Set the data dictionary. ex.Data["Time"] = DateTime.Now; ex.Data["Flag"] = true; // Throw it. throw ex; } catch (Exception ex) { // Display the exception's data dictionary. foreach (DictionaryEntry pair in ex.Data) { Console.WriteLine("{0} = {1}", pair.Key, pair.Value); } } } } Output Time = 12/9/2014 5:38:22 PM Flag = True

Custom. The .NET Framework includes many built-in exceptions. But this may not be enough. You want even more exceptions. We solve this need with custom exceptions.
Tip: To make one, create a class. Have it derive from Exception—the class is the base class for all exceptions.
Message: You can add a public override string property, Message, to specify the string displayed by the Exception.
Caution: Custom types should be reluctantly used. They tend to add more complexity. Consider instead just using built-in ones.
C# program that throws custom exception using System; class TestException : Exception { public override string Message { get { return "This exception means something bad happened"; } } } class Program { static void Main() { try { throw new TestException(); } catch (TestException ex) { Console.WriteLine(ex); } } } Output TestException: This exception means something bad happened at Program.Main()....

Tester-doer. The .NET Framework uses the Tester-Doer pattern. This refers to functions that do not throw exceptions on errors. Instead, they return an error code and take no action.
Tip: This pattern improves the performance of certain important functions by avoiding exception handling.
Further: You can use the tester-doer pattern in your own function designs. It yields similar benefits.
Speed: The Tester-Doer pattern is a clear performance win over exceptions in almost all situations.
C# program that shows a tester-doer method using System; class Program { static void Main() { // This is not valid! string value = "abc"; int result; if (int.TryParse(value, out result)) // Tester-doer method. { // Not reached. // ... Result would have the valid parsed result. Console.WriteLine(result); } } }

Benchmark, exceptions. Are exceptions fast? Here we see a method that carefully tests for null (and thus does not need exception handling) and a method that uses try and catch.Null
Version 1: This version of the code handles errors by testing against the null literal in an if-statement.
Version 2: Here we use try-catch to handle errors. We throw an exception if the argument array is null.
Result: The try-catch block has a negative effect on performance. If the array is null, performance would be even worse.
C# program that shows exceptions using System; using System.Diagnostics; class Program { static int GetA(int[] arr) { if (arr != null) // Check for null. { return arr[0]; } else { return 0; } } static int GetB(int[] arr) { try { return arr[0]; } catch // Catch exceptions. { return 0; } } const int _max = 1000000; static void Main() { int[] arr = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int count = 0; var s1 = Stopwatch.StartNew(); // Version 1: use if-statement to handle errors. for (int i = 0; i < _max; i++) { int v = GetA(arr); if (v == 5) { count++; } } s1.Stop(); var s2 = Stopwatch.StartNew(); // Version 2: use try-catch to handle errors. for (int i = 0; i < _max; i++) { int v = GetB(arr); if (v == 5) { count++; } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.Read(); } } Output 7.91 ns: GetA, if check 16.85 ns: GetB, try-catch

Optimization, exceptions. Exception handling can be made faster. If we have an exception handling construct (try-catch) in an inner loop, we could hoist it to the outside of the loop.
Version 1: Here the try-catch block is inside the inner loop. So we enter the protected region on each loop iteration.
Version 2: The try-catch block is outside the loop. The logic is different—if an exception is thrown, the entire loop terminates.
Result: Version 2 (with the try-catch outside the loop) is faster. Hoisting exception handling outside a loop helps.
C# program that optimizes exception construct using System; using System.Diagnostics; class Program { const int _max = 1000000; static void Main() { var s1 = Stopwatch.StartNew(); // Version 1: try-catch inside loop. for (int i = 0; i < _max; i++) { Method1(); } s1.Stop(); var s2 = Stopwatch.StartNew(); // Version 2: try-catch outside loop. for (int i = 0; i < _max; i++) { Method2(); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000 * 1000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000 * 1000) / _max).ToString("0.00 ns")); Console.Read(); } static void Method1() { for (int i = 0; i < 1000; i++) { try { int value = i * 100; if (value == -1) { throw new Exception(); } } catch { } } } static void Method2() { try { for (int i = 0; i < 1000; i++) { int value = i * 100; if (value == -1) { throw new Exception(); } } } catch { } } } Output 2555.43 ns: Method1 674.29 ns: Method2

Finally. Suppose we have some logic that must run on each invocation of a method. It does not matter if an error was encountered—the logic must be run. We can use "finally."Finally

Checked. The checked and unchecked contexts specify whether exceptions occur when a value type overflows. These are operators. They are uncommonly used.Checked

Exception types. The derived type of an exception is important. It helps us understand the exact nature of an error that is occurring. The message may also help here.ArgumentExceptionArrayTypeMismatch ExceptionDivideByZeroExceptionFileNotFoundExceptionIndexOutOfRangeExceptionInvalidCastExceptionInvalidOperation ExceptionIOExceptionKeyNotFoundExceptionNotImplementedExceptionNullReferenceExceptionOutOfMemoryExceptionOverflowExceptionStackOverflowExceptionTypeInitialization Exception

MemoryFailPoint. Most programs will not need to use MemoryFailPoint. But this type can help us deal without-of-memory situations.MemoryFailPoint

Code motion. It is best to use try-catch at the outermost block of loops. This is a form of "code motion." In this way we reduce the total cost of exception handling blocks.

With Exception types, we describe errors. And with their properties, we access information about errors. In the complex systems in our modern world, exceptions help contain complexity.

© 2007-2020 Sam Allen. Send bug reports to info@dotnetperls.com.