Low-level C# Practices

Exclusive offer: get 50% off this eBook here
Visual Studio 2010 Best Practices

Visual Studio 2010 Best Practices — Save 50%

Learn and implement recommended practices for the complete software development lifecycle with Visual Studio 2010 with this book and ebook.

$23.99    $12.00
by Peter Ritchie | October 2012 | Enterprise Articles

Syntax isn't the only thing that matters when it comes to writing code. We can't get far without getting past the compiler, but not everything that compiles is of acceptable quality. Practices and techniques beyond being "syntactically correct" are so important that there are entire third-party ecosystems devoted to detecting common issues and patterns in source code and in compiled code.

This article isn't about many of the issues that code analysis tools detect, but details some practices that can be used with C# to avoid certain pitfalls and improve quality. In this article by Peter Ritchie, author of Visual Studio 2010 Best Practices, we'll look at recommended practices in the following areas:

  • .NET generics
  • Sequences and iterator members
  • Lambdas
  • Extension methods
  • Exception handling

 

        Read more about this book      

(For more resources on this subject, see here.)

Working with generics

Visual Studio 2005 included .NET version 2.0 which included generics. Generics give developers the ability to design classes and methods that defer the specification of specific parts of a class or method's specification until declaration or instantiation.

Generics offer features previously unavailable in .NET. One benefit to generics, that is potentially the most common, is for the implementation of collections that provide a consistent interface to collections of different data types without needing to write specific code for each data type.

Constraints can be used to restrict the types that are supported by a generic method or class, or can guarantee specific interfaces.

Limits of generics

Constraints within generics in C# are currently limited to a parameter-less constructor, interfaces, or base classes, or whether or not the type is a struct or a class (value or reference type). This really means that code within a generic method or type can either be constructed or can make use of methods and properties.

Due to these restrictions types within generic types or methods cannot have operators.

Writing sequence and iterator members

Visual Studio 2005 and C# 2.0 introduced the yield keyword. The yield keyword is used within an iterator member as a means to effectively implement an IEnumerable interface without needing to implement the entire IEnumerable interface.

Iterator members are members that return a type of IEnumerable or IEnumerable<T>, and return individual elements in the enumerable via yield return, or deterministically terminates the enumerable via yield break. These members can be anything that can return a value, such as methods, properties, or operators. An iterator that returns without calling yield break has an implied yield break, just as a void method has an implied return.

Iterators operate on a sequence but process and return each element as it is requested. This means that iterators implement what is known as deferred execution. Deferred execution is when some or all of the code, although reached in terms of where the instruction pointer is in relation to the code, hasn't entirely been executed yet.

Iterators are methods that can be executed more than once and result in a different execution path for each execution. Let's look at an example:

public static IEnumerable<DateTime> Iterator()
{
Thread.Sleep(1000);
yield return DateTime.Now;
Thread.Sleep(1000);
yield return DateTime.Now;
Thread.Sleep(1000);
yield return DateTime.Now;
}

 

The Iterator method returns IEnumerable which results in three DateTime values. The creation of those three DateTime values is actually invoked at different times. The Iterator method is actually compiled in such a way that a state machine is created under the covers to keep track of how many times the code is invoked and is implemented as a special IEnumerable<DateTime> object. The actual invocation of the code in the method is done through each call of the resulting IEnumerator. MoveNext method.

The resulting IEnumerable is really implemented as a collection of delegates that are executed upon each invocation of the MoveNext method, where the state, in the simplest case, is really which of the delegates to invoke next. It's actually more complicated than that, especially when there are local variables and state that can change between invocations and is used across invocations. But the compiler takes care of all that.

Effectively, iterators are broken up into individual bits of code between yield return statements that are executed independently, each using potentially local shared data.

What are iterators good for other than a really cool interview question? Well, first of all, due to the deferred execution, we can technically create sequences that don't need to be stored in memory all at one time.

This is often useful when we want to project one sequence into another. Couple that with a source sequence that is also implemented with deferred execution, we end up creating and processing IEnumerables (also known as collections) whose content is never all in memory at the same time. We can process large (or even infinite) collections without a huge strain on memory.

For example, if we wanted to model the set of positive integer values (an infinite set) we could write an iterator method shown as follows:

static IEnumerable<BigInteger> AllThePositiveIntegers()
{
var number = new BigInteger(0);
while (true) yield return number++;
}

 

