Introduction

We all know that these days, for better or for worse, C# is evolving very quickly and adding features and syntactic improvements left and right. Is it an attempt to be relevant, is it an attempt to be like the cool kids, I don’t really know. It is not so good that there are many ways to do something as it only leads to unnecessary confusion.

Wanting to make the process easier is a laudable goal but on the other hand adding new optional things is not really the best use of Microsofts time. Instead, constantly adding the new ways to do existing things which can easily lead to much less uniform codebase, Microsoft could focus on adding new features.

However, whether we like it or not, the new stuff is here to stay.

As C# developers, it’s not essential to know every method of implementation, but it is crucial to make an effort to stay current. While it may not be feasible to learn about every update immediately upon release, given our real-world obligations and the need to deliver results to clients, it is reasonable to expect that we should allocate time to stay informed. If C# is your primary professional focus, it’s fair to assume that your employer would be supportive of this.

In this brief article series, I aim to update you to a certain extent. We will begin with C# 6.0, assuming that no one is lagging by more than a decade. 😉

Changes in C# 6.0

We’ll start easy, as in 2024 I would hope most people are familiar with these changes and then in some later post we’ll talk about the changes in newer versions.

Null-Conditional Operators ?. and ?[]

We all know what this is about: NullReferenceExceptions. As we all know, they happen because the developer is a bad person did not check for null before invoking a member of a null object.

static string ConvertGreetingToUpperCase(string greeting)
{
    return greeting.ToUpper();
}

string greeting = ConvertGreetingToUpperCase(null);
Console.WriteLine($"Greting in UpperCase: {greeting}");

So, the code above produces a NullReferenceException. In oroder to fix this we could add a check for null and then proceed to convert to upper case only if that conditon is not satisfied:

static string ConvertGreetingToUpperCase(string greeting)
{
    if (greeting == null)
    {
        return greeting;
    }

    return greeting.ToUpper();
}

string greeting = ConvertGreetingToUpperCase(null);
Console.WriteLine($"Greting in UpperCase: {greeting}");

Even if the null check is simple, it is still quite verbose to write So in C# 6 we were given the ability to simplify these types of null-checks:

static string ConvertGreetingToUpperCase(string greeting)
{
    return greeting?.ToUpper();
}

If the value of the object is null, ,the null-conditional operator will return null. But what happens if the operator is a member of a call chain? In the following example, we would still not get a null reference exception, even if Trim() is seemingly called on the possible null value from the null conditional operator greeting?.ToUpper. That is because the operator short-circuits and immediately returns null without calling Trim(). This is called null-propagation.

static string ConvertGreetingToUpperCase(string greeting)
{
    return greeting?.ToUpper().Trim();
}

There is also a element access conditional operator – ?[…] What it does, is it goes into index of a collection only if a collection isn’t null. It can be understood as:

var item = (collection != null) ? collection[index] : null.

So, if collection evaluates to non-null, the result of collection?[index] is the same as collection[index]. If the collection returns null, the resul of the operator is null if the collection is null.

However, I have seen people being surprised at collection?[index-out-of-bounds] throwing an IndexOutOfRangeException. That happens because the collection is not null and the index was out of bounds, so it works the same as collection[i] for non-null collections.

Auto-Property Initializers

Simply said, this feature allows us to set an intital value for a property immediately after declaring it. This also applies to read-only properties:

public class ClassOne
{
    public int Quantity { get; set; } = 42;
    public DateTime CreationTimeUtc { get; } = DateTime.UtcNow;
}

String Interpolation

The character $ creates an interpolated string. This is another, more natural way to represent and replace placeholders in a string:

Console.WriteLine($"The person's name is: {FirstName} {LastName}");

Expression Bodied Members

You are probably already familiar with this one as it is one of the first changes people were adding to their code base, in my experience. This allows us to write methods as single line expressions:

public void PrintFullName() => Console.WriteLine($"{FirstName} {LastName}");
public static string GetCurrentTimeAsString() => DateTime.Now.ToString();

Exception Filters

This allows us to catch exceptions only in certain conditions:

static async Task<string> GetContent()
{
    try
    {
        var client = new HttpClient();
        return await client.GetStringAsync("https://someserver.com");
    }
    catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.MovedPermanently)
    {
        return "Moved Permanently";
    }
    catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.NotFound)
    {
        return "Not Found";
    }
    catch (HttpRequestException e)
    {
        return e.Message;
    }
}

nameof Expression

A nameof expression will simply produce the name of the variable, type or a member as a string. This serves two purposes – it saves us from manually typing it and it also will prevent the code from compiling if the name changes. However, any decent code editor will have a rename function which will rename that member and adjust the code everywhere else where the member is referenced – including in the nameof operator:

public Person(string firstName, string lastName)
{
    if (firstName == null) 
    {
        throw new ArgumentNullException(nameof(firstName));
    }

    if (lastName == null) 
    {
        throw new ArgumentNullException(nameof(lastName));
    }

    FirstName = firstName;
    LastName = lastName;
}

Static Imports

This allows us to use static methods on a class without using the class name:

using static System.Console;
using static System.Math;

internal class Program
{
    private static void Main(string[] args)
    {
        WriteLine(Max(3, 4));        
    }
}

There you have it. This was a quick introduction to some of the features added in C# 6.0. I am sure you were at least a bit familiar with some of them- and if not, even better. That means you learned something new here and that is the most I can hope to accomplish – somehow, somewhere, teach somebody something.