rksoftware

Visual Studio とか C# とかが好きです

C# 2.0 以降の新機能まとめ (C# 7.0 ~ 9.0)

 C# 2.0 以降の新機能の名前と公式ガイドページへのリンクをまとめました。(C# 7.0 ~ )

 注1)機能の名前はできるだけ公式ガイドから言葉を拾うようにしましたが、完全ではありません。
 注2)リンク先ページはできるだけ日本語ページを拾うようにしましたが、見つけられずに英語のページもあります。
 注3)良さそうなページを探しましたが、もっと良いページがあるかもしれません。

■ C# 7.0 での新機能

・out 変数をメソッド呼び出しの引数リスト内で宣言 (Out variables)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/out-parameter-modifier
 out 引数を含むメソッドの呼び出しの際に、out 変数をメソッド呼び出しの引数リスト内で宣言できる。

string text = "10";
if (!int.TryParse(text, out int i))  // ここで変数 i が宣言できている
{
    Console.WriteLine($"{text} を int にパースできませんでした");
    return;
}
Console.WriteLine($"{text} を int: {i} にパースできました");  // 10 を int: 10 にパースできました

・タプル (Tuples)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/tuples
 複数の値を 1 つのオブジェクトに簡単にパッケージできる。

{   // 変数/定数をまとめられる
    string text;
    int length;
    (text, length) = ("saitama", 7);
    Console.WriteLine($"{text}, {length}"); // saitama, 7
}
{   // 変数宣言と同時でもよい
    (string text, int length) = ("saitama", 7);
    Console.WriteLine($"{text}, {length}"); // saitama, 7
}
{   // 型推論
    var (text, length) = ("saitama", 7);
    Console.WriteLine($"{text}, {length}"); // saitama, 7
}
{   // タプルの変数
    (string text, int length) t = ("saitama", 7);
    Console.WriteLine($"{t.text}, {t.length}"); // saitama, 7
}
{   // タプルの変数 (フィールド名なしでも Item1、Item2...で値にアクセス可能)
    (string, int) t = ("saitama", 7);
    Console.WriteLine($"{t.Item1}, {t.Item2}"); // saitama, 7
}
// メソッドの戻り値でも可能 (フィールド名付き/なし可能)
static (string, int) MethodA() => ("saitama", 7);
static (string text, int length) MethodB() => ("saitama", 7);

・分解 (Deconstruction)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/value-tuples#tuple-assignment-and-deconstruction
 タプルを"分解"し項目を展開した状態で、変数を作成できる。

static void Main(string[] args)
{
    // 分解していない書き方
    {
        var tuple = Method();
        Console.WriteLine($"{tuple.text}, {tuple.length}");     // text, 4
    }
    // 以降、分解している書き方
    {
        var (text, length) = Method();
        Console.WriteLine($"{text}, {length}");                 // text, 4
    }
    {
        (var text, var length) = Method();
        Console.WriteLine($"{text}, {length}");                 // text, 4
    }
    {
        (string text, int length) = Method();
        Console.WriteLine($"{text}, {length}");                 // text, 4
    }
}

static (string text, int length) Method()
{
    string text = "text";
    return (text: text, length: text.Length);
}

・パターンマッチ (Pattern matching)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/pattern-matching
 ifswitch で型を判断しての処理が簡潔に書ける。
 [ 注意 ] 後の言語バージョンで機能強化されています。このサンプルは C# 7.0 時点での仕様のサンプルです。

static void Main(string[] args)
{
    string textIs = IsPattern(10);
    Console.WriteLine(textIs);              // 引数は int の 10 です

    string textSwitch = SwitchPattern("10");
    Console.WriteLine(textSwitch);          // 引数は string の 10 です
}

static string IsPattern(object arg)
{
    if (arg is int i) { return $"引数は int の {i} です"; }
    if (arg is double d) { return $"引数は double の {d} です"; }
    if (arg is string s) { return $"引数は string の {s} です"; }
    return "unknown";
}

static string SwitchPattern(object arg)
{
    switch (arg)
    {
        case int i: return $"引数は int の {i} です";
        case double d: return $"引数は double の {d} です";
        case string s: return $"引数は string の {s} です";
        default: return "unknown";
    }
}

・参照返り値と参照ローカル変数 (Ref returns and locals)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/ref-returns
 値型の参照を返り値として返すことができる。

{   // 単純な値
    // 参照を受け取って参照を返す関数
    static ref int Method(ref int arg)
    {
        arg++;
        return ref arg;
    }

    int value = 10;

    // 関数内での変更が、引数で渡した変数と返却された値の両方を変化させている(同一の参照なので)
    ref int vr = ref Method(ref value);
    Console.WriteLine($"{value}, {vr}"); // 11, 11

    // 引数で渡していた変数への変更で返却された値も変化している(同一の参照なので)
    value = 20;
    Console.WriteLine($"{value}, {vr}"); // 20, 20

    // 返却された変数への変更で引数で渡していた変数も変化している(同一の参照なので)
    vr = 30;
    Console.WriteLine($"{value}, {vr}"); // 30, 30
}
{   // 配列の要素
    // 配列を受け取って最初の要素の参照を返す関数
    static ref int Method(int[] arg)
    {
        return ref arg[0];
    }

    int[] values = { 10, 20 };
    ref int sr = ref Method(values);
    Console.WriteLine($"{values[0]}, {sr}"); // 10, 10

    // 引数で渡していた配列の最初の要素への変更で返却された値も変化している(同一の参照なので)
    values[0] = 20;
    Console.WriteLine($"{values[0]}, {sr}"); // 20, 20

    // 返却された変数への変更で引数で渡していた配列の要素も変化している(同一の参照なので)
    sr = 30;
    Console.WriteLine($"{values[0]}, {sr}"); // 30, 30
}

・ローカル関数 (Local functions)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/local-functions
 メソッド内でメソッドを定義できる。

