How C# 8 Helps Software Quality

Matt Eland - Oct 9 '19 - - Dev Community

.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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. It prevents you from accidentally changing internal state in places where that was not expected (most property getters for example).
  2. 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;
}
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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
};
Enter fullscreen mode Exit fullscreen mode

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?

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player