Useful C# Features (or “Cool Things You Didn’t Know You Could Use in Unity”)

1. Tuples & Discards!

Tuples is something more dynamic languages like JavaScript have been holding over the more static languages for a while. It allows you to do more with less action (higher level ones, that is). While tuples have existed in C# before as a class of their own, they are now getting a proper implementation. It basically makes a complex variable/object without having to predefine it using a class or a struct.

var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");

You can even use it to return more than one element from a method(!!!). Unfortunately, the Unity editor doesn’t support handling those kinds of returns yet but they are still useful inside your scripts.

Discards are those underscores you see in the example below. It allows you to deconstruct a tuple and discard the data you don’t want, only keeping what you actually need in its own named variable.

using System;

public class Example
{
    public string NewYorkCityName
    {
        get
        {
            var (_, name, _) = QueryCityData("New York City");
            return name;
        }
    }

    private static (string name, int pop, double size) QueryCityData(string name)
    {
        if (name == "New York City")
        { return (name, 8175133, 468.48); }

        return ("", 0, 0);
    }
}

2. Pattern Matching!

Pattern matching allows you to test a runtime type in an if or switch statement and then convert it to that specific type all in a single statement!

if (input is int count)
{ sum += count; }

It also increases the power of switch, allowing you to do a lot more with less and create much more complex testing blocks.

public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
    int sum = 0;
    foreach (var i in sequence)
    {
        switch (i)
        {
            case 0: break;
            case IEnumerable<int> childSequence:
            {
                foreach(var item in childSequence)
                { sum += (item > 0) ? item : 0; }
                break;
            }
            case int n when n > 0: sum += n; break;
            case null: throw new NullReferenceException("Null found in sequence");
            default: throw new InvalidOperationException("Unrecognized type");
        }
    }
    return sum;
}

3. Local functions

Local functions is currently one of my favorite features in C#. You could already create functions inside functions by assigning a block into an Action or Func variable but now you can just define an anonymous function and not worry about what returns or not returns a value or where exactly you defined which variable. So, if you need a complex block or calling the same function multiple times but you don’t want it available outside, Local Functions are here for you.

private static string GetText(string path, string filename)
{
     string AppendPathSeparator(string filepath)
     { return filepath.EndsWith(@"\") ? filepath : filepath + @"\"; }

     var reader = File.OpenText($"{AppendPathSeparator(path)}{filename}");
     var text = reader.ReadToEnd();
     return text;
}

4. Switch Expressions!

You can now rephrase your Switch blocks into something much nicer and readable.

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82),
        Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

5. Property Patterns

This is another wonderful extension to the switch statement allowing you to switch on a structure and get output depending on its internal properties rather easily.

public static decimal ComputeSalesTax(Address location, decimal salePrice) =>
    location switch
    {
        { State: "WA" } => salePrice * 0.06M,
        { State: "MN" } => salePrice * 0.075M,
        { State: "MI" } => salePrice * 0.05M,
        // other cases removed for brevity...
        _ => 0M
    };

6. Tuple Patterns

This is another cool extension to the switch statement, allowing you to match a case based on individual items inside a tuple breakdown.

public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };

7. Using Declarations

One of the really nice things in C# over C and C++ is that it is a managed language. Yes, there are performance differences (which Unity has almost 100% taken care of) but it is a huge load of responsibility off the developer. The Using declaration is a great way to take advantage of it. When you need to open a file or create a web request but just for a short time and you want to dispose of it properly… you can go through the whole rigmarole or just enclose the code in a Using block that takes care of everything for you.

static int WriteLinesToFile(IEnumerable<string> lines)
{
    using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
    {
        int skippedLines = 0;
        foreach (string line in lines)
        {
            if (!line.Contains("Second"))
            { file.WriteLine(line); }
            else
            { skippedLines++; }
        }
        return skippedLines;
    } // file is disposed here
}

8. Indices and Ranges

First of all, you can now define a range in C#.

Range phrase = 1..4;

And you can use those numbers or that Range to access a sub-range or a sub-group of another variable. However, be careful because range accessors are beginning inclusive and ending exclusive.

var words = new string[] { "The", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog" };

var quickBrownFox = words[1..4]; // "quick", "brown", "fox" and no "jumped"

var lazyDog = words[^2..^0];
 // The from-end index accessor. ^0 is the end and not included
var allWords = words[..]; // contains "The" through "dog".
var firstPhrase = words[..4]; // contains "The" through "fox"
var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
var text = words[phrase]; // is like quickBrownFox

9. Null Coalescing Assignment

The Null Coalescing Operators are some of my favorite features in new C#. It allows you to check for Nullls and then do things accordingly. The null-coalescing assignment operator checks if the left side is null. If it isn’t, it’s returned. If it is, the right side is assigned to it and then it’s returned. It’s a great way to do lazy resource assignment and/or creation. 

List<int> numbers = null;
int? i = null;

numbers ??= new List<int>();
numbers.Add(i ??= 17);
numbers.Add(i ??= 20);

Console.WriteLine(string.Join(" ", numbers));  // output: 17 17
Console.WriteLine(i);  // output: 17

Posted in IT, No Category, Programming by with comments disabled.