class LocalFunctionSample
{
    void NewStyle()
    {   // ローカル関数
        // 関数呼び出し
        func(0);
        // 呼び出しより後方に書ける
        void func(int v)
        {
            Console.WriteLine(++v);
            if (v < 3)
            {
                // 自分自身を再起呼び出しできる
                func(v);
            }
        }
    }

    void OldStyle()
    {   // 以前のスタイル
        // 自分自身を再起呼び出しできないので、変数が必要
        Action<int> func = null;
        // 変数に関数を代入
        func = v =>
        {
            Console.WriteLine(++v);
            if (v < 3)
            {
            // 変数で関数を再起呼び出し
            func(v);
            }
        };
        // 呼び出しより前方で関数を書く必要がある
        // 関数呼び出し
        func(0);
    }
}

・式形式のメンバーの追加 (More expression bodied members)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members
 コンストラクター、ファイナライザー、プロパティの settergetter、メソッドを式で定義できる。

class MyClass
{
    // コンストラクターを式で宣言可能
    internal MyClass(string value) => _value = value;
    // ファイナライザーを式で宣言可能
    ~MyClass() => Value = null;

    // プロパティのアクセサーを式で宣言可能
    private string _value;
    internal string Value
    {
        get => _value;
        set => _value = value;
    }

    // メソッドを式で宣言可能
    public override bool Equals(object obj) => obj is MyClass c && this.Value == c.Value;
    public override int GetHashCode() => base.GetHashCode();
}

・throw 式 (Throw expressions)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/throw#the-throw-expression
 throw を式およびステートメントとして使用できる。

class MyClass
{
    // コンストラクタ
    internal MyClass(string value) => _value = value ?? throw new ArgumentException();

    // プロパティ
    private string _value;
    internal string Value
    {
        get => _value;
        set => _value = value ?? throw new ArgumentException();
    }

    // メソッド
    internal string GetValue() => throw new ArgumentException();
    internal void SetValue(string value) => _value = value ?? throw new ArgumentException();
}

・一般的な型での非同期メソッドの戻り値 (ValueTask)(Generalized async return types)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/async/async-return-types#generalized-async-return-types-and-valuetasktresult
 async 非同期メソッド内で非同期処理が必要である場所を通らなかった場合、Task オブジェクトを作らない事ができる。

static void Main(string[] args)
{
    int a = Func(true).Result;  // 非同期処理を通らないパターン
    int b = Func(false).Result; // 非同期処理を通るパターン
}

static async ValueTask<int> Func(bool b)
{
    if (b) return 0;
    await Task.Delay(1).ConfigureAwait(false);
    return 1;
}

・数値リテラルの表記の改善 (Literal improvements)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-7#numeric-literal-syntax-improvements
 2進数表記でのリテラルを記述できる。数値リテラルの桁数などをわかりやすく記述できる。

int nenshu = 50_000_000;
Console.WriteLine($"年収 {nenshu} 億円");

int shoyo = 0b0100_0110_0101;
Console.WriteLine($"賞与 {shoyo} 万円");

■ C# 7.1 での新機能

・非同期 Main メソッド
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/main-and-command-args/#overview
 Main メソッドを async にできます。

static async Task Main(string[] args)
{
    var text = await new System.Net.Http.HttpClient().GetStringAsync("http://rksoftware.hatenablog.com/");

    Console.WriteLine(new string(text.Take(200).ToArray()));
}

・ジェネリックによるパターン マッチング
 https://docs.microsoft.com/ja-jp/dotnet/csharp/pattern-matching
 ※リンクは掲載していますが、この機能についての言及はありません。言及のあるページは将来なくなりそうなページのため掲載していません。
 ジェネリック型の引数に対してのパターンマッチングが可能になりました。以前は、下記の例では、Match メソッドの引数 a に対するパターンマッチングはコンパイルエラーでした。

