In C# code an error can occur at almost any statement. Checking for all these errors is complex—exception handling separates this logic.
In exception handling, we have keywords like throw, try and catch. We use these in C# programs to handle errors gracefully. On errors, we can print messages, or return early.
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.
DivideByZeroException
. This operation cannot be continued.using System; class Program { static void Main() { try { int value = 1 / int.Parse("0"); Console.WriteLine(value); } catch (Exception ex) { Console.WriteLine(ex.Message); } } }Attempted to divide by zero.
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
is empty because it was not defined on the exception. This is a string
property.string
property.string
property that can be assigned to or read from.StackTrace
is the path through the compiled program's method hierarchy that the exception was generated from.TargetSite
is the name of the method where the error occurred. This property helps simplify what part of the errors are recorded.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); } } }HelpLink = Message = Attempted to divide by zero. Source = ConsoleApplication1 StackTrace = at Program.Main() in C:\...\Program.cs:line 9 TargetSite = Void Main()
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.
Hashtable
or Dictionary
. The keys and values are represented by the object type.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); } } } }Time = 12/9/2014 5:38:22 PM Flag = True
In C# programs we can access many built-in exceptions. But this may not be enough—we may want even more exceptions. We solve this need with custom exceptions.
class
. Have it derive from Exception
—the class
is the base class
for all exceptions.override
string
property, Message, to specify the string
displayed by the 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); } } }TestException: This exception means something bad happened at Program.Main()....
We can use 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.
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); } } }
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
literal in an if
-statement.try-catch
to handle errors. We throw an exception if the argument array is null
.try-catch
block has a negative effect on performance. If the array is null
, performance would be even worse.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")); } }1.95 ns: GetA, if check 3.91 ns: GetB, try-catch
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.
try-catch
block is inside the inner loop. So we enter the protected region on each loop iteration.try-catch
block is outside the loop. The logic is different—if an exception is thrown, the entire loop terminates.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")); } 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 { } } }1870.76 ns: Method1 359.79 ns: Method2
With Exception
types, we describe errors. And with their properties, we access information about errors. Exceptions help contain complexity.