Chapter 10. A smörgåsbord of features for concise code

This chapter covers

  • Avoiding code clutter when referring to static members
  • Being more selective in importing extension methods
  • Using extension methods in collection initializers
  • Using indexers in object initializers
  • Writing far fewer explicit null checks
  • Catching only exceptions you’re really interested in

This chapter is a grab bag of features. No particular theme runs through it besides expressing your code’s intention in ever leaner ways. The features in this chapter are the ones left over when all the obvious ways of grouping features have been used. That doesn’t in any way undermine their usefulness, however.

10.1. Using static directives

The first feature we’ll look at provides a simpler way of referring to static members of a type, including extension methods.

10.1.1. Importing static members

The canonical example for this feature is System.Math, which is a static class and so has only static members. You’re going to write a method that converts from polar coordinates (an angle and a distance) to Cartesian coordinates (the familiar (x, y) model) using the more human-friendly degrees instead of radians to express the angle. Figure 10.1 gives a concrete example of how a single point is represented in both coordinate systems. Don’t worry if you’re not totally comfortable with the math part of this; it’s just an example that uses a lot of static members in a short piece of code.

Figure 10.1. An example of polar and Cartesian coordinates

Assume that you already have a Point type representing Cartesian coordinates in a simple way. The conversion itself is fairly simple trigonometry:

  • Convert the angle from degrees into radians by multiplying it by π/180. The constant π is available via Math.PI.
  • Use the Math.Cos and Math.Sin methods to work out the x and y components of a point with magnitude 1, and multiply up.

The following listing shows the complete method with the uses of System.Math in bold. I’ve omitted the class declaration for convenience. It could be in a CoordinateConverter class, or it could be a factory method in the Point type itself.

Listing 10.1. Polar-to-Cartesian conversion in C# 5
using System;
...
static Point PolarToCartesian(double degrees, double magnitude)
{
    double radians = degrees * Math.PI / 180;      1
    return new Point(                              2
        Math.Cos(radians) * magnitude,             2
        Math.Sin(radians) * magnitude);            2
}

  • 1 Converts degrees into radians
  • 2 Trigonometry to complete conversion

Although this code isn’t terribly hard to read, you can imagine that as you write more math-related code, the repetition of Math. clutters the code considerably.

C# 6 introduced the using static directive to make this sort of code simpler. The following listing is equivalent to listing 10.1 but imports all the static members of System .Math.

Listing 10.2. Polar-to-Cartesian conversion in C# 6
using static System.Math;
...
static Point PolarToCartesian(double degrees, double magnitude)
{
    double radians = degrees * PI / 180;                  1
    return new Point(                                     2
        Cos(radians) * magnitude,                         2
        Sin(radians) * magnitude);                        2
}

  • 1 Converts degrees into radians
  • 2 Trigonometry to complete conversion

As you can see, the syntax for a using static directive is simple:

using static type-name-or-alias;

With that in place, all the following members are available directly by using their simple names rather than having to qualify them with the type:

  • Static fields and properties
  • Static methods
  • Enum values
  • Nested types

The ability to use enum values directly is particularly useful in switch statements and anywhere you combine enum values. The following side-by-side example shows how to retrieve all the fields of a type with reflection. The text in bold highlights the code that can be removed with an appropriate using static directive.

C# 5 code

With using static in C# 6

using System.Reflection;
...
var fields = type.GetFields(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.Public |
BindingFlags.NonPublic)
using static System.Reflection.BindingFlags;
...
var fields = type.GetFields(
Instance | Static | Public | NonPublic);

Similarly, a switch statement responding to specific HTTP status codes can be made simpler by avoiding the repetition of the enum type name in every case label:

C# 5 code

With using static in C# 6

using System.Net;

...
switch (response.StatusCode)
{
case HttpStatusCode.OK:
...
case HttpStatusCode.TemporaryRedirect:
case HttpStatusCode.Redirect:
case HttpStatusCode.RedirectMethod:
...
case HttpStatusCode.NotFound:
...
default:
...
}
using static
System.Net.HttpStatusCode;
...
switch (response.StatusCode)
{
case OK:
...
case TemporaryRedirect:
case Redirect:
case RedirectMethod:
...
case NotFound:
...
default:
...
}

Nested types are relatively rare in handwritten code, but they’re more common in generated code. If you use them even occasionally, the ability to import them directly in C# 6 can significantly declutter your code. As an example, my implementation of the Google Protocol Buffers serialization framework to C# generates nested types to represent nested messages declared in the original .proto file. One quirk is that the nested C# types are doubly nested to avoid naming collisions. Say you have an original .proto file with a message like this:

message Outer {
  message Inner {
     string text = 1;
  }

  Inner inner = 1;
}

The code that’s generated has the following structure with a lot more other members, of course:

public class Outer
{
    public static class Types
    {
        public class Inner
        {
            public string Text { get; set; }
        }
    }

    public Types.Inner Inner { get; set; }
}

To refer to Inner from your code in C# 5, you had to use Outer.Types.Inner, which is painful. The double nesting became considerably less inconvenient with C# 6, where it becomes relegated to a single using static directive:

using static Outer.Types;
...
Outer outer = new Outer { Inner = new Inner { Text = "Some text here" } };

In all these cases, the members that are available via the static imports are considered during member lookup only after other members have been considered. For example, if you have a static import of System.Math but you also have a Sin method declared in your class, a call to Sin() will find your Sin method rather than the one in Math.

The imported type doesn’t have to be static

The static part of using static doesn’t mean that the type you import must be static. The examples shown so far have been, but you can import regular types, too. That lets you access the static members of those types without qualification:

using static System.String;
...
string[] elements = { "a", "b" };
Console.WriteLine(Join(" ", elements));     1

  • 1 Access String.Join by its simple name

I haven’t found this to be as useful as the earlier examples, but it’s available if you want it. Any nested types are made available by their simple names, too. There’s one exception to the set of static members that’s imported with a using static directive that isn’t quite so straightforward, and that’s extension methods.

10.1.2. Extension methods and using static

One aspect of C# 3 that I was never keen on was the way extension methods were discovered. Importing a namespace and importing extension methods were both performed with a single using directive; there was no way of doing one without the other and no way of importing extension methods from a single type. C# 6 improves the situation, although some of the aspects I dislike couldn’t be fixed without breaking backward compatibility.

The two important ways in which extension methods and using static directives interact in C# 6 are easy to state but have subtle implications:

  • Extension methods from a single type can be imported with a using static directive for that type without importing any extension methods from the rest of the namespace.
  • Extension methods imported from a type aren’t available as if you were calling a regular static method like Math.Sin. Instead, you have to call them as if they were instance methods on the extended type.