We can then chain this iterator with another iterator, say something that gets all of the positive squares:

static IEnumerable<BigInteger> AllThePostivieIntegerSquares(
IEnumerable<BigInteger> sourceIntegers)
{
foreach(var value in sourceIntegers)
yield return value*value;
}

 

Which we could use as follows:

foreach(var value in
AllThePostivieIntegerSquares(AllThePositiveIntegers()))
Console.WriteLine(value);

 

We've now effectively modeled two infi nite collections of integers in memory.

Of course, our AllThePostiveIntegerSquares method could just as easily be used with fi nite sequences of values, for example:

foreach (var value in
AllThePostivieIntegerSquares(
Enumerable.Range(0, int.MaxValue)
.Select(v => new BigInteger(v))))
Console.WriteLine(value);

 

In this example we go through all of the positive Int32 values and square each one without ever holding a complete collection of the set of values in memory.

As we see, this is a useful method for composing multiple steps that operate on, and result in, sequences of values.

We could have easily done this without IEnumerable<T>, or created an IEnumerator class whose MoveNext method performed calculations instead of navigating an array. However, this would be tedious and is likely to be error-prone. In the case of not using IEnumerable<T>, we'd be unable to operate on the data as a collection with things such as foreach.

Context: When modeling a sequence of values that is either known only at runtime, or each element can be reliably calculated at runtime.

Practice: Consider using an iterator.

Working with lambdas

Visual Studio 2008 introduced C# 3.0 . In this version of C# lambda expressions were introduced. Lambda expressions are another form of anonymous functions. Lambdas were added to the language syntax primarily as an easier anonymous function syntax for LINQ queries. Although you can't really think of LINQ without lambda expressions, lambda expressions are a powerful aspect of the C# language in their own right. They are concise expressions that use implicitly-typed optional input parameters whose types are implied through the context of their use, rather than explicit de fi nition as with anonymous methods.

Along with C# 3.0 in Visual Studio 2008, the .NET Framework 3.5 was introduced which included many new types to support LINQ expressions, such as Action<T> and Func<T>. These delegates are used primarily as definitions for different types of anonymous methods (including lambda expressions). The following is an example of passing a lambda expression to a method that takes a Func<T1, T2, TResult> delegate and the two arguments to pass along to the delegate:

ExecuteFunc((f, s) => f + s, 1, 2);

 

The same statement with anonymous methods:

ExecuteFunc(delegate(int f, int s) { return f + s; }, 1, 2);

 

It's clear that the lambda syntax has a tendency to be much more concise, replacing the delegate and braces with the "goes to" operator (=>). Prior to anonymous functions, member methods would need to be created to pass as delegates to methods. For example:

ExecuteFunc(SomeMethod, 1, 2);

 

This, presumably, would use a method named SomeMethod that looked similar to:

private static int SomeMethod(int first, int second)
{
return first + second;
}

 

Lambda expressions are more powerful in the type inference abilities, as we've seen from our examples so far. We need to explicitly type the parameters within anonymous methods, which is only optional for parameters in lambda expressions.

LINQ statements don't use lambda expressions exactly in their syntax. The lambda expressions are somewhat implicit. For example, if we wanted to create a new collection of integers from another collection of integers, with each value incremented by one, we could use the following LINQ statement:

var x = from i in arr select i + 1;

 

The i + 1 expression isn't really a lambda expression, but it gets processed as if it were first converted to method syntax using a lambda expression:

var x = arr.Select(i => i + 1);

 

The same with an anonymous method would be:

var x = arr.Select(delegate(int i) { return i + 1; });

 

What we see in the LINQ statement is much closer to a lambda expression. Using lambda expressions for all anonymous functions means that you have more consistent looking code.

Context: When using anonymous functions.

Practice: Prefer lambda expressions over anonymous methods.

Parameters to lambda expressions can be enclosed in parentheses. For example:

var x = arr.Select((i) => i + 1);

 

The parentheses are only mandatory when there is more than one parameter:

var total = arr.Aggregate(0, (l, r) => l + r);

 

Context: When writing lambdas with a single parameter.

Practice: Prefer no parenthesis around the parameter declaration.

Sometimes when using lambda expressions, the expression is being used as a delegate that takes an argument. The corresponding parameter in the lambda expression may not be used within the right-hand expression (or statements). In these cases, to reduce the clutter in the statement, it's common to use the underscore character (_) for the name of the parameter. For example:

task.ContinueWith(_ => ProcessSecondHalfOfData());

 

The task.ContinueWith method takes an Action <Task> delegate. This means the previous lambda expression is actually given a task instance (the antecedent Task). In our example, we don't use that task and just perform some completely independent operation. In this case, we use (_) to not only signify that we know we don't use that parameter, but also to reduce the clutter and potential name collisions a little bit.

Context: When writing lambda expression that take a single parameter but the parameter is not used.

Practice: Use underscore (_) for the name of the parameter.

There are two types of lambda expressions. So far, we've seen expression lambdas. Expression lambdas are a single expression on the right-hand side that evaluates to a value or void. There is another type of lambda expression called statement lambdas. These lambdas have one or more statements and are enclosed in braces. For example:

task.ContinueWith(_ => {
var value = 10;
value += ProcessSecondHalfOfData();
ProcessSomeRandomValue(value);
});

 

As we can see, statement lambdas can declare variables, as well as have multiple statements.

Working with extension methods

Along with lambda expressions and iterators, C# 3.0 brought us extension methods. These static methods (contained in a static class whose first argument is modified with the this modifier) were created for LINQ so IEnumerable types could be queried without needing to add copious amounts of methods to the IEnumerable interface.

An extension method has the basic form of:

public static class EnumerableExtensions
{
public static IEnumerable<int> IntegerSquares(
this IEnumerable<int> source)
{
return source.Select(value => value * value);
}
}

 

As stated earlier, extension methods must be within a static class, be a static method, and the first parameter must be modified with the this modifier.

Extension methods extend the available instance methods of a type. In our previous example, we've effectively added an instance member to IEnumerable<int> named IntegerSquares so we get a sequence of integer values that have been squared.

For example, if we created an array of integer values, we will have added a Cubes method to that array that returns a sequence of the values cubed. For example:

var values = new int[] {1, 2, 3};
foreach (var v in values.Cubes())
{
Console.WriteLine(v);
}

 

Having the ability to create new instance methods that operate on any public members of a specific type is a very powerful feature of the language.

This, unfortunately, does not come without some caveats.

Extension methods suffer inherently from a scoping problem. The only scoping that can occur with these methods is the namespaces that have been referenced for any given C# source file. For example, we could have two static classes that have two extension methods named Cubes. If those static classes are in the same namespace, we'd never be able to use those extensions methods as extension methods because the compiler would never be able to resolve which one to use. For example:

public static class IntegerEnumerableExtensions
{
public static IEnumerable<int> Squares(
this IEnumerable<int> source)
{
return source.Select(value => value * value);
}
public static IEnumerable<int> Cubes(
this IEnumerable<int> source)
{
return source.Select(value => value * value * value);
}
}
public static class EnumerableExtensions
{
public static IEnumerable<int> Cubes(
this IEnumerable<int> source)
{
return source.Select(value => value * value * value);
}
}

 

If we tried to use Cubes as an extension method, we'd get a compile error, for example:

var values = new int[] {1, 2, 3};
foreach (var v in values.Cubes())
{
Console.WriteLine(v);
}

 

This would result in error CS0121: The call is ambiguous between the following methods or properties.

To resolve the problem, we'd need to move one (or both) of the classes to another namespace, for example:

namespace Integers
{
public static class IntegerEnumerableExtensions
{
public static IEnumerable<int> Squares(
this IEnumerable<int> source)
{
return source.Select(value => value*value);
}
public static IEnumerable<int> Cubes(
this IEnumerable<int> source)
{
return source.Select(value => value*value*value);
}
}
}
namespace Numerical
{
public static class EnumerableExtensions
{
public static IEnumerable<int> Cubes(
this IEnumerable<int> source)
{
return source.Select(value => value*value*value);
}
}
}

 

Then, we can scope to a particular namespace to choose which Cubes to use:

Context: When considering extension methods, due to potential scoping problems.

Practice: Use extension methods sparingly.

Context: When designing extension methods.

Practice: Keep all extension methods that operate on a specific type in their own class.

Context: When designing classes to contain methods to extend a specific type, TypeName.

Practice: Consider naming the static class TypeNameExtensions.

Context: When designing classes to contain methods to extend a specific type, in order to scope the extension methods.

Practice: Consider placing the class in its own namespace.

Generally, there isn't much need to use extension methods on types that you own. You can simply add an instance method to contain the logic that you want to have.