static async Task Main(string[] args)
{
static void Main(string[] args)
{
    Console.WriteLine(Match("test"));   // string
}

static string Match<T>(T a)
{
    switch (a)
    {
        case int v:
            return "int";
        case string v:
            return "string";
        default:
            return "default";
    }
}

・“既定” リテラル
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/default#default-literal
 型を推論できる個所では、規定値を設定する際の default(型名) の型名を省略できるようになりました。

static void Main(string[] args)
{
    // これまでは default(int) の様に型の指定が必要だった
    int i = default;
    Console.WriteLine(i);

    // 戻り値の型から推論可能
    Console.WriteLine(D());

    // 戻り値の型から推論可能
    Func<string> l = () => default;
    Console.WriteLine(l() == null);

    // 変数の型から推論可能
    string s = default;
    // ?? でも
    Console.WriteLine(s ?? default);
    // 三項演算でも
    Console.WriteLine(s == null ? default : s);
}

static DateTime D()
{
    // 戻り値の型から推論可能
    return default;
}

・推定タプル名
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/value-tuples#tuple-field-names
 タプルの使用時に、要素の名前が推定で付けられるようになりました。推定できない場合(一つの変数が複数回使われた場合など)はこれまで通り、ItemX という名前になります。

var pv = new System.Collections.Generic.KeyValuePair<int, int>(1, 2);
var t1 = (pv.Value, pv.Key);  // ここで右辺で書いているプロパティ名がタプルの要素の名前になる

// これまでは t.Item1 や t.Item2 となっていた
Console.WriteLine(t1.Key);
Console.WriteLine(t1.Value);

int x = 3, y = 4;
var t2 = (x, y);    // ここで右辺で書いている変数名がタプルの要素の名前になる

Console.WriteLine(t2.x);
Console.WriteLine(t2.y);

■ C# 7.2 での新機能

・Span 構造体
 https://docs.microsoft.com/ja-jp/dotnet/standard/memory-and-spans/memory-t-usage-guidelines
 配列(など)の一部の範囲を切り出して値の参照を持てる。

var ary = Enumerable.Range(1, 10).ToArray();
var span = new Span<int>(ary, 2, 3);
var readonlySpan = (ReadOnlySpan<int>)span;
for (int i = 0; i < readonlySpan.Length; i++)
    Console.WriteLine(readonlySpan[i]);
// 3
// 4
// 5 と出力される
span[0] = 11;
Console.WriteLine(readonlySpan[0]); // コピーでなく参照を持っているため 11 と出力される
// readonlySpan[0] = 21; ← ReadOnlySpan<T> なのでこれはエラー
ary[2] = 31;
Console.WriteLine(readonlySpan[0]); // コピーでなく参照を持っているため  31 と出力される

・readonly 構造体
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/struct#readonly-struct
 ・readonly struct は readonly なメンバーしか持てない
 ・保持する値が変更されない(値が変更可能になる定義が禁止される)

readonly struct SA
{
    public readonly int PA;
    // public int PB; ← readonly 構造体なので これはエラー
    public SA(int a)
    {
        PA = a;
    }
    // public void Set(SA a) { this = a; } ← readonly 構造体なので これはエラー
}

struct SB
{
    public readonly int PA;
    public int PB; // ← readonly 構造体でないので これは OK
    public SB(int a, int b)
    {
        PA = a;
        PB = b;
    }
    public void Set(SB a) { this = a; } // ← readonly 構造体でないので これは OK
}

・パラメーターの in 修飾子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/in-parameter-modifier
 引数を読み取り専用にできる。

// in 引数を持つ関数
void Func(St stNotIn, in St stIn)
{
    stNotIn = new();        // in 引数でないので変数を変更可能
    stNotIn.Val = new();    // in 引数でないのでフィールド変更可能
    // stIn = new();        // これはエラー in 引数は変数を変更できない
    // stIn.Val = new();    // これはエラー in 引数はフィールドを変更できない
}

// 構造体
struct St { public int Val; }

・メソッド戻り値の ref readonly 修飾子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/ref#reference-return-values
 読み取り専用の参照を戻り値にできる。

var val = 0;
// ref var rval =  ref Func(ref val);       ← ref readonly 戻り値は ref readonly でないとエラー
ref readonly var rval = ref Func(ref val);
// rval = 10;                               ← ref readonly 変数を変更するコードはエラー

// ref readonly 戻り値の関数
ref readonly int Func(ref int val) { return ref val; }

・private protected アクセス修飾子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/private-protected
 同アセンブリ内の派生クラスでのみアクセスできる。
 次の例では二つの名前空間は別アセンブリにあるとします。

namespace ClassLibrary1
{
    // private protected メソッドを持つクラス
    public class Class1
    {
        private protected void MyMethod() {; }
    }

    // 同アセンブリ内のクラス
    public class Class3 : Class1
    {
        void MyMethod2() { base.MyMethod(); } // ← 同アセンブリ内の派生クラスは OK
    }
}
// 別アセンブリ
namespace AnotherAssemblyClass
{
    // 別アセンブリのクラス
    class AnotherAssemblyClass : ClassLibrary1.Class1
    {
        void MyMethod3()
        {
            // base.MyMethod(); ← これはエラー
        }
    }
}

・末尾以外の名前付き引数
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#named-arguments
 引数の順番とあっている位置であれば、より後方に名前付きでない引数があっても名前付き引数にできる。

MyMethod(1, 2, c: 3, 4);         // ← c の位置が順番とあっているので OK
MyMethod(1, 2, d: 3, c: 4);      // ← 名前付きが後方に集まっているので OK
// MyMethod(1, c: 2, b: 3, 4);  // ← c、b の位置が順番とあっていないので NG
MyMethod(d: 1, c: 2, b: 3, a: 4);  // ← 全て名前付きなので OK
// MyMethod(1, c: 2, b: 3, a: 4); // ← a の位置は既に値が書かれている (1) ので NG}

static void MyMethod(int a, int b, int c, int d) { ; }

・0b または0x の後に桁区切り記号を使用する
 https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-7#numeric-literal-syntax-improvements
 0x0b の直後に _ を書けるようになった。

// C# 7.1 で書ける
const int A = 0x1_2345;
const int B = 0b1_0101;

// C# 7.2 以降でないとエラー
const int CS72A = 0x_1_2345;
const int CS72B = 0b_1_0101;

・ref 条件式
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/conditional-operator#conditional-ref-expression
 三項演算子で参照(ref)を返せるようになった。

{ // 変数 a と c は同じなので、a への代入で c の出力も変わる
    var (a, b) = (10, 20);
    ref var c = ref (true ? ref a : ref b);
    (a, b) = (11, 21);
    Console.WriteLine($"a:{a} b:{b} c:{c}");    // a: 11 b: 21 c: 11 と出力される
}
{ // 変数 b と c は同じなので、b への代入で c の出力も変わる
    var (a, b) = (10, 20);
    ref var c = ref (false ? ref a : ref b);
    (a, b) = (11, 21);
    Console.WriteLine($"a:{a} b:{b} c:{c}");    // a: 12 b: 21 c: 21 と出力される
}
{ // 参照が返っているので、変数 a の値を直接変更できる
    var (a, b) = (10, 20);
    (true ? ref a : ref b) = 13;
    Console.WriteLine($"a:{a} b:{b}");    // a: 13 b: 20 と出力される
}
{ // 参照が返っているので、変数 b の値を直接変更できる
    var (a, b) = (10, 20);
    (false ? ref a : ref b) = 24;
    Console.WriteLine($"a:{a} b:{b}");    // a: 10 b: 24 と出力される
}

・拡張メソッドの最初の引数では、その引数が構造体でない限り、in 修飾子を使用することはできません
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/in-parameter-modifier#limitations-on-in-parameters
 拡張メソッドの this 引数を参照にできる。拡張メソッドの最初の引数では、その引数が構造体でない限り、in 修飾子を使用することはできない。

struct St { }
class Cl { }

static class Extensions
{
    public static void St(in this St st) { ; }
    // public static void Cl(in this Cl cl) { ; } ← class の場合は in 指定できない
}

■ C# 7.3 での新機能

・ピン留めを使用せずに fixed フィールドにアクセスできます
 [ 良いリンクを見つけられませんでした ]
 fixed フィールドへのアクセスで fixed が不要になった。

unsafe struct UnsafeStruct
{
    public fixed int ExiedField[1];
}

class Program
{
    static UnsafeStruct _unsafeStruct;

    unsafe static void Main()
    {
        // 以前はこんな感じ
        fixed (int* ptr = _unsafeStruct.ExiedField)
        {
            int value = ptr[0];
        }

        // C# のバージョンを低く設定すると
        // エラー CS8024  機能 '移動可能な固定バッファーのインデックス化' は C# X では使用できません。7.3 以上の言語バージョンをお使いください。
        {
            int value = _unsafeStruct.ExiedField[0];
        }
    }
}

・ref 再代入
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/ref-returns#ref-returns-and-ref-locals-an-example
 ref ローカル変数を再割り当てできる。

var i = 1;
ref var v = ref i;

// C# のバージョンが低いと
// エラー CS8107    機能 'ref 再代入' は C# X では使用できません。7.3 以上の言語バージョンをご使用ください。
v = ref i;

・stackalloc 初期化子
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/stackalloc
 stackalloc 配列で初期化子を使用できる。

// C# のバージョンが低いと
// エラー CS8107  機能 'stackalloc 初期化子' は C# 7.0 では使用できません。7.3 以上の言語バージョンをご使用ください。
Span<int> i = stackalloc int[] { 1, 2, 3 };

・拡張可能な fixed ステートメント
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/fixed-statement
 GetPinnableReference という名前のメソッドを実装する型はピン留めできる。

struct Struct
{
    int[] _value;
    public Struct(int[] value) => _value = value;
    public ref int GetPinnableReference() => ref _value[0];
}

class Program
{
    unsafe static void Main()
    {
        // C# のバージョンを低く設定すると
        // エラー CS8107  機能 '拡張可能な fixed ステートメント' は C# X では使用できません。7.3 以上の言語バージョンをご使用ください。
        fixed (int* ptr = new Struct(new []{ 10, 20 }))
        {
            Console.WriteLine(ptr[0]);
            Console.WriteLine(ptr[1]);
        }
    }
}

・アンマネージド制約
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#unmanaged-constraint
 ジェネリックの型パラメータの制約で unmanaged を制約できる。

// where T : unmanaged がない場合、
// エラー CS0208    マネージ型 ('T') のアドレスの取得、サイズの取得、またはそのマネージ型へのポインターの宣言が実行できません
unsafe static int Method<T>() where T : unmanaged => sizeof(T);

・delegate ジェネリック型の制約
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#delegate-constraints
 ジェネリックの型パラメータの制約で delegate を制約できる。

// C# のバージョンを低く設定すると
// エラー CS8320  機能 'delegate ジェネリック型の制約' は C# X では使用できません。7.3 以上の言語バージョンをお使いください。
unsafe static System.Delegate Method<T>(T arg) where T : System.Delegate => arg;

・enum ジェネリック型の制約
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#delegate-constraints
 ジェネリックの型パラメータの制約で enum を制約できる。

// C# のバージョンを低く設定すると
// エラー CS8320  機能 'enum ジェネリック型の制約' は C# X では使用できません。7.3 以上の言語バージョンをお使いください。
unsafe static System.Enum  Method<T>(T arg) where T : System.Enum => arg;

・タプルの等値性
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/value-tuples#tuple-equality
 タプルで == と != が使える。

Console.WriteLine((1, 2) == (1, 2));  // True
Console.WriteLine((1, 2) == (1, 3));  // False

Console.WriteLine((1, 2) != (1, 2));  // False
Console.WriteLine((1, 2) != (1, 3));  // True

・メンバー初期化子とクエリ内の式変数の宣言
 https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-7#out-variables
 フィールド初期化子、プロパティ初期化子、.コンストラクター初期化子、クエリ句で式変数 を含む式が許可される。

// C# のバージョンを低く設定すると
// エラー CS8107  機能 'メンバー初期化子とクエリ内の式変数の宣言' は C# X では使用できません。7.3 以上の言語バージョンをご使用ください。

public class A
{
    public A(out int v) => v = default;

    // フィールド初期化子
    int F = int.TryParse("1", out var v) ? v : default;

    // プロパティ初期化子
    int P { get; set; } = int.TryParse("1", out var v) ? v : default;
}

public class B : A
{
    // コンストラクター初期化子
    public B(int i) : base(out var v) => Console.WriteLine(v);
}

・自動実装プロパティに属性をアタッチする
 https://docs.microsoft.com/ja-jp/dotnet/csharp/properties#attaching-attributes-to-auto-implemented-properties
 自動実装プロパティのバッキング フィールドにフィールド属性つけられる。  

class Class
{
    // C# のバージョンを低く設定すると
    // 警告 CS8371  自動プロパティ上でフィールドをターゲットとする属性を使用することは、言語バージョン X ではサポートされていません。7.3 以上の言語バージョンをお使いください。
    [field: NonSerialized]
    public int MyProperty { get; set; }
}

・オーバーロードの解決のあいまいなケースが削減されました
 [ 良いリンクを見つけられませんでした ]
 同名のインスタンス メソッドと静的メソッドとの解決/同名の型パタメーターの制約違いのメソッドの解決/引数のデリゲートの戻りの型違いのメソッドの解決が改善した。

class A
{
    static void MethodA(int i) {; }
    void MethodA(long l) {; }

    void MethodB<T>(T t, int i = 0) where T : class {; }
    void MethodB<T>(T t, long l = 0) where T : struct {; }

    void MethodC(Func<int> a) {; }
    void MethodC(Func<long> a) {; }
    int MethodD() => 1;
    long MethodE() => 1L;

    void Main()
    {
        // **インスタンス メンバーと静的メンバー**
        // C# のバージョンを低く設定すると
        // エラー CS0176  インスタンス参照でメンバー 'A.Method(int)' にアクセスできません。代わりに型名を使用してください
        // 希望としてはインスタンスメンバーの 'Method(long)' になってほしい
        this.MethodA((short)1);

        // **型引数が制約を満たしていない複数のジェネリック メソッド**
        // C# のバージョンを低く設定すると
        // エラー CS0121  次のメソッドまたはプロパティ間で呼び出しが不適切です: 'A.MethodB<T>(T, int)' と 'A.MethodB<T>(T, long)'
        // 希望としては 'MethodB<T>(T t, long l = 0) where T : struct' になってほしい
        MethodB(1);

        // **戻り値の型がデリゲートの戻り値の型と一致しない**
        // C# のバージョンを低く設定すると
        // エラー CS0121  次のメソッドまたはプロパティ間で呼び出しが不適切です: 'A.MethodC(Func<int>)' と 'A.MethodC(Func<long>)'
        // 希望としては 'A.MethodC(Func<long>)' になってほしい
        MethodC(MethodE);
    }
}

■ C# 8.0 での新機能

・構造体型の readonly インスタンス メンバー
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/struct#readonly-instance-members
 構造体のメソッドに readonly をつけられる。(クラスにはつけられない)

public readonly override string ToString() => "";

 プロパティの値を変更するコードはエラーになる。

public struct Point
{
    public double X { get; set; }

    public readonly override string ToString()
    {
        X += X;  // エラー CS1604 読み取り専用であるため 'X' に割り当てできません
        return "";
    }
}

 readonly メソッドの中で、readonly でない getter を呼ぶと警告になる。※自動実装プロパティの getter は readonly なので大丈夫

public struct Point
{
    double _x; public double X { get => _x; set => _x = value; }

    public readonly override string ToString()
    {
        Console.WriteLine(X);  // 警告 CS8656 'readonly' メンバーから readonly 以外のメンバー 'Point.X.get' を呼び出すと、'this' の暗黙のコピーが生成されます。
        return "";
    }
}

・既定のインターフェイス メソッド
 https://docs.microsoft.com/ja-jp/dotnet/csharp/tutorials/default-interface-methods-versions
 インターフェイスに実装がもてる。

// 実装を持ったインターフェイス
interface IA
{
    int Method1();
    int Method2() => 2;  // 2 を返す実装のメソッド
}

// 実装を持ったインターフェイスを実装するクラス
class A : IA
{
    public int Method1() => 1;
    // インターフェイスに実装がある Method2 はクラスで実装しなくてもよい (してもよい)
}

 クラスで実装しない場合は、Method2 はインターフェイスの型の変数でないと使えない。

var a = new A();
Console.WriteLine($"Method1 = {a.Method1()}, Method2 = {a.Method2()}");  // ← クラスで実装していないためエラー

// 実装を持っているインターフェイスの型にすれば使える
var ia = (IA)a;
Console.WriteLine($"Method1 = {ia.Method1()}, Method2 = {ia.Method2()}");

 クラスで実装すると常にクラスの実装が使われる。

var a = new A();
// どちらも 1 が出力される
Console.WriteLine($"Method2 = {a.Method2()}");
Console.WriteLine($"Method2 = {((IA)a).Method2()}");

interface IA { int Method2() => 2; }

class A : IA { public int Method2() => 1; }

・switch 式
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/switch-expression
 式である switch が追加になった。以前からあった switch ステートメントとは見た目もだいぶ違います。switch 式は式なので結果を返しますし、値を変数に受けられます。

var name = "saitama";
// switch の結果を変数に代入している
var val = name switch
{
    "gunma" => 1,
    "saitama" => 2,
    { } => 3,
    null => 4,
};

Console.WriteLine(val);  // 2

・プロパティ パターン (パターンマッチングの強化)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/tutorials/pattern-matching#add-occupancy-pricing
 プロパティの値でマッチできる。一部のプロパティだけも可能。
 この機能でようやくパターンマッチングという名前で想像される機能となったのではないでしょうか?

var point = new Point(x: 1, y: 2);
var x = point switch
{
    Point { X: 0, Y: 2 } => 0,  // X が 0、Y が 2 の場合にマッチする
    Point { X: 2, Y: 3 } => 2,  // X が 2、Y が 3 の場合にマッチする
    Point { X: 1, Y: _ } => 1,  // _ はワイルドカード。何にでもマッチする。X が 1、Y は何であってもマッチする
    _ => -1            // default。それまでのどの条件にもマッチしない場合にマッチする。
};

// 1 が出力される
Console.WriteLine(x);

・タプル パターン (パターンマッチングの強化)
 https://docs.microsoft.com/ja-jp/dotnet/csharp/tutorials/pattern-matching#add-peak-pricing
 パターンマッチングの値も条件もタプルにできる。

string first = "saitama", second = "gunma";
var val = (first, second) switch
{
    ("saitama", "ibaraki") => "ibaraki",
    ("chiba", "gunma") => "chiba",
    ("saitama", "gunma") => "大都会",
    _ => "No",
};
Console.WriteLine(val);  // "大都会" と表示される

・位置指定パターン
 [ 良いリンクを見つけられませんでした ]
 分解結果のタプルで switch できる。

var point = new Point { X = 1, Y = 2 };
var val = point switch  // 分解を条件にしている
{
    var (x, y) when x == 0 && y == 1 => x,
    (1, 2) => 3,
    var (x, y) when x == 2 && y == 3 => x,
    _ => -1
};
Console.WriteLine(val);  // 3 と表示される

// 分解のあるクラス
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
    // 分解
    public void Deconstruct(out int x, out int y) =>
        (x, y) = (X, Y);
}