I’ll demonstrate the first point by using the most commonly used set of extension methods in all of .NET: the ones for LINQ. The System.Linq.Queryable class contains extension methods for IQueryable<T> accepting expression trees, and the System.Linq.Enumerable class contains extension methods for IEnumerable<T> accepting delegates. Because IQueryable<T> inherits from IEnumerable<T> with a regular using directive for System.Linq, you can use the extension methods accepting delegates on IQueryable<T>, although you usually don’t want to. The following listing shows how a using static directive for just System.Linq.Queryable means the extension methods in System.Linq.Enumerable aren’t picked up.

Listing 10.3. Selective importing of extension methods
using static System.Linq.Queryable;
...
var query = new[] { "a", "bc", "d" }.AsQueryable();     1

Expression<Func<string, bool>> expr =                   2
    x => x.Length > 1;                                  2
Func<string, bool> del = x => x.Length > 1;             2

var valid = query.Where(expr);                          3
var invalid = query.Where(del);                         4

  • 1 Creates an IQueryable<string>
  • 2 Creates a delegate and expression tree
  • 3 Valid: uses Queryable.Where
  • 4 Invalid: no in-scope Where method accepts a delegate

One point that’s worth noting is that if you accidentally imported System.Linq with a regular using directive, such as to allow query to be explicitly typed, that would silently make the last line valid.

The impact of this change should be considered carefully by library authors. If you wish to include some extension methods but allow users to explicitly opt into them, I encourage the use of a separate namespace for that purpose. The good news is that you can now be confident that any users—at least those with C# 6—can be selective in which extension methods to import without you having to create many namespaces. For example, in Noda Time 2.0, I introduced a NodaTime.Extensions namespace with extension methods targeting many types. I expect that some users will want to import only a subset of those extension methods, so I split the method declarations into several classes, with each class containing methods extending a single type. In other cases, you may wish to split your extension methods along different lines. The important point is that you should consider your options carefully.

The fact that extension methods can’t be called as if they were regular static methods is also easily demonstrated using LINQ. Listing 10.4 shows this by calling the Enumerable.Count method on a sequence of strings: once in a valid way as an extension method, as if it were an instance method declared in IEnumerable<T>, and once attempting to use it as a regular static method.

Listing 10.4. Attempting to call Enumerable.Count in two ways
using System.Collections.Generic;
using static System.Linq.Enumerable;
...
IEnumerable<string> strings = new[] { "a", "b", "c" };

int valid = strings.Count();         1
int invalid = Count(strings);        2

  • 1 Valid: calling Count as if it were an instance method
  • 2 Invalid: extension methods aren’t imported as regular static methods

Effectively, the language is encouraging you to think of extension methods as different from other static methods in a way that it didn’t before. Again, this has an impact on library developers: converting a method that already existed in a static class into an extension method (by adding the this modifier to the first parameter) used to be a nonbreaking change. As of C# 6, that becomes a breaking change: callers who were importing the method with a using static directive would find that their code no longer compiled after the method became an extension method.

Note

Extension methods discovered via static imports aren’t preferred over extension methods discovered through namespace imports. If you make a method call that isn’t handled by regular method invocation, but multiple extension methods are applicable via imported namespaces or classes, overload resolution is applied as normal.

Just like extension methods, object and collection initializers were largely added to the language as part of the bigger feature of LINQ. And just like extension methods, they’ve been tweaked in C# 6 to make them slightly more powerful.

10.2. Object and collection initializer enhancements

As a reminder, object and collection initializers were introduced in C# 3. Object initializers are used to set properties (or, more rarely, fields) in newly created objects; collection initializers are used to add elements to newly created collections via the Add methods that the collection type supports. The following simple example shows initializing a Windows Forms Button with text and a background color as well as initializing a List<int> with three values:

Button button = new Button { Text = "Go", BackColor = Color.Red };
List<int> numbers = new List<int> { 5, 10, 20 };

C# 6 enhances both of these features and makes them slightly more flexible. These enhancements aren’t as globally useful as some of the other features in C# 6, but they’re still welcome additions. In both cases, the initializers have been expanded to include members that previously couldn’t be used there: object initializers can now use indexers, and collection initializers can now use extension methods.

10.2.1. Indexers in object initializers

Until C# 6, object initializers could invoke only property setters or set fields directly. C# 6 allows indexer setters to be invoked as well using the [index] = value syntax used to invoke them in regular code.

To demonstrate this in a simple way, I’ll use StringBuilder. This would be a fairly unusual usage, but we’ll talk about best practices shortly. The example initializes a StringBuilder from an existing string ("This text needs truncating"), truncates the builder to a set length, and modifies the last character to a Unicode ellipsis (...). When printed to the console, the result is "This text...". Before C# 6, you couldn’t have modified the last character within the initializer, so you would’ve ended up with something like this:

string text = "This text needs truncating";
StringBuilder builder = new StringBuilder(text)             
{                                                           
    Length = 10                                1
};
builder[9] = 'u2026';                         2
Console.OutputEncoding = Encoding.UTF8;        3
Console.WriteLine(builder);                    4

  • 1 Sets the Length property to truncate the builder
  • 2 Modifies the final character to “...”
  • 3 Makes sure the console will support Unicode
  • 4 Prints out the builder content

Given how little the initializer is giving you (a single property), I’d at least consider setting the length in a separate statement instead. C# 6 allows you to perform all the initialization you need in a single expression, because you can use the indexer within the object initializer. The following listing demonstrates this in a slightly contrived way.

Listing 10.5. Using an indexer in a StringBuilder object initializer
string text = "This text needs truncating";
StringBuilder builder = new StringBuilder(text)             
{                                                           
    Length = 10,                              1
    [9] = 'u2026'                            2
};
Console.OutputEncoding = Encoding.UTF8;       3
Console.WriteLine(builder);                   4

  • 1 Sets the Length property to truncate the builder
  • 2 Modifies the final character to “...”
  • 3 Makes sure the console will support Unicode
  • 4 Prints out the builder content

I deliberately chose to use StringBuilder here not because it’s the most obvious type containing an indexer but to make it clear that this is an object initializer rather than a collection initializer.

You might have expected me to use a Dictionary<,> of some kind instead, but there’s a hidden danger here. If your code is correct, it’ll work as you’d expect, but I recommend sticking to using a collection initializer in most cases. To see why, let’s look at an example initializing two dictionaries: one using indexers in an object initializer and one using a collection initializer.

Listing 10.6. Two ways of initializing a dictionary
var collectionInitializer = new Dictionary<string, int>    1
{
    { "A", 20 },
    { "B", 30 },
    { "B", 40 }
};

var objectInitializer = new Dictionary<string, int>        2
{
    ["A"] = 20,
    ["B"] = 30,
    ["B"] = 40
};

  • 1 Regular collection initializer from C# 3
  • 2 Object initializer with indexer in C# 6

Superficially, these might look equivalent. When you have no duplicate keys, they’re equivalent, and I even prefer the appearance of the object initializer. But the dictionary indexer setter overwrites any existing entry with the same key, whereas the Add method throws an exception if the key already exists.

