Though the FCL supports a great deal of predefined system exception classes (as seen in the earlier section) that can be used to represent a large gamut of errors, there is always a need to model custom errors that represent failed run-of-the-mill business logic as well as other application specific error scenarios. In these situations, you need to turn to defining your own custom exceptions that are application specific. When defining application specific custom exceptions, you need to typically create a class that is derived from the
System.ApplicationException class.
It is good practice and generally recommended that the application exception class be suffixed with Exception. For example, if you need to define an exception that indicates that a specific ticket to a movie is not available, you’d probably want to name it something like TicketNotAvailableException.
So let’s put this in practice and see how to define and use a custom application exception. The example we’ll take up is a class that simulates a television channel changer, which allows the user to surf television channels. We’ll assume that one of our business logic constraints is that we support only 80 channels and that the class is expected to flip channels only if the user enters a channel number between 1 and 80. If the user enters an invalid channel number, the class is expected throw an application exception, which indicates that the channel number entered is invalid. So let’s put together this application-specific exception class.
We’ll call the exception class ChannelNotAvailableException and derive this class from the System.ApplicationException class to indicate that this is an application specific exception. Next, we’ll have to create some constructors for this class. The best practice guidelines for exception handling recommend that we have two constructors in addition to the default no-argument constructor - One constructor that accepts the error message as a parameter and the other one that accepts both an error message and an inner exception as a parameter. Let’s take a look at the ChannelNotAvailableException class.
Code listing in C#
class ChannelNotAvailableException : System.ApplicationException
{
public ChannelNotAvailableException()
{
}
public ChannelNotAvailableException(String errorMessage) :
base(errorMessage)
{
}
public ChannelNotAvailableException(String errorMessage,
Exception innerException) : base(errorMessage, innerException)
{
}
}
Code listing in VB.NET
Public Class ChannelNotAvailableException
Inherits ApplicationException
' Default Constructor
Public Sub New()
' Call the base class constructor
MyBase.New()
End Sub
' Constructor that takes message string
Public Sub New(ByVal errorMessage As String)
' Call the base class constructor
MyBase.New(errorMessage)
End Sub
' Constructor that takes message string and inner exception
Public Sub New(ByVal errorMessage As String, _
ByVal innerException As Exception)
' Call the base class constructor
MyBase.New(errorMessage, innerException)
End Sub
End Class
The ChannelNotAvailableException class shown above is fairly trivial and you’ll notice that the non-default constructors do nothing more than initializing their corresponding base class counter parts through the base class argument-list initializer. That’s it – we’re done setting up our custom exception class. We’ll see how to put this to use in our TV channel surfer application. Let’s put together some code for the channel surfer class.
Code listing in C#
class ChannelSurfer
{
private const int MAX_CHANNELS = 80;
private int m_nCurrentChannel;
ChannelSurfer()
{
// Set channel 1 as the default
m_nCurrentChannel = 1;
}
public int CurrentChannel
{
get
{
// Return the current channel
return m_nCurrentChannel;
}
}
// Rest of the class implementation goes here . . .
}
Code listing in VB.NET
Public Class ChannelSurfer
Private Const MAX_CHANNELS As Integer = 80
Private m_nCurrentChannel As Integer
Public Sub New()
MyBase.New()
' Set channel 1 as the default
Me.m_nCurrentChannel = 1
End Sub
ReadOnly Property CurrentChannel() As Integer
Get
' Return the current channel
Return Me.m_nCurrentChannel
End Get
End Property
' Rest of the class implementation goes here . . .
End Class
The channel surfer class supports a read-only property named CurrentChannel that keeps track of the current channel being viewed. The viewer can move between channels by calling the FlipToChannel() method shown below:
Code listing in C#
class ChannelSurfer
{
// Rest of the class implementation goes here . . .
void FlipToChannel(int nChannelNumber)
{
if( (nChannelNumber < 1) || (nChannelNumber > MAX_CHANNELS))
{
throw new ChannelNotAvailableException("We support only 80 channels."
+ "Please enter a number between 1 and 80");
}
else
{
// Set the value of the current channel
m_nCurrentChannel = nChannelNumber;
}
}
}
Code listing in VB.NET
Public Class ChannelSurfer
' Rest of the class implementation goes here . . .
Sub FlipToChannel(ByVal nChannel As Integer)
If ((nChannel < 1) Or (nChannel > MAX_CHANNELS)) Then
' Raise an exception
Throw New ChannelNotAvailableException("We support only 80 channels." _
+ "Please enter a number between 1 and 80")
Else
' Set the current channel
Me.m_nCurrentChannel = nChannel
End If
End Sub
End Class
As seen above the FlipToChannel() method checks to see if the channel number that the user is requesting is between 1 and 80 and if so, sets the value of the CurrentChannel property to the requested channel. If the values are not within the specified range, the class throws the user-defined ChannelNotAvailableException exception. Let’s use the application’s Main() entry point as a test harness for the ChannelSurfer class. Take a look at the code below:
Code listing in C#
class ChannelSurfer
{
// Rest of the class implementation goes here . . .
static void Main(string[] args)
{
ChannelSurfer channelZapper = new ChannelSurfer();
// Display a message
Console.WriteLine("Press 'Q' or 'q' to quit zapping channels");
// Set up an infinite loop
for(;;)
{
try
{
// It's channel surfing time folks!
Console.Write("Please enter a channel number and press 'Enter'");
// Get the channel number from the user
String strChannel = Console.ReadLine();
// Check if the user wants to quit
if(strChannel.Equals("Q") || strChannel.Equals("q")) break;
// Convert the channel number to an integer
int nChannel = Int32.Parse(strChannel);
// Flip away to the requested channel
channelZapper.FlipToChannel(nChannel);
}
catch(ChannelNotAvailableException exChannel)
{
Console.WriteLine("Channel not supported: {0}", exChannel.Message);
}
catch(FormatException exFormat)
{
Console.WriteLine("Caught a format exception: {0}",
exFormat.Message);
}
catch(Exception ex)
{
Console.WriteLine("Caught a exception: {0}", ex.Message);
}
finally
{
// What channel are we watching?
Console.WriteLine("You are watching Channel : {0}",
channelZapper.CurrentChannel);
}
}
}
}
Code listing in VB.NET
Module SurfChannelTestHarness
Sub Main()
Dim channelZapper As ChannelSurfer = New ChannelSurfer()
' Display a message
Console.WriteLine("Press 'Q' or 'q' to Quit zapping Channels")
' Setup an infinite loop to ask the user for channel input
Do
Try
' It's channel surfing time folks !
Console.Write("Please enter a channel number and press 'Enter' ")
' Get the channel number from the user
Dim strChannel As String = Console.ReadLine()
' Check if the user wants to quit
If (strChannel.Equals("Q") Or strChannel.Equals("q")) Then
Exit Do
End If
' Convert the channel number to an integer
Dim nChannel As Integer = Int32.Parse(strChannel)
' Flip away to the requested channel
channelZapper.FlipToChannel(nChannel)
Catch exChannel As ChannelNotAvailableException
Console.WriteLine("Channel not supported: {0}", _
exChannel.Message)
Catch exFormat As FormatException
Console.WriteLine("Caught a format exception: {0}", _
exFormat.Message)
Catch ex As Exception
Console.WriteLine("Caught a exception: {0}", ex.Message)
Finally
' What channel are we watching ?
Console.WriteLine("You are watching Channel: {0}", _
channelZapper.CurrentChannel)
End Try
Loop While True
End Sub
End Module
The Main() entry point creates an instance of the ChannelSurfer class and sets up a loop that requests channel numbers from the user until the user presses the ‘Q’ or ‘q’ key to quit the application. When the channel number input is received, it calls the FlipToChannel() method of the ChannelSurfer object. The FlipToChannel() method call is enclosed within a try block and an appropriate catch handler for the
ChannelNotAvailableException will catch the exception if an invalid channel number is passed to the FlipToChannel() method. Also, if the user enters non-numeric input, the Int32.Parse() method will throw a System.FormatException that will be caught by the catch handler that we’ve setup to handle FormatException exceptions. Compile the file using the following command from the DOS command line:
Compiling in C#
csc /target:exe ChannelSurfer.cs
Compiling in VB.NET
vbc /target:exe ChannelSurfer.vb
That generates the executable file ChannelSurfer.exe. Run the application and feed it with input containing both valid and invalid input values. Here’s a sample interaction with the ChannelSurfer application.
Press 'Q' or 'q' to quit zapping channels
Please enter a channel number and press 'Enter' 62
You are watching Channel : 62
Please enter a channel number and press 'Enter' 56
You are watching Channel : 56
Please enter a channel number and press 'Enter' 104
Channel not supported: We support only 80 channels. Please enter a number between 1 and 80
You are watching Channel : 56
Please enter a channel number and press 'Enter' abcd
Caught a format exception : Input string was not in a correct format.
You are watching Channel : 56
Please enter a channel number and press 'Enter' 15
You are watching Channel : 15
Please enter a channel number and press 'Enter' q
You are watching Channel : 15
You will notice from the output above that when the user enters 104 for the channel number, the ChannelNotAvailableException is thrown from the FlipToChannel() method, which is then handled by the catch handler. Similarly, when the user keys in a non-numeric value such as abcd, a System.FormatException is raised by the Int32.Parse() method, which then gets caught by the catch handler that filters the FormatException exceptions. Using application specific exceptions like ChannelNotAvailableException
allows you to build exception-handling classes around your business-logic and application specific scenarios. Be sure to check if the framework provides a predefined exception class that suits the exception type that you want to handle. If so, reuse FCL provided system exceptions. Otherwise, feel free to model custom exception classes that are modeled around application specific exception scenarios.