C#8.0

using declarations

A using declaration is a variable declaration preceded by the using keyword. It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope.

static void WriteLinesToFile(IEnumerable<string> lines)
{
    using var file = new System.IO.StreamWriter("WriteLines2.txt");
    foreach (string line in lines)
    {
       file.WriteLine(line);       
    }
    // file is disposed here
}

This is equivalent to the old way of disposing object by using brackets around

using (var file = new System.IO.StreamWriter("WriteLines2.txt"))
{
}

Static local functions

You can now add the static modifier to local functions to ensure that local function doesn't capture (reference) any variables from the enclosing scope. Doing so generates CS8421, "A static local function can't contain a reference to.

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right; //This is good.
    
    static void LocalFunction() => y = 0; //This is bad.
//Above line Generates error: "A static local function can't contain a reference to.

}

Nullable reference types

C# 8 introduces nullable reference types, which complement reference types the same way nullable value types complement value types. You declare a variable to be a nullable reference type by appending a ? to the type.

Asynchronous streams

Starting with C# 8.0, you can create and consume streams asynchronously. A method that returns an asynchronous stream has three properties:

  • It's declared with the async modifier.
  • It returns an IAsyncEnumerable<T>.
  • The method contains yield return statements to return successive elements in the asynchronous stream.
public static async IAsyncEnumerable<int> GenerateSequence()
{
    for (int i = 0; i < 20; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

Consuming an asynchronous stream as generated by above method requires you to add the await keyword before the foreach keyword when you enumerate the elements of the stream.

await foreach (var number in GenerateSequence())
{
    Console.WriteLine(number);
}

As you have already guessed it, adding the await keyword requires the method that enumerates the asynchronous stream to be declared with the async modifier and to return a type allowed for an async method. Typically that means returning a Task or Task<TResult>

Indices and ranges

var words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};
  • words[^1] fetched dog
  • words[1..4] creates a subrange with the words "quick", "brown", and "fox"
  • words[^2..^0] creates a subrange with "lazy" and "dog"
  • words[..] fetches "The" through "dog"
  • words[..4] fetches "The" through "fox"
  • words[6..] fetches "the, "lazy" and "dog"

last but not least

declare a range and use it.

Range phrase = 1..4 
var text = words[phrase];