Listing 10.6 deliberately includes the "B" key twice. This is an easy mistake to make, usually as the result of copying and pasting a line and then forgetting to modify the key part. The error won’t be caught at compile time in either case, but at least with the collection initializer, it doesn’t do the wrong thing silently. If you have any unit tests that execute this piece of code—even if they don’t explicitly check the contents of the dictionary—you’re likely to find the bug quickly.

Roslyn to the rescue?

Being able to spot this bug at compile time would be better, of course. It should be possible to write an analyzer to spot this problem for both collection and object initializers. For object initializers using an indexer, it’s hard to imagine many cases where you’d legitimately want to specify the same constant indexer key multiple times, so popping up a warning seems entirely reasonable.

I don’t know of any such analyzer yet, but I hope it’ll exist at some point. With that danger cleared, there’d be no reason not to use indexers with dictionaries.

So when should you use an indexer in an object initializer rather than a collection initializer? You should do so in a few reasonably obvious cases, such as the following:

  • If you can’t use a collection initializer because the type doesn’t implement IEnumerable or doesn’t have suitable Add methods. (You can potentially introduce your own Add methods as extension methods, however, as you’ll see in the next section.) For example, ConcurrentDictionary<,> doesn’t have Add methods but does have an indexer. It has TryAdd and AddOrUpdate methods, but those aren’t used by the collection initializer. You don’t need to worry about concurrent updates to the dictionary while you’re in an object initializer, because only the initializing thread has any knowledge of the new dictionary.
  • If the indexer and the Add method would handle duplicate keys in the same way. Just because dictionaries follow the “throw on add, overwrite in the indexer” pattern doesn’t mean that all types do.
  • If you’re genuinely trying to replace elements rather than adding them. For example, you might be creating one dictionary based on another and then replacing the value corresponding to a particular key.

Less clear-cut cases exist as well in which you need to balance readability against the possibility of the kind of error described previously. Listing 10.7 shows an example of a schemaless entity type with two regular properties, but that otherwise allows arbitrary key/value pairs for its data. You’ll then look at the options for how you might initialize an instance.