Where extension methods really shine is for effectively creating instance methods on interfaces.

Typically, when code is necessary for shared implementations of interfaces, an abstract base class is created so each implementation of the interface can derive from it to implement these shared methods. This is a bit cumbersome in that it uses the one-and-only inheritance slot in C#, so an interface implementation would not be able to derive or extend any other classes. Additionally, there's no guarantee that a given interface implementation will derive from the abstract base and runs the risk of not being able to be used in the way it was designed. Extension methods get around this problem by being entirely independent from the implementation of an interface while still being able to extend it.

One of the most notable examples of this might be the System.Linq.Enumerable class introduced in .NET 3.5. The static Enumerable class almost entirely consists of extension methods that extend IEnumerable. It is easy to develop the same sort of thing for our own interfaces. For example, say we have an ICoordinate interface to model a three-dimensional position in relation to the Earth's surface:

namespace ConsoleApplication
{
using Numerical;
internal class Program
{
private static void Main(string[] args)
{
var values = new int[] {1, 2, 3};
foreach (var v in values.Cubes())
{
Console.WriteLine(v);
}
}
}
}

 

We could create a static class to contain extension methods to provide shared functionality between any implementation of ICoordinate. For example:

public interface ICoordinate
{
/// <summary>North/south degrees from equator.</summary>
double Latitude { get; set; }
/// <summary>East/west degrees from meridian.</summary>
double Longitude { get; set; }
/// <summary>Distance from sea level in meters.</summary>
double Altitude { get; set; }
}

 

Context: When designing interfaces that require shared code.

Practice: Consider providing extension methods instead of abstract base implementations.

Visual Studio 2010 Best Practices Learn and implement recommended practices for the complete software development lifecycle with Visual Studio 2010 with this book and ebook.
Published: August 2012
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on this subject, see here.)

Exception handling

It was pretty sketchy when exceptions were first introduced to programming languages. They've been around in some form or another in languages dating back to the seventies. Smalltalk is likely the oldest language with exception handling abilities. Similarly-aged C includes an ability to signal that could be used for general exception communications, but it is intended more to provide interrupts. C also includes the ability to "long jump", but this is mostly provided within the library rather than in the language, that is, there is a setjmp method to define a destination (for example a handler) and a longjmp method to "throw." But setjmp/longjmp is no more than a "non-local goto"—there's no decoupling of the handler from the thrower. It's very complex and error-prone to use setjmp/longjmp for exception handling. So complex that the intent of the methods using it can easily get lost.

Smalltalk effectively includes syntax for throwing and handling exceptions. Smalltalk included an ability to use types as monikers for exceptions, effectively decoupling the code that threw an exception from the code that caught the exception. Neither the handler, nor the code raising the exception needed to know about each other or share current execution state information between one another (such as setjmp/longjmp). Smalltalk effectively builds exception handling on signaling.

The most common exception handling syntax in modern languages builds on try/catch/throw. Some languages have some variants (such as finally, ensure, raise, and so on) but a try/catch model is the most common.

Despite existing in languages dating back more than 40 years, exception throwing and handling is still commonly misunderstood and misused by many programmers. Exceptions are even misused by "seasoned" programmers.

Exceptions are a way of providing hierarchical error communication from one piece of code to another. Through the use of exception types, rather than line numbers/labels or memory locations, handlers can be entirely decoupled from a code that throws exceptions from everything but the current stack frame.

There are other means of communicating errors between different pieces of code in C#, but they're tightly coupled, limiting, and are not "opt-in". You can easily communicate error information back to the calling code by means of a return code. However, this ensures the calling code must handle the error either by responding to the error code or by passing it along to its caller. Everything up the potential call graph is required to "buy in" to the return code concept. Exception handling is generally an "opt-in" error handling syntax, where only code that is deemed able to handle the exception need to handle or even know about exceptions. This means that a call graph dozens of levels deep can support propagating errors to the highest level from the lowest level simply with one try/catch and one throw.

There are some basic practices about exception handling that I won't detail here other than outline them for completeness.

Context: When designing logic flow.

Practice: Don't use exceptions for normal logic flow.

Now, saying "don't use exceptions for normal logic flow" is different than acknowledging that exceptions exist and can occur. The finally keyword provides the ability to acknowledge that exceptions can occur and circumvent yet-to-be-executed code without pulling them into normal logic flow. The finally keyword allows programmers to acknowledge that exceptions occur without a need to handle exceptions and to perform logic that is necessary both when exceptions do occur and when they don't.