・using 宣言
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/using-statement
 変数の宣言に using をつけます。するとその変数の生存範囲が終わったところで Dispose される。

// ブロックを抜けたところで Dispose される
{
    using var disposable = new Disposable();
    Console.WriteLine("1");
}
Console.WriteLine("2");
// 1
// Dispose
// 2

class Disposable : IDisposable
{
    public void Dispose() => Console.WriteLine("Dispose");
}

・静的ローカル関数
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/local-functions#local-function-syntax
 ローカル関数を static にできる。

{// これはコンパイルエラー 
    int x = 0;
    // localFunction(x);
    Console.WriteLine(x);

    // static なので外部の変数をキャプチャできずエラー
    // static int localFunction(int y) => ++x;
}
{// むしろキャプチャできないことがうれしい
    int x = 0;
    x = localFunction(x);
    Console.WriteLine(x);

    static int localFunction(int y) => ++y;
}

・破棄可能な ref 構造体
 [ 良いリンクを見つけられませんでした ]
 ref 構造体はインターフェイスを実装できないため、IDisposable インターフェイスを実装できません。しかし、これからは ref 構造体では、インターフェイスを実装しなくても void Dispose() メソッドを作るだけで OK となった。

{
    using var c = new C();
}// B.Dispose と表示される

// Dispose が実装された ref 構造体
ref struct C
{
    // IDisposable インターフェイスを実装せずメソッド宣言
    public void Dispose() => Console.WriteLine("B.Dispose");
}

