December 5, 2020

C# Updates for the Absent C# Developer (C# 6.0 and newer overview)

This post is part of C# Advent Calendar 2020.

Update 6. December 2020: Some good small discussion on Reddit.com

It has been a while since I actively developed in C#. I mostly worked with C# and .NET during the 3.0 to 4.5 days and I did async/await work very early on, so I skip over that as well. After a job change, I didn’t touch C# for actual work. I mostly just watched the development from the sidelines via news. Today, I take a short look at some features. I will skip a lot and just add some of my highlights tour.

C# grew up
Figure 1. C# All Grown Up

.NET / C# runs on Everywhere

Ok, it is the first time I tried out dotNet core on Linux. I do love dotNet core: It behaves like most language-SDKs. You download it, unpack it and of you go. No installation and bundling into Windows \o/

I downloaded .NET SDK 5.0, unzipped it and set up the environment variables. I installed IntelliJ Rider, since I’m familiar with the Jetbrains ecosystem and used it previously. It worked out of the box.

String Interpolation

Yeah, string interpolation. I think there isn’t a day in programming where I don’t have to concatenate strings together. So, either you had to use the plus operator or string.Format. String.Format is brittle and hard to read, because you need to do the line up the placeholders with the arguments. String interpolation makes everything crystal clear

String Concatenate the Hard Way:
Console.WriteLine("The number is "+aNumber+" and the string is "+aString+" with more "+moreStuff + " " + evenMore +" stuff");
// I hope you got the order right =)
Console.WriteLine("The number is {0} and the string is {1} with more {2} {3} stuff", aNumber, aString, moreStuff, evenMore);
Crystal Clear String Concatenation:
// Woop, woop, cristal clear
Console.WriteLine($"The number is {aNumber} and the string is {aString} with more {moreStuff} {evenMore} stuff");

