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.
The first feature we’ll look at provides a simpler way of referring to static members of a type, including extension methods.
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.
Assume that you already have a Point type representing Cartesian coordinates in a simple way. The conversion itself is fairly simple trigonometry:
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.
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 }
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.
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 }
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:
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 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
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.
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:
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.
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
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.
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
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.
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.
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.
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
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.
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
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.
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 };
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.
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:
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.
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.
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 };
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.
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:
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:
Now that you know what you can do, let’s take a closer look at where it makes sense to use this feature.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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.
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:
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.
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.
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:
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.
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.
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.
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.
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.
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:
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.
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.
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); }
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.
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.
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 }
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 }
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.
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.
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 } }
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.
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:
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.
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.
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(); }
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.
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.
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 }
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.
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.
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.
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:
At that point, using an exception filter to catch exceptions only when you’re going to retry the operation, the code is straightforward.
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.
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.
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.
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.
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.