DEV Community

Cover image for 8 🍨 Sweet Treats 🍨 in Modern C#
Henrick Tissink
Henrick Tissink

Posted on

8 🍨 Sweet Treats 🍨 in Modern C#

C# through the Ages

alt text

Many, many eons ago ✨, C# 'twas but an archaic, OOP focused language. Alas, 'tis no more! Many updates and feature requests later C# has evolved, shedding the object focused shackles of its past, becoming a modern, functional, programming language.

Here are 8 🔥 sizzling 🔥 features to use from C# 7, 8, 9, and 10 to write modern C#.

Throw Expressions

throw simba

A simple lil' nugget 💎 that is most useful in constructors, when combined with the null-coalescing operator is the throw expression

public string Color { get; set; }
public int Size { get; set; }

// ctor
public Car(string? color, int? size)
{
    Color = color ?? throw ArgumentNullException(nameof(color));
    Size = size ?? throw ArgumentNullException(nameof(size));
}
Enter fullscreen mode Exit fullscreen mode

If either color or size is null, the application will throw a ArugmentNullException.

Another useful place to use this is in conjunction with the ternary operator.

public void SetCharacterLimitedName(string newName, int characterLimit)
{
    Name = newName.Length > characterLimit
        ? throw new ArgumentOutOfRangeException(nameof(newName))
        : newName;
} 
Enter fullscreen mode Exit fullscreen mode

The method will throw an ArgumentOutOfRangeException if the length of the new name exceeds the character limit.

Out Variables

Functions with out parameters, returning boolean values, make the best use of this feature. It allows us to do a conditional check, and if it passes we know something about the out variable, and can then use the variable within the scope of the check.

Old way

bool isLucky(int someNumber, out string result)
{
    // the number 7 is lucky
    result = someNumber == 7 ? "you win!" : "you lose...";
    return someNumber == 7;
}

string luckyMessage;
var isLucky = isLucky(11, out luckyMessage);

if (isLucky)
    Console.WriteLine(luckyMessage);
Enter fullscreen mode Exit fullscreen mode

New way

bool isLucky(int someNumber, out string result)
{
    // the number 7 is lucky
    result = someNumber == 7 ? "you win!" : "you lose...";
    return someNumber == 7;
}

if (isLucky(7, out var message))
    Console.WriteLine(message);
Enter fullscreen mode Exit fullscreen mode

This is much more concise, and easy to read!

Pattern Matching with and, or, and not

The and, or, and not keywords have been added to pattern matching in C# to further enrich the pattern matching experience. It makes the code more readable in English, and allows for many combinations of boolean checks in a single line, ensuring there is much less code. With the addition of and, or and not pattern matching goes into overdrive.

// Setup
public enum Color
{
    Red,
    Orange,
    Green,
    Yellow
}

public class Fruit
{
    public string? Name { get; set; }
    public Color Color { get; set; }
    public bool IsTasty { get; set; }
    public int Quantity { get; set; }
}

// Implementation
var firstFruit = new Fruit
{
    Name = "Tomato",
    Color = Color.Red,
    IsTasty = true,
    Quantity = 10
};

var secondFruit = new Fruit
{
    Name = "Banana",
    Color = Color.Green,
    IsTasty = false,
    Quantity = 12
};

var thirdFruit = new Fruit
{
    Name = "Orange",
    Color = Color.Orange,
    IsTasty = false,
    Quantity = 50
};

if (firstFruit is {Name:"Tomato", IsTasty:true, Quantity:>1}
or {Name:"Tomato", Color:Color.Red, Quantity:>10})
    Console.WriteLine("Tomato, at least one tasty or at least 10 red");

if (secondFruit is {Name:"Banana"} and {IsTasty:true}
and {Color:Color.Yellow} and {Quantity:>12})
    Console.WriteLine("Banana, tasty and yellow and at least 12");

if (thirdFruit is {Name:"Orange"} and not {IsTasty:true}
or not {Color:Color.Orange, Quantity:<5})
    Console.WriteLine("Orange, not tasty or orange and less than 5");
Enter fullscreen mode Exit fullscreen mode

Pattern Matching 🍨 and out

patterns

Combining the aforementioned out variables with pattern matching, gives a wonderfully short, readable result

object numberObject = "12345";

