C# 9.0
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
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
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
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);
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.