For example, when I instantiate a StreamWriter object, I'm actually opening a file. Regardless of whether I have an exception or not, I want to make sure I close this file as soon as possible when I know I'm no longer using it. The garbage collector will be kind enough to clean up for me, but on its own timeframe. If I don't explicitly close my stream writer before the object goes out of scope, I run into a possibility of opening the file twice at one time, possibly with dire consequences. I can use finally to accomplish my goal:

public static class Coordinate
{
public static void MoveUp(this ICoordinate coordinate,
double meters)
{
coordinate.Altitude += meters;
}
}

 

This ensures that regardless of whether the Write of "some text" succeeded, I close my file and flush any potential buffer that StreamWriter might be using.

Context: When writing code that requires execution in the presence of exceptions or not.

Practice: Use a try/finally variant.

Of course, in this example, StreamWriter implements IDisposable and has a Dispose method. I could have written it as follows and gotten the same effect as the previous example:

StreamWriter writer = new StreamWriter("file.txt");
try
{
writer.Write("some text");
}
finally
{
writer.Close();
}

 

But there is a built-in keyword in C# as a shortcut for this particular pattern of using an IDisposable object and calling Dispose when it goes out of scope: the using keyword. For example:

StreamWriter writer = new StreamWriter("file.txt");
try
{
writer.Write("some text");
}
finally
{
writer.Dispose();
}

 

Context: When writing code that requires an object be disposed in the presence of exceptions or not.

Practice: Prefer the using statement.

At this point I think it's important to define what handling exceptions really means. A catch block is technically an exception handler, but exception handling is the process of catching an exception and not letting it escape to a point higher-up the call stack.

So far this may seem fairly simple, and you might be wondering "okay, but how do I handle exceptions?" Handling exceptions and catching exceptions are really two different things (much the same as fruits are vegetables, but all vegetables are not fruits). You must catch an exception to handle it, but catching an exception isn't the only part of handling the exception.

Sometimes you might want to catch an exception, but not handle it. For example:

using (StreamWriter writer = new StreamWriter("file.txt"))
{
writer.Write("some text");
}

 

The example we just saw basically models a transaction. We have a transaction, that means the first file can't exist without a correct second file. If the second file cannot be created, we can't have the first file. It is an example of wanting to catch but not handle an exception. We pass the exception up the stack to something that might be able to catch it and do something useful with it, like inform the user of the problem (which we can't do in this code as it's too low-level). The previous example shows the use of the throw statement without an IOException or Exception instance. This is an example of re-throwing the current exception. This should not be confused with throwing an instance of a caught exception. For example:

using(StreamWriter streamWriter =
new StreamWriter("FirstFile.txt"))
{
streamWriter.WriteLine(FirstFileText);
}
try
{
using (StreamWriter streamWriter =
new StreamWriter("secondFile.txt"))
{
streamWriter.WriteLine(SecondFileText);
}
}
catch (IOException)
{
// delete the first file if
// we had a problem with the second.
File.Delete("FirstFile.txt");
throw;
}

 

The previous example takes the exception instance that was caught (ioException) and passes it to the throw statement. This isn't re-throwing an exception, this is throwing a new exception by re-using an existing exception. This statement is saying that an IOException occurred in this block, not that we want to "re-throw" the existing exception.

This has the distinct drawback of completely losing the context of the original exception (the stack trace) and tells exception handlers higher up the stack that the exception occurred here, but not in the original place. While this propagates an exception, you have no way of figuring out where the original exception occurred and what to do about it if you are trying to debug your code. If you aren't at or before throw ioException, you'll need to restart the program and try to reproduce the exception to figure out what the real problem is. This is almost always not what you want.

Context: When attempting to catch and pass along caught exceptions.

Practice: Re-throw exceptions correctly.

Of course, sometimes you may want to still communicate runtime errors further up the stack, but not re-throw or re-use the current exception context. One possible scenario is that you want provide more, or extra, information. Given the context of your code and the fact that a particular exception occurred, that combination may have more implicit information than the existing exception alone. Despite thousands of person-hours invested into the .NET Framework, not all exception types, and not all thrown exceptions, may provide explicit enough information on their own for an exception handler to act upon a particular exception. There are probably various examples of this in the framework with exceptions such as InvalidOperationException or ArgumentException. Usually these exceptions are very contextual to a specific implementation.