if (numberObject is int number
|| (numberObject is string numberString 
    && int.TryParse(numberString, out number))
{
    Console.WriteLine($"It's a number alright! {number}");
}
Enter fullscreen mode Exit fullscreen mode

Here we check if the object is an int, or a string. If it is a string, we then try and parse it. If it parses then we have the result number available to use within the if block.

but wait

Pattern matching also extends far beyond simple type checks. Properties can be matched too!

public class Fighter 
{
    public string? Name { get; set; }
    public string? Style { get; set; }
    public int Strength { get; set; }
    public bool IsChampion { get; set; }
}

var fighter = new Fighter { ... };

if (fighter is {IsChampion:true})
    Console.WriteLine($"{fighter.Name} is a champ!");

if (fighter is {Style:"Karate", Strength:>9000) 
    Console.WriteLine($"{fighter.Name} is powerful!");
Enter fullscreen mode Exit fullscreen mode

Doing property patterns in this way also has the added benefit of an implicit null check. The {} pattern matches with everything except null.

Switch Expressions + Property Patterns 💅

crown

The greatest 👏 the best 👏 the most amazing 👏 of the new features - Switch Expressions! 😎

Combined with property patterns they allow a whole host of conditional operations, in a few short lines of 🧊 crisp C#.

We got em basic 🌼

public string Alakazam(int magicNumber)
{
    return magicNumber switch
    {
        1 => "pow",
        2 => "magic!",
        3 => "behold...",
        // default case
        _ => "ummm, I got nothing..."
    }
}
Enter fullscreen mode Exit fullscreen mode

And extra spicy 🌶️ 🌶️ 🌶️

// The setup
public class Monster
{
    public string? Name { get; set; }
    public int? NumberOfTentacles { get; set; }
    public bool? IsScary { get; set; }
    public Power? Power { get; set; }
}

public class Power
{
    public int Level { get; set; }
    public string? Element { get; set; }
    public bool? DoesPierceArmour { get; set; }
}

// The execution!!!
var monster = new Monster
{
    Name = "Mildred",
    NumberOfTentacles = 23,
    IsScary = true,
    Power = new Power
    {
        Level = 3000,
        Element = "Fire",
        DoesPierceArmour = false
    }
};

var res = monster switch
{
    var m when monster.NumberOfTentacles > 100 =>
        $"Too many damn tentacles! {m.NumberOfTentacles}",
    { IsScary: true } => "Ahhhhhhh!",
    { Power.Element: "Fire"} => "Ooo hot! hot! hot!",
    { Power.DoesPierceArmour: false } => "I ain't afraid of you!",
    var m when monster.Power.Level > 3000 =>
        $"So much power! {m.Power}",
    _ => "ummm, I got nothing?"
};
Enter fullscreen mode Exit fullscreen mode

Each of the case statements are so declarative that their meaning is implicit. Simply by reading the code you already know what's being checked at each case. So much meaning is suddenly contained in so little code.

This is the true power of 🌟 Switch Expressions 🌟.

(Named) Tuples

Ah, the humble tuple 🌝. So short. So sweet. And so very useful. Tuples really shine in places where you need to return multiple values but don't want to create a new object/type to do so.


public (string name, bool isReadyToParty) SomeThing() =>
("Gerry", true);

// Destructuring the tuple
var (name, isReadyToParty) = SomeThing();

Console.WriteLine($"Name: {name}, Ready to Party? {isReadyToParty}")

Enter fullscreen mode Exit fullscreen mode

When using named tuples, where each property of the tuple has a name, it can then be deconstructed on invocation, as var (name, isReadyToParty). This gives a clean way to return multiple variables AND extract them in the calling code.

Records

records

record types have the potential to be amazing. They're supposed to represent immutable objects - but are only immutaable through the use of init only setters. The init only setters ensure that their properties cannot be set after initialization - and hence guarantees immutability. *BUT record types are not currently immutable by default, and that makes me sad 🐼.

Immutability is on of the cornerstones of the functional programming paradigm.

public record Person
{
    public string? Firstname { get; init; }
    public string? Lastname { get; init; }
};

var person = new Person { Firstname = "Sam" };

// this throws an error
person.Lastname = "Person";
Enter fullscreen mode Exit fullscreen mode

Immutability prevents the following pattern


public record Person
{
    public string? Fullname { get; set; }
    public int? Counter { get; set; }
    public bool? IsTrue { get; set; }
};

var person = new Person { Fullname = "Anna" };

person.Counter++;
person.Fullname += " Luisa";
person.IsTrue != person.IsTrue;

someOtherMethod(person);
andAnotherMethod(person);
yetAnother(person);

/*
 *
 * 20 lines of code
 * passing person around
 *
*/

person.Counter++;
person.Fullname += " Magdalena";
person.IsTrue != person.IsTrue;

/*
 *
 * 20 lines of code
 * passing person around
 *
*/

person.Counter++;
person.Fullname += " Susanna";
person.IsTrue != person.IsTrue;

Enter fullscreen mode Exit fullscreen mode

What is the Person object's name? What is the value of Counter? Do we know if it was mutated inside one of the methods - or maybe not. What is the value of IsTrue? By making Person immutable we can only set this once, and prevent this sort of mess from forming, or compounding on a single object. It simplifies the logical flow through the code.

Adios Amigos

adios

The breadth and scope of the Modern C# programming language, with its wealth of new features 🪐, introduced over the last 4 years, may seem overwhelming at first 😅. However, learning to use these features a few at a time is easy 🏖️ - and will drastically increase the enjoyment derived from working with C#.

Top comments (3)

Collapse
 
taufeeq849 profile image
taufeeq849

simple, clear and consice. nobody does it like @htissink

Collapse
 
andrewbaisden profile image
Andrew Baisden

Great article I am still new to C# so I found this useful.

Collapse
 
canro91 profile image
Cesar Aguirre

I really liked the named tuples and records. Good addition to the C# language. But, I'm still waiting for built-in support for discriminated unions