・Null 許容参照型
 https://docs.microsoft.com/ja-jp/dotnet/csharp/nullable-references
 参照型を基本 null 非許容の扱いにできる。
 ※この機能を使うには明示的に機能を ON にする必要があります。

 参照型も基本 null 非許容の扱いになる。
 null を許容したい変数は ? を型の後ろに付けて宣言する。
 null 非許容の変数に null を入れようとすると警告になる。
 null 非許容の変数のメソッドなどを呼ぶと警告になる。

// null 非許容の変数に null を入れようとすると警告
string text1 = null;                // 警告 CS8600 Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。

string? text2 = null;
// null 許容の変数をそのまま使うと警告
Console.WriteLine(text2.Length);    // 警告 CS8602 null 参照の可能性があるものの逆参照です。
// null 許容の変数を使うときは ? をつける
Console.WriteLine(text2?.Length);

// null 非許容でも文脈を見て、null と思われる場合は警告がでる
Console.WriteLine(text1.Length);    //  警告 CS8602 null 参照の可能性があるものの逆参照です。

// 文脈を見て null でないとわかると警告はでない
if (text1 != null) Console.WriteLine(text1.Length);
text1 = ""; Console.WriteLine(text1.Length);

・非同期ストリーム
 https://docs.microsoft.com/ja-jp/dotnet/csharp/tutorials/generate-consume-asynchronous-stream
 ストリームを非同期で扱える。