An example of this technique could be in the following form:

StreamWriter writer = new StreamWriter("file.txt");
try
{
writer.Write("some text");
}
catch(IOException ioException)
{
Logger.Error(
String.Format("trying to write to {0}", "file.txt"),
ioException);
throw ioException;
}

 

The fact that we're using a string object is a bit of an implementation which means offset makes for a leaky abstraction. It may not be clear that the offset parameter is an index, making IndexOutOfRangeException potentially unexpected. In this case, we can simply catch the implementation-specific IndexOutOfRangeException and translate it or adapt it to another, more apt exception. In this case, we chose ArgumentException. It tells the caller that a particular argument (offset, in our example) was invalid. You could go so far as to create your own application-specific exception to communicate this, or more, information to the caller. But for our example, we'll just use one of the system exceptions.

Context: When using implementation-specific code.

Practice: Don't let confusing implementation-specific exceptions leak your implementation details.

At this point, I hope it seems clear that catching an exception should be an explicit act. You're either handling the exception or re-throwing the exception. But I think it's important to point out that you shouldn't be using catch if you are not handling an exception or re-throwing it. I can't think about a reason why you'd use catch if you weren't doing one of these two things, but I've seen my fair share of the following lines of code:

public char GetCharacter(int offset)
{
try
{
return text[index];
}
catch (IndexOutOfRangeException indexOutOfRangeException)
{
throw new ArgumentException("Offset is out of range",
"offset",
indexOutOfRangeException);
}
}

 

I don't know why I've seen this so often, but don't do it.

Context: When thinking about using catch.

Practice: Don't catch exceptions you don't handle or re-throw.

Handling an exception means not allowing the exception to propagate up the stack to other handlers of that exception, to its bases, or to the top-level exception handler. Another name for this is exception swallowing. It's important that if you do catch an exception and swallow it, that you are truly handling the exception. For example:

catch (Exception)
{
throw;
}

 

The programmer probably thought it was fairly innocuous to not allow any exceptions to escape from this method. It presumably had the context to log things to some destination, so he probably fi gured logging the error and "swallowing and forgetting" it seemed like the best option.

Unfortunately, in this case, this method doesn't own the Stream object, so swallowing the exception keeps any callers further up the stack from knowing that there's a problem. This means they're free to use their Stream instance that may be in a really bad state. The best case is that the same type of exception, with the same details, is thrown when they try to use stream again. But the worst case is that another exception is thrown that provides no detail as to why (because that was swallowed elsewhere), leading the developer on a wild goose chase to figure out what went wrong when, had they simply gotten the original exception, they may have had all of the information at their fingertips.

Context: When designing whether or not to catch exceptions.

Practice: Don't swallow and forget.

Exception handling is about knowing what exception occurred and compensating for it. Being able to compensate for exceptions generally requires some specific knowledge. Some times compensation is usually delegated to the end user. For example, while writing to a file I may get an IOException because I have run out of disk space. I could, of course, not handle that exception and let the application terminate abnormally losing any in-memory data. Most end users I deal with don't like this for some reason. While it's difficult to specifically tell if an exception is due to out of disk space conditions (and more difficult to actually do anything about lack of disk space programmatically), it's fairly easy to inform the user of the problem and let them retry the action in question. For example:

public void DoLittle(Stream stream)
{
try
{
stream.Write(myData, 0, myData.Length);
}
catch (Exception exception)
{
GetLogger().Error("", exception);
}
}

 

This code recognizes the fact that BinaryWriter.Write can fail for a number of IO problems, all of which are abstracted within a single IOException class. This code just passes along the message to the IOException class that contains and asks the user to press the Retry button if they have fi xed the problem. The code itself doesn't really do anything to handle the exception. Instead, it delegates to the user then trusts the user to press Retry to signify that the problem was "handled."

The ability to do things such as this hinges on the ability to discern specific types, and thus specific contexts of exceptions. You cannot do this if your catch block catches Exception.

Context: When catching exceptions.

Practice: Don't catch all exceptions (that is Exception).

Wow, you might be thinking exceptions are all about how to correctly handle exceptions. But people are prone to errors when they throw exceptions too. As with many other tools, exceptions can help you only if they're used properly. Exceptions are used to provide actionable error information to indeterminate code further up the call stack. The key point here is actionable. Providing code with the fact that something went wrong as an exception without details of how they could possibly compensate for the problem really means the code must simply pass the exception along to eventually let the application terminate abnormally. For example:

