Low-level C# Practices

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:

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:

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:

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:

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.

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:

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:

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:

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:

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:

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.

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.

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:

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:

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:

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.

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:

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.

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.

Alerts & Offers

Series & Level

We understand your time is important. Uniquely amongst the major publishers, we seek to develop and publish the broadest range of learning and information products on each technology. Every Packt product delivers a specific learning pathway, broadly defined by the Series type. This structured approach enables you to select the pathway which best suits your knowledge level, learning style and task objectives.

Learning

As a new user, these step-by-step tutorial guides will give you all the practical skills necessary to become competent and efficient.

Beginner's Guide

Friendly, informal tutorials that provide a practical introduction using examples, activities, and challenges.

Essentials

Fast paced, concentrated introductions showing the quickest way to put the tool to work in the real world.

Cookbook

A collection of practical self-contained recipes that all users of the technology will find useful for building more powerful and reliable systems.

Blueprints

Guides you through the most common types of project you'll encounter, giving you end-to-end guidance on how to build your specific solution quickly and reliably.

Mastering

Take your skills to the next level with advanced tutorials that will give you confidence to master the tool's most powerful features.

Starting

Accessible to readers adopting the topic, these titles get you into the tool or technology so that you can become an effective user.

Progressing

Building on core skills you already have, these titles share solutions and expertise so you become a highly productive power user.