featured image thumbnail for post Jimmy Boinembalome

Jimmy Boinembalome

4 min read

Some features of C# 8.0 to reduce our code

Let's discover some features and improvements that appeared with the 8th version of the C# language.

featured image thumbnail for post Some features of C# 8.0 to reduce our code
Photo by Marek Piwnicki on Unsplash

C# 8.0 was released in September 2019 at the same time as .NET Core 3.0. In this article, we will look at some examples of features that can reduce the size of our code and/or make it more readable. Of course, the features of C# 8.0 are applicable in higher versions.

Switch expression

The switch expression is a new way of writing a switch statement. It avoids writing the words case and break which can be redundant when we have several cases to consider.

Traditional way:

Copy
public static string DoYouLikeCSharp8(bool? answer)
{
    switch (answer)
    {
        case true:
            return "It's fun! ๐Ÿ˜ƒ";
        case false:
            return "The next versions will satisfy you better! ๐Ÿ™";
        default:
            return "Oh... ๐Ÿ˜ข Do you prefer another language? ๐Ÿ˜‡";
    };
}

Now let's reproduce the same code with the switch expression.

New way:

Copy
public static string DoYouLikeCSharp8(bool? answer)
{
    return answer switch
    {
        true  => "It's fun! ๐Ÿ˜ƒ",
        false => "The next versions will satisfy you better! ๐Ÿ™",
        _     => "Oh... ๐Ÿ˜ข Do you prefer another language? ๐Ÿ˜‡"
    };
}

Property and Tuple patterns

Property pattern

The property model allows us to establish cases of a switch expression by taking into consideration the properties of an object.

Copy
public enum Level
{
    Notion,
    Practice,
    Autonomous,
    Expert
}
Copy
public class Language
{
    public string Name { get; set; }

    public Level Level { get; set; }

    public Language(string name, Level level)
    {
        Name = name;
        Level = level;
    }
}

Traditional way:

Copy
public static string GetLevelMessage(Language language)
{
    if (language.Level == Level.Notion && language.Name == "C#")
        return $"You are some notions in C#! ๐Ÿ‘";
    else if (language.Level == Level.Practice && language.Name == "C#")
        return $"You practice the C#! ๐Ÿ’ช";
    else if(language.Level == Level.Autonomous && language.Name == "C#")
        return $"You are automomous in C#! ๐Ÿค˜";
    else if(language.Level == Level.Expert && language.Name == "C#")
        return $"You are an expert in C#! ๐Ÿ‘";
    else 
        return $"I don't know what your level in {language.Name} is but I'm sure you are good! ๐Ÿ˜Œ";
}

In this example, we return a message according to the level and the name of the language. This implies to have a succession of if/else if. (Only for illustration, it is possible to use another way like the Factory Pattern as well)

With the property pattern, we can improve our code by using a switch expression.

New way:

Copy
public static string GetLevelMessage(Language language)
{
    return language switch
    {
        { Level: Level.Notion, Name: "C#" } => $"You are some notions in C#! ๐Ÿ‘",
        { Level: Level.Practice, Name: "C#" } => $"You practice the C#! ๐Ÿ’ช",
        { Level: Level.Autonomous, Name: "C#" } => $"You are automomous in C#! ๐Ÿค˜",
        { Level: Level.Expert, Name: "C#" } => $"You are an expert in C#! ๐Ÿ‘",
        _ => $"I don't know what your level in {language.Name} is but I'm sure you are good! ๐Ÿ˜Œ"
    };
}

Tuple pattern

Like the property pattern, we can apply a switch expression based on a tuple pattern:

Copy
public static string GetLevelMessage(Level level, string language)
{
    return (level, language) switch
    {
        (level: Level.Notion, language: "C#") => $"You are some notions in {language}! ๐Ÿ‘",
        (level: Level.Practice, language: "C#") => $"You practice the {language}! ๐Ÿ’ช",
        (level: Level.Autonomous, language: "C#") => $"You are automomous in {language}! ๐Ÿค˜",
        (level: Level.Expert, language: "C#") => $"You are an expert in {language}! ๐Ÿ‘",
        _ => $"I don't know what your level in {language} is but I'm sure you are good! ๐Ÿ˜Œ"
    };
}

Using declaration

Sometimes in C# it can be useful to dispose an object. For example, this is necessary when we read a text file with StreamReader or when we open a SQL connection with SqlConnection.

The using statement allows us to define a limit for the object outside of which, the object is automatically destroyed. This makes our code more elegant and avoids calling the dispose method in a try/catch.

Traditional way:

Copy
public static void ReadFile(string path)
{
    using (var streamReader = new StreamReader(path))
    {
        string line;
        // Read and display lines from the file until the
        // end of the file is reached.
        while ((line = streamReader.ReadLine()) != null)
            Console.WriteLine(line);
    } // file is disposed here
}

Since C# 8.0, it is possible to make a using declaration. This indicates to the compiler that the variable with the using keyword should be disposed at the end of the scope.

New way:

Copy
public static void ReadFile(string path)
{
    using var streamReader = new StreamReader(path);
    string line;
    // Read and display lines from the file until the
    // end of the file is reached.
    while ((line = streamReader.ReadLine()) != null)
        Console.WriteLine(line);
} // file is disposed here

Null-coalescing assignment

As we know, the null coalescence operator ?? returns the value of the left side if it is not null. Otherwise, it returns the value of the right side:

Copy
string message = null;
message = message ?? "Default message";

Here message equals to "Default message" because message was null before.

With the null-coalescing assignment, we can simplify the second line like this:

Copy
string message = null;
message ??= "Default message";

Indices and ranges

Indices and ranges provide an elegant syntax for accessing single elements or ranges in a sequence of elements.

To see this, let's create an array of elements:

Copy
var emojis = new string[]
{
    "๐Ÿ™‚","โ˜น๏ธ", "๐Ÿ‘", "๐Ÿ‘Ž",
    "๐Ÿถ", "๐Ÿฑ", "๐ŸŽ", "๐Ÿ"
};

Indices

The ^ operator allows us to use the index from end:

Copy
var lastEmoji = emojis[^1]; // emojis[emojis.Length - 1]
var secondLastEmoji = emojis[^2]; // emojis[emojis.Length - 2]
var thirdLastEmoji = emojis[^3]; // emojis[emojis.Length - 3]

Ranges

About ranges, there also is new operator .. which specifies the start and end of a range:

Copy
var smileys = emojis[0..2]; // emojis.Take(2)
var gestures = emojis[2..4]; // emojis.Skip(2).Take(2)
var animals = emojis[4..6]; // emojis.Skip(4).Take(2)
var fruits = emojis[6..8]; // emojis.Skip(6).Take(2)

Conclusion

With C# 8.0, programming may not be as easy to understand if you are not familiar with the new features. However, once learned, it becomes a real advantage for all those who know it with its shorter and more elegant syntax.

I hope you enjoyed reading this article! ๐Ÿ‘

If you want to know more about the features of C# 8.0, here is a link from the Microsoft docs:

Blouppy

Making the world a better place by sharing information for all.

ยฉ 2025 Blouppy, Inc. All rights reserved.