private void saveButton_Click(object sender, EventArgs e)
{
bool retry = false;
do
{
try
{
using(Stream stream =
new FileStream(filePath,
FileMode.OpenOrCreate,
FileAccess.Write))
using(BinaryWriter streamWriter =
new BinaryWriter(stream))
{
streamWriter.Write(applicationData);
}
}
catch (IOException ioException)
{
retry = DialogResult.Retry == MessageBox.Show(this,
"Error",
String.Format("The following error "+
"occurred writing to a file:"+
"{0}{1}{0}If you have fixed the "+
"problem, press Retry",
ioException.Message,
Environment.NewLine),
MessageBoxButtons.RetryCancel);
}
} while (retry);
}

 

Not being able to programmatically take action on an exception doesn't provide much value to the end users (lost data, lost progress, lost time, and so on, doesn't generally make them happy).

Context: When designing code that throws exceptions.

Practice: Create explicit exception types to model situations that require explicit handling.

Probably one of the best examples of violating everything that exceptions stand for is this simple snippet of code: throw new Exception().

In order to catch this exception, a handler would be required to write catch Exception exception (or simply catch), which seems innocuous, but this also means that it will also catch every single other possible exception.

The throw new Exception() snippet of code provides no intent, which means catching it can infer no intent either. The handler cannot know if it was your throw new Exception() intent that it's catching or some other intent such as FileNotFoundException. In which case, the handler cannot know, based on the type alone, that it's truly handling an exception correctly or hiding an exception. Throwing Exception is usually just hiding the fact that exceptions are being used for normal program fl ow.

Yes, you could use the Exception instance's GetType() method to figure out if the type is actually Exception or not, or you could use the Exception instance's Message property to textually compare to a string literal, but don't. You're only limiting the possible miscaught exceptions, or not illuminating them (for example, someone else used throw new Exception() or used "something went wrong" as the exception message).

Context: When writing code to communicate runtime errors.

Practice: Don't throw Exception.

One of the huge benefi ts of exception handling in C# is the ability to decouple the code raising the runtime error, from the code handling the runtime error, based on an Exception-derived type. This means that you can communicate common runtime errors, or you can communicate specific runtime errors. If you perform an action whose runtime error is best abstracted by throwing FileNotFoundException, you can simply throw a FileNotFoundException and handlers further up the stack will know what you mean. If you have a specific circumstance that is specific to your application and a specific context, then you can create a unique exception type to model that runtime error. That way, only things that catch Exception or your specific exception can handle the runtime error condition.

This ideally means that the condition by which a handler decides to handle an exception is based solely on the type of the exception. Everything within the catch block is devoted to handling the exception, and nothing else.

If you need to resort to some state on the exception instance (such as Message), then the exception isn't designed properly, or the faulting code isn't throwing the right exception. Your exception handler is forced to take on too much responsibility and you're not using C#'s exception abilities to their greatest potential. You've effectively created two places where the decision to handle an exception is made.

Context: When designing exceptions.

Practice: Prefer to rely on exception type rather than exception state.

Exceptions to the exception practices

As with any practices, they're contextual. We've detailed some really useful exception practices, but most don't have a universal context. There are places where you do "violate" these practices. Let's have a look at a few.

There's at least one place in every program that relinquishes control back to the system and never comes back. In .NET, this is generally the return from the Program.Main method. Whatever exception is allowed to be thrown out of Program. Main exits the application. The system might do something unspecific with it, such as dump it to a console, but from the application's point of view, this means we're done. None of our code is generally going to run again for this invocation.

This is a unique situation. This effectively means that there aren't any other handlers further up the call stack, so any practices we have that are meant to alleviate problems with handlers further up the call stack are moot. I call this situation the top-level exception handler or the last-chance exception handler. This handler has some special status. It can do things we'd normally not want to do in any other exception handler.

The best example is catching Exception. In a top-level exception handler, we have a good reason to catch Exception. We may want to log that exception before we terminate. This is the last chance we have to do anything with the exception, such as track it. For example:

if(x != 42)
{
throw new Exception("something went wrong");
}

 

In this example, we delegate from the Main method , to DoAllTheThings, presumably to do all of the work and leave Main responsible to be the top-level exception handler. Then, we catch Exception and dump the details that we find important out to a text file called log.txt.