Listing 10.7. A schemaless entity type with key properties
public sealed class SchemalessEntity
    : IEnumerable<KeyValuePair<string, object>>
{
    private readonly IDictionary<string, object> properties =
        new Dictionary<string, object>();

    public string Key { get; set; }
    public string ParentKey { get; set; }

    public object this[string propertyKey]
    {
        get { return properties[propertyKey]; }
        set { properties[propertyKey] = value; }
    }

    public void Add(string propertyKey, object value)
    {
        properties.Add(propertyKey, value);
    }

    public IEnumerator<KeyValuePair<string, object>> GetEnumerator() =>
        properties.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

Let’s consider two ways of initializing an entity for which you want to specify a parent key, the new entity’s key, and two properties (a name and location, just as simple strings). You can use a collection initializer but then set the other properties afterward or do the whole thing with an object initializer but risk typos in the keys. The following listing demonstrates both options.

Listing 10.8. Two ways of initializing a SchemalessEntity
SchemalessEntity parent = new SchemalessEntity { Key = "parent-key" };
SchemalessEntity child1 = new SchemalessEntity       1
{                                                                   
    { "name", "Jon Skeet" },                                        
    { "location", "Reading, UK" }                                   
};                                                                  
child1.Key = "child-key";                            2
child1.ParentKey = parent.Key;                       2

SchemalessEntity child2 = new SchemalessEntity
{
    Key = "child-key",                               3
    ParentKey = parent.Key,                          3
    ["name"] = "Jon Skeet",                          4
    ["location"] = "Reading, UK"                     4
};

  • 1 Specifies data properties with a collection initializer
  • 2 Specifies key properties separately
  • 3 Specifies key properties in an object initializer
  • 4 Specifies data properties using indexers

Which of these approaches is better? The second looks a lot cleaner to me. I’d typically extract the name and location keys into string constants anyway, at which point the risk of accidentally using duplicate keys is at least reduced.

If you’re in control of a type like this, you can add extra members to allow you to use a collection initializer. You could add a Properties property that either exposes the dictionary directly or exposes a view over it. At that point, you could use a collection initializer to initialize Properties within an object initializer that also sets Key and ParentKey. Alternatively, you could provide a constructor that accepts the key and parent key, at which point you can make an explicit constructor call with those values and then specify the name and location properties with a collection initializer.

This may feel like a huge amount of detail for a choice between using indexers in an object initializer or using a collection initializer as in previous versions. The point is that the choice is yours to make: no book will be able to give you simple rules to follow that give you a best answer in every case. Be aware of the pros and cons, and apply your own judgment.

10.2.2. Using extension methods in collection initializers

A second change in C# 6 related to object and collection initializers concerns which methods are available in collection initializers. As a reminder, two conditions must be met in order to use a collection initializer with a type:

  • The type must implement IEnumerable. I’ve found this to be an annoying restriction; sometimes I implement IEnumerable solely so I can use the type in collection initializers. But it is what it is. This restriction hasn’t changed in C# 6.
  • There must be a suitable Add method for every element in the collection initializer. Any elements that aren’t in curly braces are assumed to correspond to single-argument calls to Add methods. When multiple arguments are required, they must be in curly braces.

Occasionally, this can be a little restrictive. Sometimes you want to easily create a collection in a way that the Add methods supplied by the collection itself don’t support. The preceding conditions are still true in C# 6, but the definition of “suitable” in the second condition now includes extension methods. In some ways, this has simplified the transformation. Here’s a declaration using a collection initializer:

List<string> strings = new List<string>
{
    10,
    "hello",
    { 20, 3 }
};

That declaration is essentially equivalent to this:

List<string> strings = new List<string>();
strings.Add(10);
strings.Add("hello");
strings.Add(20, 3);

The normal overload resolution is applied to work out what each of those method calls means. If that fails, the collection initializer won’t compile. With just the regular List<T>, the preceding code won’t compile, but if you add a single extension method it will:

public static class StringListExtensions
{
    public static void Add(
    this List<string> list, int value, int count = 1)
    {
        list.AddRange(Enumerable.Repeat(value.ToString(), count));
    }
}

With this in place, the first and last calls to Add in our earlier code end up calling the extension method. The list ends up with five elements ("10", "hello", "20", "20", "20"), because the last Add call adds three elements. This is an unusual extension method, but it helps demonstrate three points:

  • Extension methods can be used in collection initializers, which is the whole point of this section of the book.
  • This isn’t a generic extension method; it works for only List<string>. This is a kind of specialization that couldn’t be performed in List<T> itself. (Generic extension methods are fine, too, so long as the type arguments can be inferred.)
  • Optional parameters can be used in the extension methods; our first call to Add will effectively be compiled to Add(10, 1) because of the default value of the second parameter.

Now that you know what you can do, let’s take a closer look at where it makes sense to use this feature.

Creating other general-purpose Add signatures

One technique I’ve found useful in my work with Protocol Buffers is to create Add methods accepting collections. This process is like using AddRange but it can be used in collection initializers. This is particularly useful within object initializers in which the property you’re initializing is read-only but you want to add the results of a LINQ query.

For example, consider a Person class with a read-only Contacts property that you want to populate with all the contacts from another list who live in Reading. In Protocol Buffers, the Contacts property would be of type RepeatedField<Person>, and RepeatedField<T> has the appropriate Add method, allowing you to use a collection initializer:

Person jon = new Person
{
    Name = "Jon",
    Contacts = { allContacts.Where(c => c.Town == "Reading") }
};

It can take a little getting used to, but then it’s extremely useful and certainly beats having to call jon.Contacts.AddRange(...) separately. But what if you weren’t using Protocol Buffers, and Contacts was exposed only as List<Person> instead? With C# 6, that’s not a problem: you can create an extension method for List<T> that adds an overload of Add accepting an IEnumerable<T> and calling AddRange with it, as shown in the following listing.

Listing 10.9. Exposing explicit interface implementations via extension methods
static class ListExtensions
{
    public static void Add<T>(this List<T> list, IEnumerable<T> collection)
    {
        list.AddRange(collection);
    }
}

With that extension method in place, the earlier code works fine even with List<T>. If you wanted to be broader still, you could write an extension method targeting IList<T> instead, although if you went down that route, you’d need to write the loop within the method body because IList<T> doesn’t have an AddRange method.

Creating specialized Add signatures

Suppose you have a Person class, as shown earlier, with a Name property, and within one area of code you do a lot of work with Dictionary<string, Person> objects, always indexing the Person objects by name. Adding entries to the dictionary with a simple call to dictionary.Add(person) can be convenient, but Dictionary<string, Person> doesn’t, as a type, know that you’re indexing by name. What are your choices?

You could create a class derived from Dictionary<string, Person> and add an Add(Person) method to it. That doesn’t appeal to me, because you’re not specializing the behavior of the dictionary in any meaningful way; you’re just making it more convenient to use.

You could create a more general class implementing IDictionary<TKey, TValue> that accepts a delegate explaining the mapping from TValue to TKey and implement that via composition. That could be useful but may be overkill for this one task. Finally, you could create an extension method for this one specific case, as shown in the following listing.

Listing 10.10. Adding a type-argument-specific Add method for dictionaries
static class PersonDictionaryExtensions
{
    public static void Add(
        this Dictionary<string, Person> dictionary, Person person)
    {
        dictionary.Add(person.Name, person);
    }
}

That already would’ve been a good option before C# 6, but the combination of using the using static feature to limit the way extension methods are imported along with the use of extension methods in collection initializers makes it more compelling. You can then initialize a dictionary without any repetition of the name:

var dictionary = new Dictionary<string, Person>
{
    { new Person { Name = "Jon" } },
    { new Person { Name = "Holly" } }
};

An important point here is how you’ve specialized the API for one particular combination of type arguments to Dictionary<,> but without changing the type of object you’re creating. No other code needs to be aware of the specialization here, because it’s only superficial; it exists only for our convenience rather than being part of an object’s inherent behavior.

Note

This approach has downsides as well, one of which is that nothing prevents an entry from being added by using something other than a person’s name. As ever, I encourage you to think through the pros and cons for yourself; don’t blindly trust my advice or anyone else’s.

Reexposing existing methods “hidden” by explicit interface implementation

In section 10.2.1, I used ConcurrentDictionary<,> as an example of where you might want to use an indexer instead of a collection initializer. Without any extra help, you can’t use a collection initializer because no Add method is exposed. But ConcurrentDictionary<,> does have an Add method; it’s just that it uses explicit interface implementation to implement IDictionary<,>.Add. Usually, if you want to access a member that uses explicit interface implementation, you have to cast to the interface—but you can’t do that in a collection initializer. Instead, you can expose an extension method, as shown in the following listing.

Listing 10.11. Exposing explicit interface implementations via extension methods
public static class DictionaryExtensions
{
    public static void Add<TKey, TValue>(
        this IDictionary<TKey, TValue> dictionary,
        TKey key, TValue value)
    {
        dictionary.Add(key, value);
    }
}

At first glance, this looks completely pointless. It’s an extension method to call a method with exactly the same signature. But this effectively works around explicit interface implementation, making the Add method always available, including in collection initializers. You can now use a collection initializer for ConcurrentDictionary<,>:

var dictionary = new ConcurrentDictionary<string, int>
{
    { "x", 10 },
    { "y", 20 }
};

This should be used cautiously, of course. When a method is obscured by explicit interface implementation, that’s often meant to discourage you from calling it without a certain amount of care. This is where the ability to selectively import extension methods with using static is useful: you could have a namespace of static classes with extension methods that are meant to be used only selectively and import just the relevant class in each case. Unfortunately, it still exposes the Add method to the rest of the code in the same class, but again you need to weigh whether that’s worse than the alternatives.

The extension method in listing 10.11 is broad, extending all dictionaries. You could decide to target only ConcurrentDictionary<,> instead to avoid inadvertently using an explicitly implemented Add method from another dictionary type.

10.2.3. Test code vs. production code

You’ve probably noticed a lot of caveats in this section. Few clear-cut cases exist that enable you to “definitely use it here” with respect to these features. But most of the downsides I’ve noted are in terms of areas where the feature is convenient in one piece of code but you don’t want it infecting other places.

My experience is that object and collection initializers are usually used in two places:

  • Static initializers for collections that’ll never be modified after type initialization
  • Test code

The concerns around exposure and correctness still apply for static initializers but much less so for test code. If you decide that in your test assemblies it’s handy to have Add extension methods to make collection initializers simpler, that’s fine. It won’t impact your production code at all. Likewise, if you use indexers in your collection initializers for tests and accidentally set the same key twice, chances are high that your tests will fail. Again, the downside is minimized.

This isn’t a distinction that affects only this pair of features. Test code should still be of high quality, but how you measure that quality and the impact of making any particular trade-off is different for test code as compared to production code, particularly for public APIs.

The addition of extension methods as part of LINQ encouraged a more fluent approach to composing multiple operations. Instead of using multiple statements, in many cases it’s now idiomatic to chain multiple method calls together in a single statement. That’s what LINQ queries end up doing all the time, but it became a more idiomatic pattern with APIs such as LINQ to XML. This can lead to the same problem we’ve had for a long time when chaining property accesses together: everything breaks as soon as you encounter a null value. C# 6 allows you terminate one of these chains safely at that point instead of the code blowing up with an exception.

10.3. The null conditional operator

I’m not going to go into the merits of nullity, but it’s something we often have to live with, along with complex object models with properties several levels deep. The C# language team has been thinking for a long time about making nullity easier to work with. Some of that work is still in progress, but C# 6 has taken one step along the way. Again, it can make your code much shorter and simpler, expressing how you want to handle nullity without having to repeat expressions everywhere.

10.3.1. Simple and safe property dereferencing

As a working example, let’s suppose you have a Customer type with a Profile property that has a DefaultShippingAddress property, which has a Town property. Now let’s suppose you want to find all customers within a collection whose default shipping address has Reading as a town name. Without worrying about nullity, you could use this:

var readingCustomers = allCustomers
    .Where(c => c.Profile.DefaultShippingAddress.Town == "Reading");

That works fine if you know that every customer has a profile, every profile has a default shipping address, and every address has a town. But what if any of those is null? You’ll end up with a NullReferenceException when you probably just want to exclude that customer from the results. Previously, you’d have to rewrite this as something horrible, checking each property for nullity one at a time by using the short-circuiting && operator:

var readingCustomers = allCustomers
    .Where(c => c.Profile != null &&
                c.Profile.DefaultShippingAddress != null &&
                c.Profile.DefaultShippingAddress.Town == "Reading");

Yeesh. So much repetition. It gets even worse if you need to make a method call at the end rather than using == (which already handles null correctly, at least for references; see section 10.3.3 for possible surprises). So how does C# 6 improve this? It introduces the null conditional ?. operator, which is a short-circuiting operator that stops if the expression evaluates to null. A null-safe version of the query is as follows:

var readingCustomers = allCustomers
    .Where(c => c.Profile?.DefaultShippingAddress?.Town == "Reading");

This is exactly the same as our first version but with two uses of the null-conditional operator. If either c.Profile or c.Profile.DefaultShippingAddress is null, the whole expression on the left side of == evaluates to null. You may be asking yourself why you have only two uses, when four things are potentially null:

  • c
  • c.Profile
  • c.Profile.DefaultShippingAddress
  • c.Profile.DefaultShippingAddress.Town

I’ve assumed that all the elements of allCustomers are non-null references. If you needed to handle the possibility of null elements there, you could use c?.Profile at the start instead. That covers the first bullet; the == operator already handles null operands, so you don’t need to worry about the last bullet.

10.3.2. The null conditional operator in more detail

This brief example shows only properties, but the null conditional operator can also be used to access methods, fields, and indexers. The basic rule is that when a null conditional operator is encountered, the compiler injects a nullity check on the value to the left of the ?. If the value is null, evaluation stops and the result of the overall expression is null. Otherwise, evaluation continues with the property, method, field, or index access to the right of the ? without reevaluating the first part of the expression. If the type of the overall expression would be a non-nullable value type without the null conditional operator, it becomes the nullable equivalent if a null conditional operator is involved anywhere in the sequence.

The overall expression here—the part where evaluation stops abruptly if a null value is encountered—is basically the sequence of property, field, indexer, and method access involved. Other operators, such as comparisons, break the sequence because of precedence rules. To demonstrate this, let’s have a closer look at the condition for the Where method in section 10.3.1. Our lambda expression was as follows:

c => c.Profile?.DefaultShippingAddress?.Town == "Reading"

The compiler treats this roughly as if you’d written this:

string result;
var tmp1 = c.Profile;
if (tmp1 == null) 
      {
    result = null;
      }
else
{
    var tmp2 = tmp1.DefaultShippingAddress;
    if (tmp2 == null)
    {
        result = null;
    }
    else
    {
        result = tmp2.Town;
    }
}
return result == "Reading";

Notice how each property access (which I’ve highlighted in bold) occurs only once. In our pre-C# 6 version checking for null, you’d potentially evaluate c.Profile three times and c.Profile.DefaultShippingAddress twice. If those evaluations depended on data being mutated by other threads, you could be in trouble: you could pass the first two nullity tests and still fail with a NullReferenceException. The C# code is safer and more efficient because you’re evaluating everything only once.

10.3.3. Handling Boolean comparisons

Currently, you’re still performing the comparison at the end with the == operator; that isn’t short-circuited away if anything is null. Suppose you want to use the Equals method instead and write this:

c => c.Profile?.DefaultShippingAddress?.Town?.Equals("Reading")

Unfortunately, this doesn’t compile. You’ve added a third null conditional operator, so you don’t call Equals if you have a shipping address with a Town property of null. But now the overall result is Nullable<bool> instead of bool, which means our lambda expression isn’t suitable for the Where method yet.

This is a pretty common occurrence with the null conditional operator. Anytime you use the null conditional operator in any kind of condition, you need to consider three possibilities:

  • Every part of the expression is evaluated, and the result is true.
  • Every part of the expression is evaluated, and the result is false.
  • The expression short-circuited because of a null value, and the result is null.

Usually, you want to collapse those three possibilities down to two by making the third option map to a true or false result. There are two common ways of doing this: comparing against a bool constant or using the null coalescing ?? operator.

Language design choices for nullable Boolean comparisons

The behavior of bool? in comparisons with non-nullable values caused concern for the language designers in the C# 2 time frame. The fact that x == true and x != false are both valid but with different meanings if x is a bool? variable can be pretty surprising. (If x is null, x == true evaluates to false, and x != false evaluates to true.)

Was it the right design choice? Maybe. Often all the choices available are unpleasant in one respect or other. It won’t change now, though, so it’s best to be aware of it and write code as clearly as possible for readers who may be less aware.

To simplify our example, let’s suppose you already have a variable called name containing the relevant string value, but it can be null. You want to write an if statement and execute the body of the statement if the town is X based on the Equals method. This is the simplest way of demonstrating a condition: in real life, you could be conditionally accessing a Boolean property, for example. Table 10.1 shows the options you can use depending on whether you also want to enter the body of the statement if name is null.

Table 10.1. Options for performing Boolean comparisons using the null conditional operator

You don’t want to enter the body if name is null

You do want to enter the body if name is null

if (name?.Equals("X") ?? false) if (name?.Equals("X") == true)
if (name?.Equals("X") ?? true) if (name?.Equals("X") != false)

I prefer the null coalescing operator approach; I read it as “try to perform the comparison, but default to the value after the ?? if you have to stop early.” After you understand that the type of the expression (name?.Equals("X") in this case) is Nullable<bool>, nothing else is new here. It just so happens that you’re much more likely to come up against this case than you were before the null conditional operator became available.

10.3.4. Indexers and the null conditional operator

As I mentioned earlier, the null conditional operator works for indexers as well as for fields, properties, and methods. The syntax is again just adding a question mark, but this time before the opening square bracket. This works for array access as well as user-defined indexers, and again the result type becomes nullable if it would otherwise be a non-nullable value type. Here’s a simple example:

int[] array = null;
int? firstElement = array?[0];

There’s not a lot more to say about how the null-conditional operator works with indexers; it’s as simple as that. I haven’t found this to be nearly as useful as working with properties and methods, but it’s still good to know that it’s there, as much for consistency as anything else.

10.3.5. Working effectively with the null conditional operator

You’ve already seen that the null conditional operator is useful when working with object models with properties that may or may not be null, but other compelling use cases exist. We’ll look at two of them here, but this isn’t an exhaustive list, and you may come up with additional novel uses yourself.

Safe and convenient event raising

The pattern for raising an event safely even in the face of multiple threads has been well known for many years. For example, to raise a field-like Click event of type EventHandler, you’d write code like this:

EventHandler handler = Click;
if (handler != null)
{
    handler(this, EventArgs.Empty);
}

Two aspects are important here:

  • You’re not just calling Click(this, EventArgs.Empty), because Click might be null. (That would be the case if no handler was subscribed to the event.)
  • You’re copying the value of the Click field to a local variable first so that even if it changes in another thread after you’ve checked for nullity, you still have a non-null reference. You may invoke a “slightly old” (just unsubscribed) event handler, but that’s a reasonable race condition.

So far, so good—but so long-winded. The null conditional operator comes to the rescue, however. It can’t be used for the shorthand style of delegate invocation of handler(...), but you can use it to conditionally call the Invoke method and all in a single line:

Click?.Invoke(this, EventArgs.Empty);

If this is the only line in your method (OnClick or similar), this has the compound benefit that now it’s a single-expression body, so it can be written as an expression-bodied method. It’s just as safe as the earlier pattern but a good deal more concise.

Making the most of null-returning APIs

In chapter 9, I talked about logging and how interpolated string literals don’t help in terms of performance. But they can be cleanly combined with the null conditional operator if you have a logging API designed with that pattern in mind. For example, suppose you have a logger API along the lines of that shown in the next listing.

Listing 10.12. Sketch of a null-conditional-friendly logging API
public interface ILogger                   1
{
    IActiveLogger Debug { get; }           2
    IActiveLogger Info { get; }            2
    IActiveLogger Warning { get; }         2
    IActiveLogger Error { get; }           2
}

public interface IActiveLogger             3
{
    void Log(string message);
}

  • 1 Interface returned by GetLog methods and so on
  • 2 Properties returning null when the log is disabled
  • 3 Interface representing an enabled log sink

This is only a sketch; a full logging API would have much more to it. But by separating the step of getting an active logger at a particular log level from the step of performing the logging, you can write efficient and informative logging:

logger.Debug?.Log($"Received request for URL {request.Url}");

If debug logging is disabled, you never get as far as formatting the interpolated string literal, and you can determine that without creating a single object. If debug logging is enabled, the interpolated string literal will be evaluated and passed on to the Log method as usual. Without getting too misty eyed, this is the sort of thing that makes me love the way C# has evolved.

Of course, you need the logging API to handle this in an appropriate way first. If whichever logging API you’re using doesn’t have anything like this, extension methods might help you out.

A lot of the reflection APIs return null at appropriate times, and LINQ’s FirstOrDefault (and similar) methods can work well with the null-conditional operator. Likewise, LINQ to XML has many methods that return null if they can’t find what you’re asking for. For example, suppose you have an XML element with an optional <author> element that may or may not have a name attribute. You can easily retrieve the author name with either of these two statements:

string authorName = book.Element("author")?.Attribute("name")?.Value;
string authorName = (string) book.Element("author")?.Attribute("name");

The first of these uses the null conditional operator twice: once to access the attribute of the element and once to access the value of the attribute. The second approach uses the way that LINQ to XML already embraces nullity in its explicit conversion operators.

10.3.6. Limitations of the null conditional operator

Beyond occasionally having to deal with nullable value types when previously you were using only non-nullable values, there are few unpleasant surprises with the null conditional operator. The only thing that might surprise you is that the result of the expression is always classified as a value rather than a variable. The upshot of this is that you can’t use the null-conditional operator as the left side of an assignment. For example, the following are all invalid:

person?.Name = "";
stats?.RequestCount++;
array?[index] = 10;

In those cases, you need to use the old-fashioned if statement. My experience is that this limitation is rarely an issue.

The null conditional operator is great for avoiding NullReferenceException, but sometimes exceptions happen for more reasonable causes, and you need to be able to handle them. Exception filters represent the first change to the structure of a catch block since C# was first introduced.

10.4. Exception filters

Our final feature in this chapter is a little embarrassing: it’s C# playing catch-up with VB. Yes, VB has had exception filters forever, but they were introduced only in C# 6. This is another feature you may rarely use, but it’s an interesting peek into the guts of the CLR. The basic premise is that you can now write catch blocks that only sometimes catch an exception based whether a filter expression returns true or false. If it returns true, the exception is caught. If it returns false, the catch block is ignored.

As an example, imagine you’re performing a web operation and know that the server you’re connecting to is sometimes offline. If you fail to connect to it, you have another option, but any other kind of failure should result in an exception bubbling up in the normal way. Prior to C# 6, you’d have to catch the exception and rethrow it if didn’t have the right status:

try
{
    ...                                                 1
}
catch (WebException e)
{
    if (e.Status != WebExceptionStatus.ConnectFailure)  2
    {                                                   2
        throw;                                          2
    }                                                   2
    ...                                                 3
}

  • 1 Attempts the web operation
  • 2 Rethrows if it’s not a connection failure
  • 3 Handles the connection failure

With an exception filter, if you don’t want to handle an exception, you don’t catch it; you filter it away from your catch block to start with:

try
{
    ...                                                    1
}
catch (WebException e)
    when (e.Status == WebExceptionStatus.ConnectFailure)   2
{    
    ...                                                    3
}

  • 1 Attempts the web operation
  • 2 Catches only connection failures
  • 3 Handles the connection failure

Beyond specific cases like this, I can see exception filters being useful in two generic use cases: retry and logging. In a retry loop, you typically want to catch the exception only if you’re going to retry the operation (if it meets certain criteria and you haven’t run out of attempts); in a logging scenario, you may never want to catch the exception but log it while it’s in-flight, so to speak. Before going into more details of the concrete use cases, let’s see what the feature looks like in code and how it behaves.

10.4.1. Syntax and semantics of exception filters

Our first full example, shown in the following listing, is simple: it loops over a set of messages and throws an exception for each of them. You have an exception filter that will catch exceptions only when the message contains the word catch. The exception filter is highlighted in bold.

Listing 10.13. Throwing three exceptions and catching two of them
string[] messages =
{
    "You can catch this",
    "You can catch this too",
    "This won't be caught"
};
foreach (string message in messages)                  1
{
    try
    {
        throw new Exception(message);                 2
    }
    catch (Exception e)
        when (e.Message.Contains("catch"))            3
    {
        Console.WriteLine($"Caught '{e.Message}'");   4
    }
}

  • 1 Loops outside the try/catch statement once per message
  • 2 Throws an exception with a different message each time
  • 3 Catches the exception only if it contains "catch"
  • 4 Writes out the message of the caught exception

The output is two lines for the caught exceptions:

Caught 'You can catch this'
Caught 'You can catch this too'

Output for the uncaught exception is a message of This won’t be caught. (Exactly what that looks like depends on how you run the code, but it’s a normal unhandled exception.)

Syntactically, that’s all there is to exception filters: the contextual keyword when followed by an expression in parentheses that can use the exception variable declared in the catch clause and must evaluate to a Boolean value. The semantics may not be quite what you expect, though.

The two-pass exception model

You’re probably used to the idea of the CLR unwinding the stack as an exception “bubbles up” until it’s caught. What’s more surprising is exactly how this happens. The process is more complicated than you may expect using a two-pass model.[1] This model uses the following steps:

1

I don’t know the origins of this model for exception processing. I suspect it maps onto the Windows Structured Exception Handling (often abbreviated to SEH) mechanism in a straightforward way, but this is deeper into the CLR than I like to venture.

  • The exception is thrown, and the first pass starts.
  • The CLR walks down the stack, trying to find which catch block will handle the exception. (We’ll call this the handling catch block as shorthand, but that’s not official terminology.)
  • Only catch blocks with compatible exception types are considered.
  • If a catch block has an exception filter, the filter is executed; if the filter returns false, this catch block won’t handle the exception.
  • A catch block without an exception filter is equivalent to one with an exception filter that returns true.
  • Now that the handling catch block has been determined, the second pass starts:
  • The CLR unwinds the stack from the point at which the exception was thrown as far as the catch block that has been determined.
  • Any finally blocks encountered while unwinding the stack are executed. (This doesn’t include any finally block associated with the handling catch block.)
  • The handling catch block is executed.
  • The finally statement associated with the handling catch block is executed, if there is one.

Listing 10.14 shows a concrete example of all of this with three important methods: Bottom, Middle, and Top. Bottom calls Middle and Middle calls Top, so the stack ends up being self-describing. The Main method calls Bottom to start the ball rolling. Please don’t be daunted by the length of this code; it’s not doing anything massively complicated. Again, the exception filters are highlighted in bold. The LogAndReturn method is just a convenient way to trace the execution. It’s used by exception filters to log a particular method and then return the specified value to say whether the exception should be caught.

Listing 10.14. A three-level demonstration of exception filtering
static bool LogAndReturn(string message, bool result)      1
{                                                          1
    Console.WriteLine(message);                            1
    return result;                                         1
}                                                          1

static void Top()
{
    try
    {
        throw new Exception();
    }
    finally                                                2
    {                                                      2
        Console.WriteLine("Top finally");                  2
    }                                                      2
}

static void Middle()
{
    try
    {
        Top();
    }
    catch (Exception e)
        when (LogAndReturn("Middle filter", false))        3
    {
        Console.WriteLine("Caught in middle");             4
    }
    finally                                                5
    {                                                      5
        Console.WriteLine("Middle finally");               5
    }                                                      5
}

static void Bottom()
{
    try
    {
        Middle();
    }
    catch (IOException e)
        when (LogAndReturn("Never called", true))          6
    {
    }
    catch (Exception e)
        when (LogAndReturn("Bottom filter", true))         7
    {
        Console.WriteLine("Caught in Bottom");             8
    }
}

static void Main()
{
    Bottom();
}

  • 1 Convenience method called by exception filters
  • 2 Finally block (no catch) executed on the second pass
  • 3 Exception filter that never catches
  • 4 This never prints, because the filter returns false.
  • 5 Finally block executed on the second pass
  • 6 Exception filter that’s never called—wrong exception type
  • 7 Exception filter that always catches
  • 8 This is printed, because you catch the exception here.

Phew! With the description earlier and the annotations in the listing, you have enough information to work out what the output will be. We’ll walk through it to make sure it’s really clear. First, let’s look at what’s printed:

Middle filter
Bottom filter
Top finally
Middle finally
Caught in Bottom

Figure 10.2 shows this process. In each step, the left side shows the stack (ignoring Main), the middle part describes what’s happening, and the right side shows any output from that step.

Figure 10.2. Execution flow of listing 10.14

Security impact of the two-pass model

The execution timing of finally blocks affects using and lock statements, too. This has an important implication of what you can use try/finally or using for if you’re writing code that may be executed in an environment that may contain hostile code. If your method may be called by code you don’t trust, and you allow exceptions to escape from that method, then the caller can use an exception filter to execute code before your finally block executes.

All of this means you shouldn’t use finally for anything security sensitive. For example, if your try block enters a more privileged state and you’re relying on a finally block to return to a less privileged state, other code could execute while you’re still in that privileged state. A lot of code doesn’t need to worry about this sort of thing—it’s always running under friendly conditions—but you should definitely be aware of it. If you’re concerned, you could use an empty catch block with a filter that removes the privilege and returns false (so the exception isn’t caught), but that’s not something I’d want to do regularly.

Catching the same exception type multiple times

In the past, it was always an error to specify the same exception type in multiple catch blocks for the same try block. It didn’t make any sense, because the second block would never be reached. With exception filters, it makes a lot more sense.

To demonstrate this, let’s expand our initial WebException example. Suppose you’re fetching web content based on a URL provided by a user. You might want to handle a connection failure in one way, a name resolution failure in a different way, and let any other kind of exception bubble up to a higher-level catch block. With exception filters, you can do that simply:

try
{
    ...                              1
}
catch (WebException e)
    when (e.Status == WebExceptionStatus.ConnectFailure)
{
    ...                              2
}
catch (WebException e)
    when (e.Status == WebExceptionStatus.NameResolutionFailure)
{
    ...                              3
}

  • 1 Attempts the web operation
  • 2 Handles a connection failure
  • 3 Handles a name-resolution failure

If you wanted to handle all other WebExceptions at the same level, it’d be valid to have a general catch (WebException e) { ... } block with no exception filter after the two status-specific ones.

Now that you know how exception filters work, let’s return to the two generic examples I gave earlier. These aren’t the only uses, but they should help you recognize other, similar situations. Let’s start with retrying.

10.4.2. Retrying operations

As cloud computing becomes more prevalent, we’re generally becoming more aware of operations that can fail and the need to think about what effect we want that failure to have on our code. For remote operations—web service calls and database operations, for example—there are sometimes transient failures that are perfectly safe to retry.

Keep track of your retry policies

Although being able to retry like this is useful, it’s worth being aware of every layer of your code that might be attempting to retry a failed operation. If you have multiple layers of abstraction each trying to be nice and transparently retrying a failure that might be transient, you can end up delaying logging a real failure for a long time. In short, it’s a pattern that doesn’t compose well with itself.

If you control the whole stack for an application, you should think about where you want the retry to occur. If you’re responsible for only one aspect of it, you should consider making the retry configurable so that a developer who does control the whole stack can determine whether your layer is where they want retries to occur.

Production retry handling is somewhat complicated. You may need complicated heuristics to determine when and how long to retry for and an element of randomness on the delays between attempts to avoid retrying clients getting in sync with each other. Listing 10.15 provides a hugely simplified version[2] to avoid distracting you from the exception filter aspects.

2

At a bare minimum, I’d expect any real-world retry mechanism to accept a filter to check which failures are retriable and a delay between calls.

All your code needs to know is the following:

  • What operation you’re trying to execute
  • How many times you’re willing to try it

At that point, using an exception filter to catch exceptions only when you’re going to retry the operation, the code is straightforward.

Listing 10.15. A simple retry loop
static T Retry<T>(Func<T> operation, int attempts)
{   
    while (true)
    {
        try
        {
            attempts--;
            return operation();
        }
        catch (Exception e) when (attempts > 0)
        {
            Console.WriteLine($"Failed: {e}");
            Console.WriteLine($"Attempts left: {attempts}");
            Thread.Sleep(5000);
        }
    }
}

Although while(true) loops are rarely a good idea, this one makes sense. You could write a loop with a condition based on retryCount, but the exception filter effectively already provides that, so it’d be misleading. Also, the end of the loop would then be reachable from the compiler’s standpoint, so it wouldn’t compile without a return or throw statement at the end of the method.

When this is in place, calling it to achieve a retry is simple:

Func<DateTime> temporamentalCall = () =>
{
    DateTime utcNow = DateTime.UtcNow;
    if (utcNow.Second < 20)
    {
        throw new Exception("I don't like the start of a minute");
    }
    return utcNow;
};

var result = Retry(temporamentalCall, 3);
Console.WriteLine(result);

Usually, this will return a result immediately. Sometimes, if you execute it at about 10 seconds into a minute, it’ll fail a couple of times and then succeed. Sometimes, if you execute it right at the start of a minute, it’ll fail a couple of times, catching the exception and logging it, and then fail a third time, at which point the exception won’t be caught.

10.4.3. Logging as a side effect

Our second example is a way of logging exceptions in-flight. I realize I’ve used logging to demonstrate many of the C# 6 features, but this is a coincidence. I don’t believe the C# team decided that they’d target logging specifically for this release; it just works well as a familiar scenario.

The subject of exactly how and where it makes sense to log exceptions is a matter of much debate, and I don’t intend to enter that debate here. Instead, I’ll assert that at least sometimes, it’s useful to log an exception within one method call even if it’s going to be caught (and possibly logged a second time) somewhere further down the stack.

You can use exception filters to log the exception in a way that doesn’t disturb the execution flow in any other way. All you need is an exception filter that calls a method to log the exception and then returns false to indicate that you don’t really want to catch the exception. The following listing demonstrates this in a Main method that will still lead to the process completing with an error code, but only after it has logged the exception with a timestamp.

Listing 10.16. Logging in a filter
static void Main()
{
    try
    {
        UnreliableMethod();
    }
    catch (Exception e) when (Log(e))
    {
    }
}        

static void UnreliableMethod()
{
    throw new Exception("Bang!");
}

static bool Log(Exception e)
{
    Console.WriteLine($"{DateTime.UtcNow}: {e.GetType()} {e.Message}");
    return false;
}

This listing is in many ways just a variation of listing 10.14, in which we used logging to investigate the semantics of the two-pass exception system. In this case, you’re never catching the exception in the filter; the whole try/catch and filter exist only for the side effect of logging.

10.4.4. Individual, case-specific exception filters

In addition to those generic examples, specific business logic sometimes requires some exceptions to be caught and others to propagate further. If you doubt that this is ever useful, consider whether you always catch Exception or whether you tend to catch specific exception types like IOException or SqlException. Consider the following block:

catch (IOException e)
{
    ...
}

You can think of that block as being broadly equivalent to this:

catch (Exception tmp) when (tmp is IOException)
{
    IOException e = (IOException) tmp;
    ...
}

Exception filters in C# 6 are a generalization of that. Often, the relevant information isn’t in the type but is exposed in some other way. Take SqlException, for example; it has a Number property corresponding to an underlying cause. It’d be far from unreasonable to handle some SQL failures in one way and others in a different way. Getting the underlying HTTP status from a WebException is slightly tricky because of the API, but again, you may well want to handle a 404 (Not Found) response differently than a 500 (Internal Error).

One word of caution: I strongly urge you not to filter based on the exception message (other than for experimental purposes, as I did in listing 10.13). Exception messages aren’t generally seen as having to stay stable between releases, and they may well be localized, depending on the source. Code that behaves differently based on a particular exception message is fragile.

10.4.5. Why not just throw?

You may be wondering what all the fuss is about. We’ve always been able to rethrow exceptions, after all. Code using an exception filter like this

catch (Exception e) when (condition)
{
    ...
}

isn’t very different from this:

catch (Exception e)
{
    if (!condition)
    {
        throw;
    }
    ...
}

Does this really meet the high bar for a new language feature? It’s arguable.

There are differences between the two pieces of code: you’ve already seen that the timing of when condition is evaluated changes relative to any finally blocks higher up the call stack. Additionally, although a simple throw statement does preserve the original stack trace for the most part, subtle differences can exist, particularly in the stack frame where the exception is caught and rethrown. That could certainly make the difference between diagnosing an error being simple and it being painful.

I doubt that exception filters will massively transform many developers’ lives. They’re not something I miss when I have to work on a C# 5 codebase, unlike expression-bodied members and interpolated string literals, for example, but they’re still nice to have.

Of the features described in this chapter, using static and the null conditional operator are certainly the ones I use most. They’re applicable in a broad range of cases and can sometimes make the code radically more readable. (In particular, if you have code that deals with a lot of constants defined elsewhere, using static can make all the difference in terms of readability.)

One aspect that’s common to the null conditional operator and the object/collection initializer improvements is the ability to express a complex operation in a single expression. This reinforces the benefits that object/collection initializers introduced back in C# 3: it allows expressions to be used for field initialization or method arguments that might otherwise have had to be computed separately and less conveniently.

Summary

  • using static directives allow your code to refer to static type members (usually constants or methods) without specifying the type name again.
  • using static also imports all extension methods from the specified type, so you don’t need to import all the extension methods from a namespace.
  • Changes to extension method importing mean that converting a regular static method into an extension method is no longer a backward-compatible change in all cases.
  • Collection initializers can now use Add extension methods as well as those defined on the collection type being initialized.
  • Object initializers can now use indexers, but there are trade-offs between using indexers and collection initializers.
  • The null conditional ?. operator makes it much easier to work with chained operations in which one element of the chain can return null.
  • Exception filters allow more control over exactly which exceptions are caught based on the exception’s data rather than just its type.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset