C# Logo

C# 9.0 went to General Availability on the 10th of Nov 2020.

I’ve listed below the parts which I like and have decided to do this blog post early and add to it as updates happen and I learn more.

Super interesting times!

C# 9.0 on the record by Mads Torgersen is the official release post of C# 9.0

Visual Studio 16.8 includes .NET 5.0 - notice the rename drop of Core from .NET Core 3.1

Top-level programs / statements

MS Blog link

R# giving my red squiggles so I have to get R# Early Access Program with the version on the 11th of Nov 2020 being R# 2020.3 EAP 6

JetBrains blog link

using System;

// very nice!
Console.WriteLine("Hello World!");

The compiler creates a Program class with an entry point on Main under the hood.

  • Only 1 entry point per project is allowed
  • async / await is allowed
  • types at the bottom

I’m doing bits of ETL quite often - lets see if it can help.

using System;
using System.Collections.Generic;
using Dapper;
using System.Data;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Linq;
using CsvHelper;

using var db = GetOpenConnection();

// Clear down db first (using Dapper)
db.Execute("DELETE FROM Actors");

// 1.Extract Actors
var actors = LoadActorsFromCsv();
Console.WriteLine($"Total Actors imported from csv is {actors.Count}"); // 98,690

// 2.Load
foreach (var actor in actors.Take(50))
{
    var sql = @"
        INSERT Actors
        VALUES (@actorid, @name, @sex)";

    db.Execute(sql, actor);
}

// a test async query
var someActors = await db.QueryAsync<Actor>(@"
    SELECT TOP 10 * 
    FROM Actors 
    ORDER BY Name DESC");

foreach (var someActor in someActors) 
    Console.WriteLine($"{someActor.actorid} {someActor.name} {someActor.sex}");

Console.WriteLine("Done");
    
// local function using CsvHelper to read a csv
List<Actor> LoadActorsFromCsv()
{
    using var reader = new StreamReader("..\\..\\..\\..\\data\\actors.csv");
    using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
    csv.Configuration.Delimiter = ";";
    return csv.GetRecords<Actor>().ToList();
}

// a local function to help Dapper
IDbConnection GetOpenConnection()
{
    //var connStrng = @"Server=.\;Database=IMDBChallenge;Trusted_Connection=True;MultipleActiveResultSets=true";
    var connStrng = @"Server=(localdb)\mssqllocaldb;Database=IMDBChallenge;Trusted_Connection=True;MultipleActiveResultSets=true";
    var connection = new SqlConnection(connStrng);
    return connection;
}

// types must be defined at the bottom of the file
class Actor
{
    // favouring the simplest data type string in this load
    // until I understand the data (ie what edge cases are there)
    // yikes - naming convention need to refactor!
    public string actorid { get; set; }
    public string name { get; set; }
    public string sex { get; set; }
}

Source code for this project is here

Shorter code, much more readable. I like it.

Init-only properties

This code will error: Error CS8852 Init-only property or indexer 'Person.LastName' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

var person = new Person { FirstName = "Mads", LastName = "Nielsen" };
person.LastName = "Torgersen"; // Error 

public class Person
{
    public string? FirstName { get; init; }
    public string? LastName { get; init; }
}

Init-only properies protect the state of the object from mutation once initialisation is finished.

Records

Mads blog post

Here is the with expression that creates a copy of the object with a new property

var person = new Person { FirstName = "Mads", LastName = "Nielsen" };
// with expression
var otherPerson = person with { LastName = "Torgersen" };

public record Person
{
    public string? FirstName { get; init; }
    public string? LastName { get; init; }
}

Positional record construction:

var person = new Person(FirstName: "Mads", LastName: "Nielsen"); // positional construction
Console.WriteLine(person); // nice override of ToString
var personB = new Person("Mads", "Nielsen"); // don't need argument names
//Error CS8852	Init-only property or indexer 'Person.LastName' can only be assigned in an object initializer,
//or on 'this' or 'base' in an instance constructor or an 'init' accessor.	
//person.LastName = "asdf";

var (f, l) = person; // positional deconstruction

// value-ness based equality
// ie if all fields are equal then the 2 objects are considered equal
if (person == personB) Console.WriteLine("they are equal");

var otherPerson = person with { LastName = "Torgersen" };
if (person == otherPerson) Console.WriteLine("they are equal");

// positional record
// init-only properly under the hood
// notice capitals
public record Person(string FirstName, string LastName);

Steve Gordon blogpost

So what are my use cases for Records?

I’ve written another article, exploring where to use Records, along with the C# 8.0 feature Nullable Reference Types (NRT) and .NET 5 Razor pages. However currently I’m favouring not using NRT’s for ViewModel code, and Positional Records don’t pick up Attributes for Razor pages, so not using them for ViewModel data. See here for more information.

Target typed new expression

Interesting syntax, but am not sure will be using it. Possibly useful in newing up a Property.

// Actors is always going to be a List, possibly empty, never null
public List<Actor> Actors { get; set; } = new();

// I stil prefer the old syntax
 //var things = new List<Actor>();
List<Actor> things = new();

Deploy to *nix

todo - explore whether the apt package repositories have been updated for Ubuntu 18.04 etc.

Conclusion

I’ve updated my main machines to VS 16.8 and .NET 5.0 with no problems as yet. Impressive.