C# 11
Rainer Stropek | @rstropek@fosstodon.org | @rstropek
C# 11 Status
- C# 11 is final!
- Interesting reads
- Code snippets have been verified using sharplab.io
- See links on right bottom corner of slides
- Some code samples work with lates VS 2022 Preview
Newlines in Interpolations
using System;
using System.Linq;
var numbers = new[] { 1, 2, 3, 4, 5 };
Console.WriteLine($"{
numbers
.Where(n => n % 2 == 0)
.Select(n => n * n)
.Sum()
}");
Newline in Interpolations
File-local Types
using System.Numerics;
public class Math
{
private readonly Calculator<int> calc = new();
public int Add(int x, int y) => calc.Add(x, y);
}
// Note file-local type.
file class Calculator<T>
where T: IAdditionOperators<T, T, T>
{
public T Add(T x, T y) => x + y;
}
var m = new Math();
Console.WriteLine(m.Add(21, 21));
// The following line does not
// work as Calculator<T> is a
// file-local type
//var c = new Calculator<int>();
File-local Types
Pattern Matching Enhancements
List patterns! Finally... 😉
using System;
using System.Collections.Generic;
var numbers = new List<int>() { 1, 2, 3, 5, 8 };
// List pattern
if (numbers is [ 1, 2, 3, 5, 8 ])
{
Console.WriteLine("Fibonacci");
}
// Property pattern
if (numbers is [ var first, 2, 3, 5, var last ] && first == 1 && last == 8)
{
Console.WriteLine("Very special Fibonacci");
}
// Slice pattern
Console.WriteLine(numbers switch {
[ 1, .., var sl, 8 ] => $"Starts with 1, ends with 8, and 2nd last number is {sl}",
[ 1, .., var sl, > 8 or < 0 ] =>
$"Starts with 1, ends with something > 8 or < 0, and 2nd last number is {sl}",
[ 1, _, _, .. ] => "Starts with 1 and is at least 3 long",
[ 1, .. ] => "Starts with 1 and is at least 1 long",
_ => "WAT?"
});
List Patterns
using System;
using System.Collections.Generic;
var heroes = new List<Hero>
{
new("Superman", int.MaxValue),
new("The Tick", 10),
};
if (heroes is [ { MaxJumpDistance: > 1000 }, { MaxJumpDistance: < 100, Name: var snd } ])
{
Console.WriteLine($"First can fly, second ('{snd}') cannot jump very far");
}
class Hero
{
public string Name;
public int MaxJumpDistance;
public Hero(string name, int maxJumpDistance)
=> (Name, MaxJumpDistance) = (name, maxJumpDistance);
}
List Patterns Combined
ReadOnlySpan<char> span = "Homelander";
switch (span)
{
case "Starlight":
WriteLine("We have Starlight");
break;
case "The Deep":
WriteLine("We have The Deep");
break;
case "Homelander":
WriteLine("We have Homelander");
break;
default:
WriteLine("We have someone else");
break;
}
if (span is "Homelander")
{
WriteLine("We have Homelander");
}
Pattern Matching of Span<T>
Simplified Null Checking
We have to talk... 😁🤪🤮🤬
This feature was dropped!
#nullable enable
using System;
string BuildFullName(Person p)
{
// We compile with #nullable enable, so we rely on
// p and p.FirstName to not be null.
if (p.FirstName.Length == 0) {
return p.FirstName;
}
return $"{p.LastName}, {p.FirstName}";
}
var p = new Person("Foo", "Bar");
Console.WriteLine(BuildFullName(p));
// Pass null and supress nullable warning (shouldn't do that,
// but sh... sometimes happen).
Console.WriteLine(BuildFullName(null!));
record Person(string FirstName, string LastName);
Null Checking
#nullable enable
using System;
string BuildFullName(Person p)
{
// .NET 6
ArgumentNullException.ThrowIfNull(p);
// Prior
//if (p is null) { throw new ArgumentNullException(nameof(p)); }
if (p.FirstName.Length == 0) {
return p.FirstName;
}
return $"{p.LastName}, {p.FirstName}";
}
var p = new Person("Foo", "Bar");
Console.WriteLine(BuildFullName(p));
// Pass null and supress nullable warning (shouldn't do that,
// but sh... sometimes happen).
Console.WriteLine(BuildFullName(null!));
record Person(string FirstName, string LastName);
Null Checking
#nullable enable
using System;
string BuildFullNameWithCheck(Person p!!)
{
// We compile with #nullable enable, so we rely on
// p and p.FirstName to not be null.
if (p.FirstName.Length == 0) {
return p.FirstName;
}
return $"{p.LastName}, {p.FirstName}";
}
var p = new Person("Foo", "Bar");
Console.WriteLine(BuildFullName(p));
// Pass null and supress nullable warning (shouldn't do that,
// but sh... sometimes happen).
Console.WriteLine(BuildFullName(null!));
record Person(string FirstName, string LastName);
Null Checking
ArgumentNullException.ThrowIfNull(p);
Raw String Literals
""""""""""""""""""""😵💫""""""""""""""""""""
using static System.Console;
// This is how Hello World can look like in C#
{
var s = "System.Console.WriteLine(\n\t\"Hello World!\"\n);";
WriteLine(s);
}
Traditional String
using static System.Console;
{
var greet = "Hello World!";
var s = $"System.Console.WriteLine(\n\t\"{greet}\"\n);";
WriteLine(s);
}
String Interpolation
using static System.Console;
{
var s = @"System.Console.WriteLine(
""Hello World!""
);";
WriteLine(s);
}
Verbatim String Literals
verbatim = "wortwörtlich" in German
using static System.Console;
{
var greet = "Hello World!";
var s = @$"System.Console.WriteLine(
""{greet}""
);";
WriteLine(s);
}
Verbatim String Interpolation
using static System.Console;
{
var s = "using static System.Console;\nnamespace Demo\n{\n\tpublic class Program\n\t{\n\t\tpublic void Main()\n\t\t{\n\t\t\tWriteLine(\"Hello World!\");\n\t\t}\n\t}\n}";
WriteLine(s);
}
Escaping
using static System.Console;
{
var s = @"using static System.Console;
namespace Demo
{
public class Program
{
public void Main()
{
WriteLine(""Hello World!"");
}
}
}";
WriteLine(s);
}
Multi-line Verbatim String
using static System.Console;
{
// Note: First and last newline are ignored
// Be careful when mixing tabs and spaces. Shouldn't to that.
var s = """
using static System.Console;
namespace Demo
{
public class Program
{
public void Main()
{
WriteLine("Hello World!");
}
}
}
""";
WriteLine(s);
}
Raw String Literal
using static System.Console;
{
WriteLine(""""
The new Raw String Literal feature is great:
System.Console.WriteLine("""
Hello!
""");
"""");
}
Want more " 🤪
using static System.Console;
{
var greet = "Hello!";
WriteLine($""""
The new Raw String Literal feature is great:
System.Console.WriteLine("""
{greet}
""");
"""");
}
Raw String Literal Interpolation
using var conn = new SqlConnection("Server=(localdb)\\mssqllocaldb;Database=master;Trusted_Connection=True;");
await conn.OpenAsync();
const string query = """
SELECT TABLES.TABLE_NAME as Name
FROM INFORMATION_SCHEMA.TABLES
ORDER BY TABLES.TABLE_NAME
""";
Console.WriteLine(query);
var tabs = await conn.QueryAsync<Table>(query);
WriteLine(JsonSerializer.Serialize(tabs, new JsonSerializerOptions { WriteIndented = true }));
Raw String Literal Interpolation
Generic Attributes
#nullable enable
using System;
using System.Linq;
[AttributeUsage(System.AttributeTargets.Class)]
class MyVersionAttribute<T> : Attribute
{
public T Version { get; }
public MyVersionAttribute(T version)
{
Version = version;
}
}
[MyVersion<int>(42)]
class A { }
[MyVersion<string>("4.2")]
class B { }
Generic Attributes
#nullable enable
using System;
using System.Linq;
MyVersionAttribute<T>? GetVersion<T>(Type t)
{
return t.GetCustomAttributes(false)
.OfType<MyVersionAttribute<T>>()
.FirstOrDefault();
}
Console.WriteLine($"The version is {GetVersion<int>(typeof(A)).Version}");
Console.WriteLine($"The version is {GetVersion<string>(typeof(B)).Version}");
Console.WriteLine($"The version is {GetVersion<int>(typeof(B))?.Version ?? -1}");
Generic Attributes
Other String
Enhancements
// C# will allow conversions between string constants and byte sequences
// where the text is converted into the equivalent UTF8 byte representation.
byte[] array = "hello"u8.ToArray(); // new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f }
ReadOnlySpan<byte> rospan = "cat"u8;// new byte[] { 0x63, 0x61, 0x74 }
// New u8 suffix.
var s2 = "hello"u8; // Okay and type is ReadOnlySpan<byte>
UTF8 String Literals
ReadOnlySpan<char> span = "Homelander";
// Pre-C# 11
switch (span)
{
case var _ when span == "Starlight":
WriteLine("We have Starlight");
break;
case var _ when span == "The Deep":
WriteLine("We have The Deep");
break;
case var _ when span == "Homelander":
WriteLine("We have Homelander");
break;
default:
WriteLine("We have someone else");
break;
}
ReadOnlySpan Pattern Matching
ReadOnlySpan<char> span = "Homelander";
// Now
switch (span)
{
case "Starlight":
WriteLine("We have Starlight");
break;
case "The Deep":
WriteLine("We have The Deep");
break;
case "Homelander":
WriteLine("We have Homelander");
break;
default:
WriteLine("We have someone else");
break;
}
if (span is "Homelander")
{
WriteLine("We have Homelander");
}
ReadOnlySpan Pattern Matching
Enhanced
nameof
nameof for Parameters
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
var x = Path.GetFileName(null);
Console.WriteLine(x.ToUpperInvariant());
public class Path
{
[return: NotNullIfNotNull(nameof(path))]
public static String? GetFileName(string? path) { /* ... */ return path; }
}
using System.Runtime.CompilerServices;
var x = 5;
Verify.Lower(x * 2, Convert.ToInt32(Math.Floor(Math.PI)));
public static class Verify
{
public static void Lower(int argument, int maxValue,
[CallerArgumentExpression(nameof(argument))] string? argumentExpression = null,
[CallerArgumentExpression(nameof(maxValue))] string? maxValueExpression = null)
{
if (argument > maxValue)
{
throw new ArgumentOutOfRangeException(nameof(argument),
$"{argumentExpression} must be lower or equal {maxValueExpression}");
}
}
}
Enhanced nameof Scope
Required
Members
Required Members
// The following line will not work because Age initialization is missing
//var p1 = new Person("Foo", "Bar");
var p1 = new Person("Foo", "Bar") { Age = 42 };
Console.WriteLine(p1);
record Person(string FirstName, string LastName)
{
// Note: Record properties will be added to default ToString impl.
// Note: Required members need to be setable (set or init).
public required int Age { get; init; }
}
Drawbacks
- Currently, OmniSharp (VSCode) has problems with required
- Will probably go away in the near future
-
SetsRequiredMembers can not specify which members are set in ctor
- ctor must initialize all required members
Demo
Time!
Generic Math,
Generic Parsable
The Road to Generic Math
- C# 10: Static abstract members in interfaces
- Important prerequisite for generic math
- C# 10/.NET 6: Generic math preview
- Becomes stable with C# 11/.NET 7
- ⚠️ Breaking changes
- ⚠️ Breaking changes
-
System.Numerics 🔗
- Relevant for building custom math data types
- Overview of available APIs 🔗
- System.IParsable, System.ISpanParsable
Demo
Time!
Checked/Unchecked
- C# supports checked/unchecked regions for overflow/underflow checking
- Until .NET 7, customer operators could not plug into this mechanism
- This limitations has been lifted
// ...
public static Container operator checked +(Container c, item i)
{
return new(checked(c.value + i));
}
// ...
Interested in more?
C# 11 🤘
Rainer Stropek | @rstropek
C# 11
By Rainer Stropek
C# 11
C# 11 is still a moving target, but the current preview already allows us to take a glimpse into what is coming. This presentation gives an overview of the current state of potential new language features coming in C# 11. It links to a collection of code samples that you can try out on sharplab.io.
- 1,863