// async System.Collections.Generic.IAsyncEnumerable メソッド名() でメソッドを宣言する
async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence()
{
    for (int i = 0; i < 20; i++)
    {
        // メソッドの中で await できる
        await Task.Delay(1000);
        // メソッドの中では yield return する
        yield return i;
    }
}

// foreach に await を付ける
await foreach (var number in GenerateSequence())
{
    Console.WriteLine(number);
}

・非同期の破棄可能
 https://docs.microsoft.com/ja-jp/dotnet/standard/garbage-collection/implementing-disposeasync
 インターフェイス System.IAsyncDisposable が追加された。await using で使う。using で使いたければ同時に System.IDisposable も実装することも可能。

// 非同期の破棄
await using var sample = new Sample();
// DisposeAsyncCore
// DisposeAsync
// と出力される

// IAsyncDisposable を実装したクラス
public class Sample : IAsyncDisposable
{
    // IAsyncDisposable インターフェイスで定義されているのメソッド
    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore();
        Console.WriteLine("DisposeAsync");
    }

    // sealed クラスでない場合は派生クラスから読んでもらうためにこのメソッドを作っておく
    protected async virtual ValueTask DisposeAsyncCore()
    {
        Console.WriteLine("DisposeAsyncCore");
    }
}

・インデックスと範囲
 https://docs.microsoft.com/ja-jp/dotnet/csharp/tutorials/ranges-indexes
 二つの型(構造体)と二つの演算子が追加された。
 ・System.Index 型 インデックスを表す
 ・System.Range 型 範囲を表す
 ・^ 演算子 末尾からを表す
 ・.. 演算子 範囲を表す

 二つの型の関係は System.Range のコンストラクタを見るとわかりやすい。先頭のインデックスと末尾のインデックスを持つのが範囲だとわかる。

Range (System.Index start, System.Index end)

 インデックスもコンストラクタを見るとわかりやすい。何番目なのかを表す数値と先頭からなのか末尾なのかを表すフラグを持っている。

Index (int value, bool fromEnd = false)

 演算子はそれぞれ次のように使える。

// System.Index
System.Index index1 = 1;
System.Index index2 = ^1;  // 演算子 ^

// System.Range
System.Range range1 = index1..index2;  // 演算子 ..

 範囲の指定例

// System.Index と .. を使って
System.Range range1 = index1..index2;  // index1 から index2 まで
System.Range range2 = index1..;        // index1 から末尾まで
System.Range range3 = ..index2;        // 先頭から index2 まで

// ^ と .. を使って
System.Range range1 = 1..2;
System.Range range2 = ^1..1;
System.Range range3 = ^2..^1;

// 配列での使用例
var array = new[] { "1", "2", "3", "4" };
Console.WriteLine(string.Join(", ", array[1]));     // 2
Console.WriteLine(string.Join(", ", array[^1]));    // 4        (一番後方が ^1)
Console.WriteLine(string.Join(", ", array[1..^0])); // 2, 3, 4(一番後方が ^0)

・null 合体割り当て
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/null-coalescing-operator
 ??=。左辺の変数が null の場合、左辺に右辺の値が代入される。左辺が null でない場合は何も起きない。

string text = null;
// text が "saitama" となり "saitama" が表示される
Console.WriteLine(text ??= "saitama");
// text は "saitama" なので ??= で値は変わらず "saitama" が表示される
Console.WriteLine(text ??= "gunma");

・構築された構造体型もアンマネージド型
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/unmanaged-types
 フィールド全てがアンマネージド型の場合、ジェネリック構造体がアンマネージドになる。