Static Imports (C# 6.0)

This is something I missed from Java. The ability to import static methods. It allows you to use methods like their free-standing methods. Especially useful for things like importing Asserts. Previously you had to always add the class, which was most of the time noise:

Repeating the Asserts Class:
Assert.AreEqual("Expected", actualValue);
Assert.IsNotEmpty(otherValue);
Assert.IsEmpty(yetAnotherValue);
With Static Imports:
// Use a static import of the Assert class
using static NUnit.Framework.Assert;
// Then you can use the static methods on it directly
AreEqual("Expected", actualValue);
IsNotEmpty(otherValue);
IsEmpty(yetAnotherValue);

Better Auto Property Support (C# 6.0)

It allows you to create an auto property and directly initialize it. Previously you only could initialize fields directly, but auto-properties had to be initialized in the constructor. This levels the playing field.

Auto Properties must be Initialized in the Constructor Before:
class SomeValues
{
    public int Answer = 42; // directly initialized
    public string StringAnswer { get; set; }; // had to be initialized in the constructor =(

    public SomeValues()
    {
        StringAnswer = "42";
    }
}
Auto Properties Can Be Initialized Like Fields:
class SomeValues
{
    public int Answer = 42; // directly initialized
    public string StringAnswer { get; set; } = "42"; // Yeah
}

Also read only auto properties are better supported now. Before, you couldn’t have read-only auto-properties. You either had to create a read-only backing field yourself, or have an implicit understanding that a property is read-only:

Read Only Properties:
class ReadOnlyValues
{
    // Choose your poison: Boiler plate code
    private readonly int _intAnswer = 42;
    public int IntAnswer
    {
        get { return _intAnswer; }
    }
    // Or 'trust' that everyone understand this is read only
    public string StringAnswer { get; private set; }

    public ReadOnlyValues(int answer)
    {
        _intAnswer = answer;
        StringAnswer = answer.ToString();
    }
}
Readonly Auto Property Yeah:
class ReadOnlyValues
{
    public int IntAnswer { get; }
    public string StringAnswer { get; }

    public ReadOnlyValues(int answer)
    {
        IntAnswer = answer;
        StringAnswer = answer.ToString();
    }
}

Conditional Null (C# 6.0)

The new ?. operator allows dereferencing a field or property, but return null if the reference itself is null.

Checking For Null The Hard Way:
var answerOrNull = someValues == null ? null : someValues.StringAnswer;
Propagate the Null:
var answerOrNull = someValues?.StringAnswer;

Expression-Bodied Members

This is probably the change that feels most 'non-C#' to me. You can put a lamda expression where a method body would be. It removes some boilerplate. However, it seems to me that a more compact code formatting would do half of the trick. It’s just that the C# community likes 'well spaced out' formatting.

Classic Method Style:
public override string ToString()
{
    return $"{IntAnswer} -> {StringAnswer}";
}

public void PrintToConsole(string prefix)
{
    Console.WriteLine($"{prefix}: {IntAnswer} -> {StringAnswer}");
}
Expression Style:
public override string ToString() => $"{IntAnswer} -> {StringAnswer}";

public void PrintToConsole(string prefix) => Console.WriteLine($"{prefix}: {IntAnswer} -> {StringAnswer}");
However, Compact Code Style Does Half the Trick:
public override string ToString() { return $"{IntAnswer} -> {StringAnswer}"; }

public void PrintToConsole(string prefix) { Console.WriteLine($"{prefix}: {IntAnswer} -> {StringAnswer}"); }

Pattern Matching (C# 7.0)

I’m very familiar with pattern matching from my work in Scala. So, nice to have. It allows you to write compact matching rules instead of a long if-else chain.

If Else Chain:
interface Shape
{
}

class Circle : Shape
{
    public int Radius { get; }
}

class Square : Shape
{
    public int Side { get; }
}

public static double ComputeArea(Shape shape)
{
    var s = shape as Square;
    var c = shape as Circle;
    if (s != null)
    {
        if (s.Side == 0)
            return 0;
        else
            return s.Side * s.Side;
    }
    else if(c != null)
    {
        if (c.Radius == 0)
            return 0;
        else
            return c.Radius * c.Radius * Math.PI;
    }
    else
    {
        throw new ArgumentException(
            message: "shape is not a recognized shape",
            paramName: nameof(shape));
    }
}
If Else Switch Jungle
Figure 2. If Else Switch Jungle
Pattern Match:
public static double ComputeArea(Shape shape)
{
    switch (shape)
    {
        case Square s when s.Side == 0:
        case Circle c when c.Radius == 0:
            return 0;

        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}
Pattern Match
Figure 3. Pattern Match

Digit Separator (C# 7.0)

Such a small thing I was missing from Java and still missing in some languages. It allows you to use an underscore to write down large number literals

Count the Zeros:
// How large is this number? Count the zeros, don't screw it up
public const long LargeNumber = 10000000000;
It is Obviously 10 Billions:
// How large is this number? Count the zeros, don't screw it up
public const long LargeNumber = 10_000_000_000;

Default Interface Members (C# 8.0)

So people would extend interfaces with extension methods. However, sometimes you want to extend the interface with some reasonable method, but allow implementations to override that method. Default methods allows adding a method with implementation to an interface. This looks like the same mechanism as Java has to me.

Original Interface with Extension:
// Original Interface
public interface IOrder
{
    DateTime Purchased { get; }
    decimal Cost { get; }
}

// Added Later
public static class OrderExtensions
{
    // Works, but implementations cannot override this static method.
    public static string AsCsvLine(this IOrder order)
    {
        return $"Order;{order.Purchased};{order.Cost}";
    }
}
With Default Method:
public interface IOrder
{
    DateTime Purchased { get; }
    decimal Cost { get; }

    public string AsCVSLine()
    {
        return $"Order;{Purchased};{Cost}";
    }
}

// Unlike a Extension method, Default methods can be overridden
public class SpecialOrder : IOrder
{
    // ...
    public string AsCVSLine()
    {
        return $"Super-Special;{Purchased};{Cost}";
    }
}

Nullable References (C# 8.0)

You now can declare references as nullable. What? you say, references are already nullable. Yes, but as reader, it helps me a lot to know that something is nullable or not. My own conversion in my code usually is: References are not nullable, and if they are, they have an ugly name like 'nameNullable'. But with nullable references, I can easily declare it without ugly names.

Even better, the compiler can warn about nullable issues. I expect that over time the warnings screws get tightened bye default, until putting a null into a regular reference is an error in future versions of C#. So, it will transition to a Kotlin like behavior.

Without Nullable References:
public class Person
{
    // Required
    public string DisplayName { get; set; } = "";
    // Nullable
    public string NickName { get;set; }
}

public static void SomeOperations()
{
    var person = SomePerson();

    // Oups, maybe assigns null to a field not expecting null
    // Nothing hints at the issue. NullPointer exception maybe way later on
    // Or worse, invalid state got persisted to database/disk
    person.DisplayName = person.NickName;
}
Guess the Required Fields
Figure 4. Guess the Required Fields
With Nullable Hints, The Reader Has a Chance to Notice the Bug and the Compiler Warns You
// Enable nullable warnings by the compiler
#nullable enable

public class Person
{
    public string DisplayName { get; set; } = "";
    public string? NickName { get;set; }
}

public static void SomeOperations()
{
    var person = SomePerson();

    // Compiler warns: Program.cs(46, 34): [CS8601] Possible null reference assignment.
    person.DisplayName = person.NickName;
}

Records (C# 9.0)

Records, yeah =). Very familiar with it from Scala (called case classes in Scala) I’m happy to see this appear in C# and Java. It allows you to write down a pure data class and I have tons of these usually. It removes the noise no-one is interested in. In languages with records, its way easier to just pass data around where you don’t need more. Yes, you can do it without records, but it gets drawn down in the noise and it’s easy to break the rules: Like change the fields and forget to update the equality operations. Furthermore, records 'scream' at the reader: I’m just holding this data, nothing to see here.

Without Records:

public class Person
{
    public string DisplayName { get; set; } = "";
    public string? NickName { get;set; }
    public string? Email { get;set; }

    public Person(string displayName, string? nickName, string? email)
    {
        DisplayName = displayName;
        NickName = nickName;
        Email = email;
    }
    protected bool Equals(Person other) // Boiler plate

    public override bool Equals(object? obj)  // Boiler plate

    public override int GetHashCode()  // Boiler plate
}

With Records:

public record Person
{
    public string DisplayName { get; init; } = "";
    public string? NickName { get;init; }
    public string? Email { get;init; }

    // Done. Constructor, Equality etc is done for us
}

Even better, it also includes improvements to work with the immutable records. There is a with operator to create a new record with your changes applied.

With Operator:
var person = SomePerson();

var alternateEgo = person with { DisplayName = "Batman" };
Keeping Records
Figure 5. Keeping Records

I ran out of time: Tons of Features I didn’t check out yet

Again, there seems tons of small improvements around tuples, structs, out parameter etc. I just ran out of time to take a deeper look for this blog post =)

Out of Time
Figure 6. Out of Time
Tags: C# Development