Context: When considering designing reliable applications.

Practice: Consider thread/process entry-points as last-chance exception handlers.

The Program.Main method isn't the only place where we have a top-level exception handler. The entry-points of other threads are also top-level exception handlers. I detail "other" because every process has at least one thread, and with .NET, the entry-point for that thread is really Program.Main.

By default threads in .NET are actions rather than functions. They don't communicate results back to the invoking thread. There are all sorts of application-specific magic that you can write to communicate results back from a newly spawned thread, but there are better ways of doing that nowadays. Suffice it to say that if you're not communicating results from one thread to another, you probably don't have an infrastructure to communicate any exceptions that occurred during processing of that action. To that end, it's useful to consider entry-points such as this as last-chance exception handlers and do what you need to do to make your application robust and reliable, such as log the problem. The following code is very much similar to what we did in Program.Main:

static void Main(string[] args)
{
try
{
DoAllTheThings(args);
}
catch (Exception exception)
{
using(StreamWriter writer =
new StreamWriter("log.txt"))
{
writer.Write(
string.Format(
"Exception:{0}{1}{0}{2}",
Environment.NewLine,
exception.Message,
exception.StackTrace));
}
}
}

 

For interest's sake, you may invoke this thread entry on its own thread with code similar to the following:

private static void ThreadEntry()
{
try
{
DoAllTheThingsInBackground();
}
catch (Exception exception)
{
GetLogger().Error("Oops, ran into a problem.",
exception);
}
}

 

You may not want to simply catch Exception and log it with all thread "entry-points." The framework provides the ability to spawn threads in order to calculate results asynchronously. In these circumstances, the ability to communicate the results back to the calling thread (or back to a specific thread) usually includes the ability to throw these exceptions asynchronously.

Context: When considering top-level exception handlers in "thread entry-points."

Practice: Avoid swallowing exceptions in situations where asynchronous exceptions are supported.

In these cases I generally recommend using thread pool threads and parts of the framework such as the Task Parallel Library (TPL), instead of manually creating threads.

Summary

As we've seen, there are many practices that we can use in our everyday programming and design that go a level beyond just the syntax of the language we're using. Syntax is often is added to a language for specific reasons. It's useful to know the principles behind why the syntax changed or was added to. This article covered some more recent additions to the language, such as lambdas and extension methods. With some simple practices we can make better use of this syntax.

Visual Studio 2010 Best Practices Learn and implement recommended practices for the complete software development lifecycle with Visual Studio 2010 with this book and ebook.
Published: August 2012
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

Further resources on this subject:


About the Author :


Peter Ritchie

Peter Ritchie is a software development consultant. Peter is president of Peter Ritchie Inc. Software Consulting Co., a software consulting company in Canada's National Capital Region specializing in Windows-based software development management, process, and implementation consulting. Peter has worked with such clients as Mitel, Nortel, Passport Canada, and Innvapost from mentoring to architecture to implementation. Peter has considerable experience building software development teams and working with startups towards agile software development.

Peter's range of experience ranges from designing and implementing simple standalone applications to architecting distributed n-tier applications spanning dozens of computers; from C++ to C#. Peter is active in the software development community attending and speaking at various events as well as authoring various works including Refactoring with Microsoft Visual Studio 2010.

Books From Packt


Software Testing using Visual Studio 2010
Software Testing using Visual Studio 2010

Microsoft Visual Studio LightSwitch Business Application Development
Microsoft Visual Studio LightSwitch Business Application Development

Refactoring with Microsoft Visual Studio 2010
Refactoring with Microsoft Visual Studio 2010

Visual Studio 2012 Cookbook
Visual Studio 2012 Cookbook

Software Testing with Visual Studio Team System 2008
Software Testing with Visual Studio Team System 2008

Microsoft SharePoint 2010 Development with Visual Studio 2010 Expert Cookbook
Microsoft SharePoint 2010 Development with Visual Studio 2010 Expert Cookbook

Microsoft SharePoint 2010 Developer’s Compendium: The Best of Packt for Extending SharePoint
Microsoft SharePoint 2010 Developer’s Compendium: The Best of Packt for Extending SharePoint

Microsoft SharePoint 2010 Business Application Blueprints
Microsoft SharePoint 2010 Business Application Blueprints


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
6
C
w
C
u
A
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software