.NET Core 3 is a major milestone in .NET and with it, brings some exciting new functionality - most notably with the arrival of C# 8.
I want to offer my own perspective on this release and specifically how C# 8 helps with software quality.
Note that by software quality, I mean the overall quality of delivered software, not code quality which relates to the maintainability and readability of our source code.
C# 8.0
C# 8.0 is now available with Visual Studio 2019 and available in .NET Core 3 and .NET Standard 2.1 projects.
The new set of features are dependent on changes inside of the .NET runtime, which means that the new language features we'll be discussing here are not available for .NET Framework or Visual Studio 2017 or earlier at this time.
So, that's the bad news.
The good news is that C# 8 is a very feature-rich update that has some very exciting features for all aspects of software development.
I won't be talking about everything new in C# 8, but the most significant language features that can improve software quality. Specifically, I'm going to be focusing on:
- Nullable Reference Types
- Null Coalescing Assignments
- Readonly Properties
- Switch Expressions & Pattern Matching
As we go, we'll look at what each of these are and what they can do to improve software quality.
Nullable Reference Types
I personally disagree with the name of this language feature as what we've already had up to this point has been nullable reference types. What we're now getting is reference types that can default to non-nullable.
Let me explain.
In C# a reference type either holds a reference to an object or it is null
. That means that for things that can be null, you need to check if the object is present at all before you try to interact with it. If you don't, you receive the infamous NullReferenceException
.
C# 8.0 offers you a new option on this. You can enable non-nullable reference types in your code either via a project setting or pre-processor statements and Visual Studio 2019 will automatically assume that reference types declared can never be null unless you explicitly tell it via adding ?
to the type declaration.
Let's look at a simple class:
#nullable enable
public class KeywordData
{
public int Id { get; set; }
public string SomeNonNullValue { get; set; }
public string? SomeNullableValue { get; set; }
}
#nullable restore
In the above example, SomeNonNullValue
is assumed to never contain a null value because it lacks the ?
syntax and Visual Studio will warn you if it notices you checking it for null or setting something into it that it thinks could be null.
Conversely, SomeNullableValue
is interpreted as potentially having a null value since it is indicated with the ?
syntax.
The reason that the nullable
preprocessor statements are present is because most people working with legacy code can't activate this feature for their entire codebase at once, so these regions let you enable or disable the functionality as needed.
Why This Matters for Quality
The impacts of this feature are huge as far as removing unnecessary null checks from code and, more importantly, ensuring that things are properly checked for null at time of development.
This new language feature offers the possibility of removing or vastly reducing entire classes of errors from code, which is always a good thing.
I personally love this new syntax and find the parallels it offers to TypeScript type definitions very appealing.
If you'd like more information on getting started with these, take a look at my in-depth article on nullable reference types in C# 8.0.
Null Coalescing Assignments
This one is another mouthful.
Coalescing refers to bringing things together to form one whole. C# already supports a number of null operators from the simple ?.
to safely access properties on objects which may or may not be null to the ??
coalescing operator to use one value if the first is null.
The null coalescing assignment operator, ??=
builds slightly on these by addressing a specific scenario.
Say you have code where you want to check if something is null and if it is, you want to assign it a value.
In normal C# syntax you might do the following:
public void MyMethod(string value) {
if (value == null) {
value = "Batman";
}
// Do something with value
}
We can simplify this code using the null coalescing assignment operator to the following:
public void MyMethod(string value) {
value ??= "Batman";
// Do something with value
}
Why This Matters for Quality
This hurts readability slightly to those unfamiliar with ??=
(which right now is most everyone), but this also makes parameter default value assignment logic a lot more concise allowing more vertical space for the actual meat of the method.
Additionally, this prevents copy and paste mistakes that you might see where you check one variable for null but assign a value to another in case of null.
This sounds far-fetched, but this sort of "duplicate a block of code then fail to change all necessary areas" type of behavior is a frequent cause of bugs in many systems.
Readonly Members
This readonly isn't what you're thinking. When you think of readonly in C#, people typically think of fields that are defined readonly in order to gain small CLR performance boosts as well as code readability and safety benefits.
Readonly members, on the other hand, are properties or method defined as not being allowed to change the state of the instance they're associated with. This sounds a lot like Pure logic, and most implementations will be, but there's not promise that a readonly member won't modify the state of another object besides its own instance.
Let's take a look at this in action:
public int Foo {get; set;}
public readonly int CalculateFooPlusFortyTwo() => Foo + 42;
Here we define the CalculateFooPlusFortyTwo
method as readonly
which prevents it from modifying any instance state at the compiler level. Let's say some future programmer tried to change the code to the following:
public int Foo {get; set;}
public readonly int CalculateFooPlusFortyTwo() {
Foo += 42;
return Foo;
}
In this example, the code wouldn't compile because the logic was trying to modify the Foo
property when the method is defined as readonly
.
Why This Matters for Quality
Readonly is important for two major reasons:
- It prevents you from accidentally changing internal state in places where that was not expected (most property getters for example).
- It more clearly communicates the intent of your code to other code.
By that second point, let's take a look at the DateTime.AddDays()
method. To those not familiar, this method will take the DateTime instance's date, add X days to it, and then return the new date. Many beginners assume that calling AddDays
on an object will increment that object's date reference to the new value, but this is a common .NET gotcha.
Let's say the AddDays
method was instead defined as readonly
. In that case, the caller would know more clearly that the method doesn't modify the internal state of the date instance and it'd be another clue to the uninitiated as to the behavior of the class.
Switch Expressions & Pattern Matching
Finally, let's talk about C# 8's new improvements to the switch
statement.
A switch statement is pretty standard knowledge for most developers.
switch (entry.Name) {
case "Bruce Wayne":
case "Matt Eland":
return Heroes.Batman;
case "The Thing":
if (entry.Source == "John Carpenter") {
return Heroes.AntiHero;
}
return Heroes.ComicThing;
case "Bruce Banner":
return Heroes.TheHulk;
// Many heroes omitted...
default:
return Heroes.NotAHero;
}
The problem is that there's a lot of repetitive syntax in a switch statement that gets a little boring. Additionally, switches can make you have to do some interesting logic at times inside of case statements to disambiguate, as we saw with the Thing entry in the example above.
C# 8 offers a lot more flexibility and conciseness in switch statements.
For example, I could rewrite the above example as:
return entry.Name switch {
"Bruce Wayne" => Heroes.Batman,
"Matt Eland" => Heroes.Batman,
"The Thing" when entry.Source == "John Carpenter" => Heroes.AntiHero,
"The Thing" => Heroes.ComicThing,
"Bruce Banner" => Heroes.TheHulk,
_ => Heroes.NotAHero
};
First of all, this is significantly shorter. Second of all, the conditional logic possible via when
helps us separate two ambiguous cases more cleanly.
Additionally, the logic is even more powerful than displayed above. Let's say instead of knowing the class, you had to switch on something that was an Object
or some abstract type.
Take a look at the Vehicle pricing example below:
VehicleBase myVehicle = GetVehicle();
return myVehicle switch {
Tank { Movement = TankType.Treads } => 100000M,
Tank => 75000M,
RocketShip => 99999999M,
Car { Color = Colors.Red } => 21999M,
Car => 20000M,
_ => 1000M
};
Here we can actually switch off of the concrete object and its properties. That's fairly powerful, yet concise logic.
Why This Matters for Quality
At first glance you wouldn't think that these "syntax sugar" improvements would do too much for actual software quality, but I think we as developers often underestimate the impact that boiler-plate logic and syntax have on software quality and code maintainability.
Let me put it this way: If I don't have to write a line of code, I won't make a mistake on that line that I have a chance of never catching until it gets to production.
By using switch language syntax and conciseness, I'm relying on .NET to do the casting and matching for me without having to write custom logic for that (and potentially get it wrong).
Additionally, by compressing the boiler-plate logic into a smaller region of code, it puts more emphasis on the program logic that actually matters, which means that more attention gets put on our application logic during code review and during debugging sessions which increases the odds that defects in those blocks will be found.
Closing
These are four interesting new features in C# 8. There are quite a few others, but these 4 collectively help software quality by identifying potential issues in code and minimizing the trivial to the point where we can focus on the meaningful.
There are many more new features in C# 8. If you'd like to learn more, I recommend you read Microsoft's What's new in C# 8.0 article.
What features are you most excited to use? What features have you found helpful in increasing your code quality?
Top comments (15)
Love this writeup, and I agree 100%. "Syntax sugar" on the surface appears to only benefit the creator of the software, but over time once it becomes status quo it makes for less code and greater readability for the next person working on it.
Remember we don't write code for compilers/runtimes we write code for humans. The more efficient, clean, and readable the code is the less chance for mistakes to creep in.
Nice article Matt.
That's a huge part of why I'm learning F#. I believe the language's conciseness improves software quality significantly by reducing potential points of failure and increasing the amount of meaningful code that can be focused on (in addition to the other quality benefits it offers via null handling, preferring immutable objects, etc).
Great article as usual! So have you tried using Entity Framework Core (EFCore) with the non-nullable references?
I’m wondering if that improves things.
For instance, in some ORMs a
findById
operation returnsT
but in reality, afindById
operation always has the potential to not find the item. So the correct return type isT | null
.It’s just another good example of communicating intent.
“Communicating your needs” / TypeScript’s value from a Buddhist perspective (part 1)
Cubicle Buddha ・ May 29 ・ 4 min read
generics and nullable won't play nicely
I yet have to experiment, but bear in mind that this nullable feature when used on generics requires you to express whether the generic type is either a reference type or a value type (aka where T: class or where T: struct). And since you can't combine both constraints - well... :) It leaves me wondering ;)
Interesting. I’d love to hear your findings after you experiment. If you write an article, please link it here. :)
Damn this one hit me right when I needed it. Today at work I saw some vs suggestion to change how my switch worked and replaced it for the new sintax, I wrote a note to look it up later and found this post that gave much more than I was looking for. Great post
Be sure to check out the link to the Microsoft What's New in C# 8 article, because it also includes tupled switch expressions, which are pretty cool too, but I didn't cover them in this article.
I love all the things introduced, and I agree, it promotes readibility.
However Nullable has my head scratching quite a bit.
C#8.0 introduces a way to deal with null, yet it also provides numerous escape roads. So I advise not to use it, explained in the article why not to do it...
Whilst I admire the efforts that have gone into making the world more null-safe, I fear that all the escape roads given make this feature not only useless but dangerous and confusing. Crucially this is where the feature fails massively in its goal to get rid of null reference exceptions. In fact, you could argue it makes it even worse as it masks, gives you exit-strategies (like goto).
Microsoft has a history of killing old technologies like these recent technologies were killed in
.NET Core 3.0:
• ASP.NET Web Forms
• ASP.NET MVC
• WCF Server
• Windows Workflow
• AppDomains
• Code Access Security
(c-sharpcorner.com/article/future-o...
All community developed many codes with .NET Framework 4.x that is good and don't need to be upgraded to .NET Core x.x and why we need stop in time using .NET Framework if c# is opensource? The Visual Studio team must have some work to make c# 8.0 compatible.
We don't want to throw our code in the trash of time. We will design in .NET Core 3.x, 5, but our current code must not be forgotten.
Based on this I created today a Facebook page to POST protests against Microsoft and call the community to demand c# 8.0 works with .NET Framework 4.x.
Share, like, comment:
facebook.com/CSharp-8-to-Microsoft...
Great article, thanks. I do like the "nullable reference types" naming though. I think the implementation plays well with the existing "nullable value types" paradigm.
I'm oddly picky on names. I'm okay with the term when saying that string? is what's new, as that is a nullable ref type. But the reality is we've been living with nullable ref types since the betas.
Just my own neuroses. Thanks for the feedback!
Nice article Matt.
Thanks! This one blew up today on Medium and I'm not sure why. Always nice to have writing benefit others.
Thank you Matt. Your article is really great!
Readonly Members
this looks similar to the old c++ way of marking method as const
where value is a member field.