Wednesday, 1 April 2015

Exceptions

When it comes to exception handling, the standard practices apply,
  • In methods and functions throw exceptions, don't try and return -1's or nulls or empty strings, something went wrong let your system know through an exception.
  • Don't bury exceptions, when an exception occurs it contains a lot of good information so when you catch one re throw it using just throw; otherwise you'll strip the stack trace and it'll be harder to figure out where's the source of your error.
  • clean up your data, if an exception occurs clean the mess up.
Now lets take a look at a quick example.

and the xaml

<Page
    x:Class="pc.exceptions.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:pc.exceptions"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="100"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="1" Text="Exception Example"
                   Style="{ThemeResource HeaderTextBlockStyle}"
                   VerticalAlignment="Center"/>
        <StackPanel Grid.Column="1" Grid.Row="1">
            <TextBox x:Name="Denominator_TXT" Text="0" Width="100"
                     HorizontalAlignment="Left"/>
            <Button x:Name="DivideByZero_BTN" Content="1/0 Exception" />
        </StackPanel>
    </Grid>
</Page>

and lets look at the code behind

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace pc.exceptions
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.DivideByZero_BTN.Click += DivideByZero_BTN_Click;
        }

        //thrid exception thrown
        void DivideByZero_BTN_Click(object sender, RoutedEventArgs e)
        {
            var d = Convert.ToInt32(Denominator_TXT.Text);

            try
            {
                var x = CalculateTwo(CalculateOne, 9, d);
            }
            catch (Exception ex)
            {
                //this is bad, don't do this
                throw ex;
            }
        }

        //second function to throw exception
        double CalculateTwo(Func<int, int, double> CalculationFunc, int A, int B)
        {
            try
            {
                return CalculationFunc.Invoke(A, B);
            }
            catch (DivideByZeroException ex)
            {
                throw;
            }
        }

        //First Function to throw Exception
        double CalculateOne(int a, int b)
        {
            try
            {
                return a / b;
            }
            catch (DivideByZeroException ex)
            {
                throw;
            }
        }
    }
}

What we have is pretty straight forward, basically we create a divide by zero exception on purpose to trace the errors now when we run our app and cause our exception. lets inspect our exception stack trace at each level

CalculateOne

   at pc.exceptions.MainPage.CalculateOne(Int32 a, Int32 b)

We see the original source of our exception

CalculateTwo

   at pc.exceptions.MainPage.CalculateOne(Int32 a, Int32 b)
   at pc.exceptions.MainPage.CalculateTwo(Func`3 CalculationFunc, Int32 A, Int32 B)

Now we can trace the original source of our exception

DivideByZero_BTN_Click

  at pc.exceptions.MainPage.DivideByZero_BTN_Click(Object sender, RoutedEventArgs e)

we've lost our stack trace history, so by inspecting it at or after this level we can't really tell where the error originated from. however if we change

//thrid exception thrown
void DivideByZero_BTN_Click(object sender, RoutedEventArgs e)
{
    var d = Convert.ToInt32(Denominator_TXT.Text);

    try
    {
        var x = CalculateTwo(CalculateOne, 9, d);
    }
    catch (Exception ex)
    {
        //this is bad, don't do this
        throw ex;
    }

}

to

//thrid exception thrown
void DivideByZero_BTN_Click(object sender, RoutedEventArgs e)
{
    var d = Convert.ToInt32(Denominator_TXT.Text);

    try
    {
        var x = CalculateTwo(CalculateOne, 9, d);
    }
    catch (Exception ex)
    {
        //Much better
        throw;
    }

}

then inspect the stack trace of the exception once it's thrown we see

   at pc.exceptions.MainPage.CalculateOne(Int32 a, Int32 b)
   at pc.exceptions.MainPage.CalculateTwo(Func`3 CalculationFunc, Int32 A, Int32 B)
   at pc.exceptions.MainPage.DivideByZero_BTN_Click(Object sender, RoutedEventArgs e)

much better, this again allows us to trace our tack to see where the error actually originated from.

now you may have noticed that once our application runs out of opportunities to handle our exception our application catches it for the final time in debugger via the unhandled excption event



#pragma checksum "c:\users\administrator\documents\visual studio 2013\Projects\pc.exceptions\pc.exceptions\App.xaml" "{406ea660-64cf-4c82-b6f0-42d48172a799}" "3D2BE5729E141535E26842E969B8DF4A"
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


namespace pc.exceptions
{
#if !DISABLE_XAML_GENERATED_MAIN
    public static class Program
    {
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        static void Main(string[] args)
        {
            global::Windows.UI.Xaml.Application.Start((p) => new App());
        }
    }
#endif

    partial class App : global::Windows.UI.Xaml.Application
    {
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
        private bool _contentLoaded;

        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Windows.UI.Xaml.Build.Tasks"," 4.0.0.0")]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        public void InitializeComponent()
        {
            if (_contentLoaded)
                return;

            _contentLoaded = true;
#if DEBUG && !DISABLE_XAML_GENERATED_BINDING_DEBUG_OUTPUT
            DebugSettings.BindingFailed += (sender, args) =>
            {
                global::System.Diagnostics.Debug.WriteLine(args.Message);
            };
#endif
#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
            UnhandledException += (sender, e) =>
            {
                if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break();
            };
#endif
        }
    }
}

this code will cause our application to break and since the exception is unhanded it will then crash, if you would like to prevent that in you app.xaml.cs you can handle the event.

public App()
{
    this.InitializeComponent();
    this.Suspending += OnSuspending;
    this.UnhandledException += App_UnhandledException;
}

private void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    //handle excption
    e.Handled = true;
}


now this will keep your application form terminating... some of the times... this isn't exactly recommended actually it's discouraged, however this would be an excellent place to save data to storage or log exceptions, maybe post the exception to a web-service so that you can debug it at a later date.

There are exceptions that will terminate your application regardless if they're handled and of coarse when there's an exception you can no longer guarantee the state of your application and thus the best course of action may very well be to just shut it down.