On 2 April 2019, Googler Jon Skeet, kindly joined us in Milton Keynes to present a talk at our local user group, MK.NET.
Jon is highly respected in the C# developer community and author of one of the definitive language guides C# In Depth. As with Scott Hanselman’s talk at MK.NET earlier in the year, turnout for the meetup was high.
So of course, I’d like to extend a genuine and respectful thank you to Jon for taking the time and trouble to come and do this for us !!
As usual, thanks to Twilio’s Developer Evangelist, Layla Porter for organising the event and for continuing to attract world-class speakers to MK (we also have Jim Bennett and Julie Lerman coming later this year). Thanks to Twilio for fronting the cost of providing food and drink for everyone. Also, I would like to thank VWFS for providing the large venue for our community to use.
With regards to the video of the talk itself, despite not having the same learning curve and other technical challenges that I faced previously [with the video edit of Scott’s talk], editing this video did still take me an entire weekend of work to produce, so I do hope you find the video useful! :-
You can find code samples from the talk on Jon’s GitHub pages:
https://github.com/jskeet/DemoCode/tree/master/CSharp8.
A majority of the following article is based on the topics raised during Jon’s talk.
Expression Bodied Constructors
Jon briefly touched on the subject of C# 7 Expression Bodied Constructors. This is useful because you can potentially minimise boilerplate code.
So, if you have code which looks a bit like this …
class Person
{
public string FirstName { get; }
public string LastName { get; }
public string MiddleName { get; }
public Person(string firstName, string lastName, string middleName)
{
FirstName = firstName;
LastName = lastName;
MiddleName = middleName;
}
}
… it is now possible to omit some of that repetitive variable-assigning code and instead use something that looks like this:-
...
public Person(string firstName, string lastName, string middleName)
=> (FirstName, LastName, MiddleName) = (firstName, lastName, middleName);
...
Notice that this is self-contained - the constructor has no curly braces and is terminated with a semi-colon.
In language terms, the final set of parentheses in the above code (which contains the arguments) is a Tuple Literal Expression.
To clarify, we are talking about the modern version of Tuples, as introduced in C# 7 - not to be confused with the legacy
System.Tuple
, which are inflexible and hard-coded so that we can only refer to items literally as “Item1” and “Item2”.
I did have a reservation with this though; in addition to variable assignment, I generally write defensive code and constructors are a key place to perform tasks such as validation and null-value-testing, etc.
Such a piece of code could look like this:-
this.FirstName = firstName?? throw new ArgumentNullException(nameof(firstName));
The above uses the C# 7 null-coalescing feature to test the supplied argument.
If you use code like this, your program will still fail at runtime, but at least you then have the opportunity to throw a meaningful error at the first opportunity. This may help you to handle, or at least log, the problem better.
Because I suspect that the issue is likely that I just don’t understand how best to use this new feature, I’ve asked this question on StackOverflow, so we’ll have to wait and see what the community has to say.
Nullable Reference Types
You can read about nullable and non-nullable reference types in the Microsoft documentation.
“We’ve had nullable types since C# 2 ! I’ve been using the
?
operator since, like, forever dude!”… Whoa, slow down! C# 8 is introducing something new. Let’s back up:-
So as a brief recap:-
value types, such as
int
andbool
, have always been strict in not allowing nulls. This is because they hold an actual value, directly in memory. When C# 2 came along, we were then given the option to use the?
operator to mark them as nullable (e.g.int? myNumber = null
), thereby giving us the option to explicitly assign anull
value.reference types in the meanwhile, have always been nullable. This is because, in memory, they store a reference to an object (not the actual value) - which may, or may not, exist.
So, as C# developers, we’ve grown up understanding that “reference types are inherently nullable”. It’s been like that since C# 1 back in 2002.
To clarify, C# 2 introduced support for nullable value types, whereas this new C# 8 addition is to make reference types explicitly nullable or non-nullable.
It has been a source of discussion as to why null
even exists in the C# language. However, there are considered reasons as to why we have it - you can read more about that subject in this stackoverflow article, which in particular, makes reference to comments made by Microsoft Lead C# Language Designer, Mads Torgersen and explains why we need it.
Regardless, as a practising C# developer, you will constantly have had to deal with the fact that null needs to be accounted for … specifically, as it manifests as occurences of the dreaded NullReferenceException
.
For example:-
- the need to take care to always instantiate an object (how many times have you cursed under your breath that you forgot to create a new instance of that object that you just declared!?)
- the littering of your code with tests to detect and handle null references
To get to the essence of the C# null
problem:-
- NullReferenceExceptions only rear their heads at runtime. Therefore, we’ve always had to write our code in a way that made best effort to defend-against and manage the outcome of these unexpected runtime problems.
- We’ve never had compile-time tools to help us to detect and deal with null-related problems up-front.
Until now.
… And this is where the C# 8 changes come in.
Compiler messages and squiggly lines in Visual Studio are about to come to the rescue.
Nothing changes unless you enable it
This is an important take-away to note. Unlike other language features that “just become available”, as and when you adopt newer versions of C#, this “Nullability” feature needs to be explicitly enabled. This is because there is so much potential to create compile-time breakages, in your existing code.
There are two ways to enable the feature:
- On a file-by-file basis (or strictly speaking, on a coderegion-by-coderegion basis), by adding the directives
#nullable enable
and#nullable disable
respectively. You add these directives into your code, just the same way as you might withregions
. - On a project basis, by editing the
.csproj
file. Those changes look like this:-
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netCoreApp3.0</TargetFramework>
<OutputType>Exe</OutputType>
<LangVersion>preview</LangVersion>
<NullableContextOptions>enable</NullableContextOptions>
…
With regard to the
LangVersion
attribute, in the video, Jon explained that previously you would have provided a value of8.0
(for C# 8), but at time of writing this article, you would need to enterpreview
. This is because C# 8 hasn’t actually been released yet (despite the fact that VS2019 was released on the day of Jon’s talk). You should check current documentation to learn which setting is applicable.
In terms of syntax, you decorate your reference types:-
- with a
?
(question mark) to explicitly denote that something is intended to be nullable (just like you have always done for value types) - i.e.MyObject?
- with a
!
(exclamation/bang) to explicitly denote that something is not intended to be nullable - i.e.MyObject!
Amusingly, Jon referred to the use of the exclamation/bang as the “dammit operator” (as in, “dammit, I know better than you!”). I think we should all make this a thing, so be sure to spread the word! ;-)
Jon also noted that the actual act of refactoring older code was, in his words “a game of whack-a-mole”. It’s going to be one of those tasks where you could spend several hours working through a huge ripple of errors/warnings.
There is a whole swathe of other issues and subtleties related to this topic, so I would wholly recommend you set aside an hour of your time and watch at least the section of my video between 0:4:42 and 1:00:05.
Switch Expressions
As before, to experiment with this language feature you may need to modify your .csproj to include
<LangVersion>preview</LangVersion>
As a brief intro to Switch Expressions, consider the following example of a classic switch
statement and see how that can be changed into an Expression Bodied Switch in the subsequent code block:-
static int SquareClassicSwitch(int n)
{
switch (n)
{
case 0: return 0;
case 1: return 1;
default: return n >= 2 ? n * n : throw new ArgumentOutOfRangeException();
}
}
static int SquareSwitchExpression(int n) => n switch
{
0 => 0,
1 => 1,
_ => n >= 2 ? n * n : throw new ArgumentOutOfRangeException()
};
There are a couple of things worth noting in the second code block:-
- The use of
=>
(fat arrows) aren’t actually lambda’s - the syntax means “goes to” (its a shortcut for curly braces and a return, whereas lambdas are a shortcut for delegates and expression trees). - The use of
_
(underscore) is the pattern-matching symbol for “match all”, which is the equivalent of thedefault
in a classic switch. - If you are returning a result out of a Switch Expression, then it must account for all possibilities, otherwise you will get warnings related to “does not handle all possible inputs”. Unlike the classic switch that didn’t care about matching all options (it would quite happily skip over and exit), you now need to ensure that your Switch Expression “is exhaustive”.
Here is a shortcut to the Switch Expression section of my video for more insight.
Recursive Pattern Matching
C# 7 Introduced a feature called Pattern Matching, that was essentially a basic way to match types, using keywords such as is
. For example:-
object x = ""
if (x is 0)
{
//do something
}
C# 8 introduces more complex features where, not only can you match basic types, but we can now go further and say “can we match the type - AND - can we match some properties of that type?”.
Consider this complete program listing (I have adapted elements from Jon’s talk - notably the “DateTimeExtension Deconstruct” - but have otherwise simplified, slightly, away from his example in the video)
using System;
using System.Collections.Generic;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var listPeople = new List<Person>
{
new Actor{FavouriteFilm="Scream", DateOfBirth=new DateTime(1960, 6, 30) },
new Programmer{FavouriteProgrammingLanguage="C#", DateOfBirth=new DateTime(1970, 1, 1) },
new Programmer{FavouriteProgrammingLanguage="Modular2", DateOfBirth=new DateTime(2000, 1, 1) }
};
foreach (var person in listPeople)
{
Console.WriteLine(GetYearOfBirth(person));
}
Console.ReadKey();
}
static string GetYearOfBirth(Person person) => person switch
{
Actor { } => "Actors never reveal their age",
Programmer { FavouriteProgrammingLanguage: var fpl, DateOfBirth: var (year, _, _) } when ((fpl == "Modular2") && (year > 1990)) => $"Programmer declared they were born in {year}, but they like Modular2 ... sounds suspicious",
Programmer { DateOfBirth: var (year, _, _) } => $"Programmer declared they were born in {year}",
_ => "Unknown profession"
};
}
static class DateTimeExtensions
{
public static void Deconstruct(this DateTime date, out int year, out int month, out int day) =>
(year, month, day) = (date.Year, date.Month, date.Day);
}
class Person
{
public DateTime DateOfBirth { get; set; }
}
class Actor : Person
{
public string FavouriteFilm { get; set; }
}
class Programmer : Person
{
public string FavouriteProgrammingLanguage { get; set; }
}
}
Running the program should return the following response:-
Actors never reveal their age
Programmer declared they were born in 1970
Programmer declared they were born in 2000, but they like Modular2 ... sounds suspicious
In the above listing, the pieces of code that are most relevant to the topic of switch expressions can be seen inside the method GetYearOfBirth
.
Within that switch expression, there are the following notable activities:-
- we use regular C# 7 Pattern Matching, to detect the type
Actor
and produce a basic textual response. - we use the newer C# 8 Recursive Pattern Matching to both identify the type
Programmer
and to then also examine the base propertyDateOfBirth
- within the Date pattern matching, we use the
_
(underscore) operator as a Discard, to say that we are only interested in theyear
field and that we can “Discard” the month and day. - we end with an
_
as a “catch all” used to display a default message.
Here is a shortcut to the Recursive Pattern Matching section of my video.
Wrapping up
There’s some interesting stuff here.
I didn’t know anything about the new language features, prior to listening to Jon. There seems a fair bit of new syntax to become accustomed to.
I personally found some of the new syntax a bit confusing to read - for example, the mixed use of the _
(underscore) operator (which is used to mean, separately, both “discard” and “catch all”) and [as already mentioned earlier] the =>
(fat arrow) having different uses.
My initial impression of Recursive Pattern Matching in particular, was that it is evidently quite powerful stuff, but again, fiddly to read and write. I think I would like to see some examples of real-world use and then I’m sure I’ll better appreciate it with experience.