unsafe
{
    // エラーにならない
    Console.WriteLine(sizeof(MyType<int>));

    // エラー CS0208  マネージ型('MyType<object>') のアドレスの取得、サイズの取得、またはそのマネージ型へのポインターの宣言が実行できません
    Console.WriteLine(sizeof(MyType<Object>));
}

struct MyType<T>
{
    public T Value;
}

・入れ子になった式の stackalloc
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/stackalloc
 SpanSystem.ReadOnlySpan が通ることろで stackalloc[ ] { } が書ける。stackalloc[ ] { }foreach 等に直接書くと SpanSystem.ReadOnlySpan になる。

Span<int> nums = stackalloc[] { 1, 2, 3, 4, 5, 6 };

foreach (var num in stackalloc[] { 1, 2, 3, 4, 5, 6 })
  Console.WriteLine(num);

・verbatim 補間文字列の拡張
 https://docs.microsoft.com/ja-jp/dotnet/csharp/tutorials/string-interpolation#how-to-use-escape-sequences-in-an-interpolated-string
 補完文字列の @""、$"" を組み合わせて使う際に任意の順番で書くことができる。以前は $@"" だけだったが、 @$"" とも書けるようになった。

var text = "十万石饅頭";
// これまでは $@ だけ
Console.WriteLine($@"C:\saitama\gyoda\{text}");  // C:\saitama\gyoda\十万石饅頭

// これからは @$ も OK
Console.WriteLine(@$"C:\saitama\gyoda\{text}");  // C:\saitama\gyoda\十万石饅頭

■ C# 9.0 での新機能

・レコード
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/records
 GetHashCode()ToString()== なんかをいい感じに勝手に実装してくれる素敵な型。値を表現するクラスをいい感じに作ってくれます。プロパティもいい感じに作ってくれるのも魅力。

// 引数 string Value を持つコンストラクタが作られる
var saitama = new Saitama("せんべい");
// 引数と同名のプロパティが自動で作られて初期化も行われる
Console.WriteLine(saitama.Value);  // せんべい
// 良い感じの ToString も自動で実装
Console.WriteLine(saitama);  // Saitama { Value = せんべい }

// class の代わりに record と書く
// 引数を書くとコンストラクタとプロパティも作られる
public record Saitama(string Value)
{
}

・init 専用セッター
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/init
 初期化時にだけ値をセットできる素敵なプロパティを作れます。もっとわかりやすく簡単に言うと、イミュータブルなオブジェクトが作れる。
 初期化とは
 ・コンストラクタ
 ・初期化子 ( new XXXX { プロパティ名 = 値} )
 ・with 式 ( 型名 新しい変数 = 変数 with { プロパティ名 = 値 }; )

var saitama1 = new Saitama { InitProperty = "" };   // これはエラーにならない。初期化専用なので。うれしい
var saitama2 = saitama1 with { InitProperty = "" }; // これはエラーにならない。初期化専用なので。うれしい
//saitama2.InitProperty = "";                       // これはエラー。初期化専用なので。うれしい

public record Saitama
{
    // プロパティの宣言で普段 set と書いている所を init と書くと使える
    public string InitProperty { get; init; }
    public Saitama() => InitProperty = "value5";
}

・最上位レベルのステートメント
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/main-and-command-args/top-level-statements
 C# はプロジェクト内のどれかのクラスの static void Main(string[] args) メソッドから実行されるものだったが、クラスを書かずにいきなりコードが書けるようになった。

using System;

// いきなりコード
Console.WriteLine("Hello World!");

// コンソールアプリのコマンドライン引数は args という名前で使える
Console.WriteLine(string.Join(", ", args));

・パターン マッチングの拡張機能
 https://docs.microsoft.com/ja-jp/dotnet/csharp/tutorials/pattern-matching#add-peak-pricing
 パターンマッチングが強化された。

// a に関する条件を a を何度も書かずに
int a = new Random().Next();
if (a is (> 1 and < 3) or (> 100 and < 200)) Console.WriteLine("match");

// 否定は not。null のチェックに使うのがおすすめらしい
string b = null;
if (b is not null) Console.WriteLine("not null");

// switch 式ですごいことに
string Method(object arg)
{
    var text = arg switch
    {
        > 1 => "> 1",
        "a" => "a",
        not null => "not null",
        _ => "null",
    };
    return text;
}
Console.WriteLine(Method(0));

・ネイティブ サイズの整数
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/nint-nuint
 nin と nuint という型が追加された。32 ビット環境では 32 ビット整数型に 64 ビット環境では 64 ビット整数型になるとのこと。

// nint のサイズを測ってみる
Console.WriteLine($"int   : Type = {typeof(int).FullName,14} : MinValue = {int.MinValue,20} : MaxValue : {int.MaxValue,20}");
Console.WriteLine($"nint  : Type = {typeof(nint).FullName,14} : MinValue = {nint.MinValue,20} : MaxValue : {nint.MaxValue,20}");
Console.WriteLine($"long  : Type = {typeof(long).FullName,14} : MinValue = {long.MinValue,20} : MaxValue : {long.MaxValue,20}");
// 64 ビット (x64) の場合
// int   : Type =   System.Int32 : MinValue =          -2147483648 : MaxValue :           2147483647
// nint  : Type =  System.IntPtr : MinValue = -9223372036854775808 : MaxValue :  9223372036854775807
// long  : Type =   System.Int64 : MinValue = -9223372036854775808 : MaxValue :  9223372036854775807
// 32 ビット (x86) の場合
// int   : Type =   System.Int32 : MinValue =          -2147483648 : MaxValue :           2147483647
// nint  : Type =  System.IntPtr : MinValue =          -2147483648 : MaxValue :           2147483647
// long  : Type =   System.Int64 : MinValue = -9223372036854775808 : MaxValue :  9223372036854775807

・関数ポインター
 [ 良いリンクを見つけられませんでした ]
 関数ポインター。ポインターというからには unsafe で書く必要がある。

unsafe
{
    // delegate* で関数ポインターを格納する変数を宣言
    // &string.IsNullOrEmpty とメソッド名に & をつけることで関数のポインターを取得している
    delegate*<string, bool> f = &string.IsNullOrEmpty;
    // 普通の関数呼び出しの構文で呼べる
    Console.WriteLine(f(""));  // True
    Console.WriteLine(f("a")); // False
}

・localsinit フラグの出力を抑制する
 https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.compilerservices.skiplocalsinitattribute
 System.Runtime.CompilerServices.SkipLocalsInit 属性がついている場合、ゼロ初期化がなされずパフォーマンスが向上する場合がある。

[System.Runtime.CompilerServices.SkipLocalsInit]
unsafe static void Main(string[] args)
{
    int i;
    int* j = &i;
    Console.WriteLine(*j);  // 0 でない値
}

・ターゲット型の新しい式
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/new-operator
 new 式 という式が追加された。 型がわかっているところで new するときに型を書く必要がなくなった。

// 変数宣言
System.Collections.Generic.List<int> hoge1 = new();

// 引数
Fuga(new());
static void Fuga(System.Collections.Generic.List<int> hoge)
    => Console.WriteLine(hoge.GetType().FullName);

// 初期化子
System.Collections.Generic.List<int> hoge2 = new() { 0, 1, 2 };

class Class
{
    // フィールド
    internal static System.Collections.Generic.List<int> Hoge3 = new();
    // プロパティ
    internal static System.Collections.Generic.List<int> Hoge4 { get; set; } = new();
}

・静的な匿名関数
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/lambda-expressions#capture-of-outer-variables-and-variable-scope-in-lambda-expressions
 ラムダ式と匿名関数に static 修飾子をつけられるようになった。 ※例では匿名関数は省略。

new[] { 0 }.Select(static x => x);

・ターゲットにより型指定された条件式
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/conditional-operator
 三項演算の型の解決が賢くなった。

// 以前はエラーになったコード。List<int> でも int[] でも IEnumerable<int> に代入できるはずだが、
// それ以前に三項演算の結果の型が推論できずにエラーになっていた。今は大丈夫
IEnumerable<int> list = true ? new List<int>() : new int[0];

・共変の戻り値の型
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/override
 戻り値の型を共変できる。override したメソッドの戻り値の型を、スーパークラスのメソッドの戻り値の型のサブクラスにできる。

A a = new B();
IEnumerable<int> list = a.Method(); // return 値の型は List<int>

internal class A
{
    public virtual IEnumerable<int> Method() { return null; }
}

internal class B : A
{
    // 以前は戻り値の型を変えられなかった。今はサブクラスに変えられる
    public override List<int> Method() { return new List<int>(); }
}

・foreach ループの拡張機能 GetEnumerator サポート
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/foreach-in
 拡張メソッドで GetEnumerator メソッドを作ればどんな型のオブジェクトでも foreach できる。

var data = new Data { MyProperty1 = "Saitama", MyProperty2 = "Chiba", MyProperty3 = "Ibaraki" };
// 拡張メソッド GetEnumerator で foreach
foreach (string datum in data)
    Console.WriteLine(datum);

// GetEnumerator を持たない型
class Data
{
    public string MyProperty1 { get; set; }
    public string MyProperty2 { get; set; }
    public string MyProperty3 { get; set; }
}

// 拡張メソッド GetEnumerator
static class DataExtensions
{
    internal static System.Collections.IEnumerator GetEnumerator(this Data data)
        => new string[] { data?.MyProperty1, data?.MyProperty2, data?.MyProperty3 }.GetEnumerator();
}

・ラムダ ディスカード パラメーター
 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/lambda-expressions#input-parameters-of-a-lambda-expression
 ラムダ式のパラメータで使わないものが複数ある場合に、_ と書くと該当のパラメータを使えなくできる。

// 一つ目のパラメータも二つ目のパラメータも使わない場合
new int[0].Select((_, _) => 0);
// _ は破棄なので変数名になっていない。次のコードはエラーとなってくれてうれしい
new int[0].Select((_, _) => _);  // 現在のコンテキストに '_' という名前は存在しません  

・ローカル関数の属性
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/local-functions#local-function-syntax
 ローカル関数で属性をつけられる。

#nullable enable
static void Main(string[] args)
{
    var list = List(null);

    // ローカル関数に Obsolete 属性、NotNull 属性をつけている
    [System.Obsolete]
    static IEnumerable<int> List([System.Diagnostics.CodeAnalysis.NotNull] string text) => new int[0];
}

・モジュールの初期化子
 https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute
 アセンブリ (.dll) が使われる際の初期化メソッドを実装できる。

namespace ClassLibrary
{
    public class Class1
    {
        public static string Saitama => "Saitama! Saitama!";
    }

    class Class2
    {
        // 初期化メソッド。アセンブリが使われる時に勝手に実行される
        [System.Runtime.CompilerServices.ModuleInitializer]
        internal static void Initialize1() => Console.WriteLine("Initialize1");

        // 初期化メソッド。複数作れる。全部実行される
        [System.Runtime.CompilerServices.ModuleInitializer]
        internal static void Initialize2() => Console.WriteLine("Initialize2");
    }
}

・部分メソッドの新機能
 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods#partial-methods
 ※部分メソッドの説明へのリンクですが、現時点 (2021/03/30) ではC# 9.0 での仕様には反映されていないようです。
 部分メソッドが戻り値を持てるようになった。out 引数も持てるようになった。

partial class MyClass
{
    // partial メソッドの宣言 先頭に private と書くことが必要
    private partial int MyMethod(out int arg);
}

partial class MyClass
{
    // partial メソッドの実体
    private partial int MyMethod(out int arg)
    {
        arg = 1;
        return 2;
    }
}

■ 出典/参考文献

 上記まとめの作成に、次のサイトを大いに参考にさせていただきました。
 文言やサンプルコードは C# のガイド のコードを利用させていただきました。

C# のガイド | Microsoft Docs