rksoftware

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

C# 2.0 以降の新機能概要まとめ (C# 11.0)

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

■ C# 11.0 での新機能

・ファイル スコープ型
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/file  型を修飾子 file で定義することで、同一ファイル内だけで使える型を作れます。

file class Class1 { }
file struct Struct1 { }

// 同一ファイル内では使える
class Class2
{
    void Method()
    {
        new Class1();
        new Struct1();
    }
}
// 別ファイルでは使えない
class Class3
{
    void Method()
    {
        new Class1();   // エラー CS0246 型または名前空間の名前 'Class1' が見つかりませんでした(using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
        new Struct1();  // エラー CS0246 型または名前空間の名前 'Struct1' が見つかりませんでした(using ディレクティブまたはアセンブリ参照が指定されていることを確認してください)
    }
}

・ジェネリック型数値演算のサポート - インターフェイスの static virtual メンバー
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/interface#static-abstract-and-virtual-members
 インターフェイスに static virtual メソッド、static abstract メソッドを定義できます。性質は virtualabscract を思い浮かべれば良いのですが、static virtual メソッドを呼ぶことはできないので注意が必要です。

IInterface.Method2(0);  // エラー CS8926 静的な仮想または抽象インターフェイス メンバーには、型パラメーターでのみアクセスできます。

CClass.Method2(0); // OK
CClass.Method3(0);

interface IInterface
{
    static int Method1(int arg) => default;
    static virtual int Method2(int arg) => default;
    static abstract int Method3(int arg);
}

class CClass : IInterface
{
    public static int Method2(int arg) => default;  // これがあると呼び出せる
    public static int Method3(int arg) => default;
}

・ジェネリック型数値演算のサポート - ユーザー定義の checked 演算子
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/arithmetic-operators#user-defined-checked-operators
 演算子のオーバーロードをする際に、checked 用の演算子を定義できます。

Console.WriteLine(new Record(100) + new Record(200));               // Record { Value = 1 }
checked { Console.WriteLine(new Record(100) + new Record(200)); }   // Record { Value = 2 }

record struct Record(int Value)
{
    public static Record operator +(Record arg1, Record arg2) => new(1);
    public static Record operator checked +(Record arg1, Record arg2) { checked { return new(2); } }
}

・ジェネリック型数値演算のサポート - 緩和されたシフト演算子
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#operator-overloadability
 シフト演算子のオーバーロードをする際に、右オペランド (第 2 引数) に int でない型を使えます。独自の方でも OK です。

Console.WriteLine(new Sample() >> 1.0 / 2.0);
Console.WriteLine(new Sample() >> new Dummy());

record struct Dummy { }

record struct Sample
{
    public static Sample operator >>(Sample arg1, int arg2) => new Sample();
    public static Sample operator >>(Sample arg1, double arg2) => new Sample();
    public static Sample operator >>(Sample arg1, Dummy arg2) => new Sample();
}

・ジェネリック型数値演算のサポート - 符号なし右シフト演算子
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#unsigned-right-shift-operator-
 新しい右シフト演算子 >>> で符号なしの右シフトができます。

Console.WriteLine(Convert.ToString(-1, toBase: 2).PadLeft(32, '0'));
Console.WriteLine(Convert.ToString(-1 << 1, toBase: 2).PadLeft(32, '0'));
Console.WriteLine(Convert.ToString(-1 >> 1, toBase: 2).PadLeft(32, '0'));
Console.WriteLine(Convert.ToString(-1 >>> 1, toBase: 2).PadLeft(32, '0'));
// 11111111111111111111111111111111
// 11111111111111111111111111111110
// 11111111111111111111111111111111
// 01111111111111111111111111111111

・auto-default 構造体
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/struct#struct-initialization-and-default-values
 構造体のコンストラクタで初期化されないフィールドおよびプロパティが自動で default で初期化されるようになりました。

Console.WriteLine(null == new Saitama().Omiya);     // True
Console.WriteLine(null == new Saitama().Kawagoe);   // True

struct Saitama
{
    public string Omiya;
    public string Kawagoe { get; set; }
    public Saitama() { }
}

・string 定数での Span<char> のパターン マッチ
 https://learn.microsoft.com/ja-jp/dotnet/csharp/fundamentals/functional/pattern-matching#compare-discrete-values
 Span<char> の値の対するパターンマッチで文字列とマッチできます。

Span<char> saitama = "saitama".ToCharArray();
ReadOnlySpan<char> tokyo = "tokyo".ToCharArray();

Console.WriteLine($"{saitama} is {((saitama is "saitama")? "最高":"もうすこしがんまりましょう")} です。");
Console.WriteLine($"{tokyo} is {((tokyo is "saitama") ? "最高" : "もうすこしがんまりましょう")} です。");
// saitama is 最高 です。
// tokyo is もうすこしがんまりましょう です。

・拡張 nameof スコープ
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/nameof
 メソッドに付ける属性の宣言時に、そのメソッドのパラメータ名で nameof できます。

[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(arg))]
string? Method(string? arg) => arg;

・数値 IntPtr
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/integral-numeric-types#characteristics-of-the-integral-types
 nintSystem.IntPtr の、 nuintSystem.UIntPtr のそれぞれエイリアスになりました。

Console.WriteLine(((nint)0).GetType());     // System.IntPtr
Console.WriteLine(((nuint)0).GetType());    // System.UIntPtr

・UTF-8 の文字列リテラル
 https://learn.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-11#utf-8-string-literals
 文字列の後ろ ( 閉じの " の後ろ ) に u8 を付けると UTF-8 エンコードの System.ReadOnlySpan<T> が得られます。

foreach (var c in "埼玉".ToArray()) Console.Write(c); Console.WriteLine();
foreach (var c in System.Text.Encoding.Unicode.GetBytes("埼玉")) Console.Write(c); Console.WriteLine();
foreach (var c in System.Text.Encoding.UTF8.GetBytes("埼玉")) Console.Write(c); Console.WriteLine();
foreach (var c in "埼玉"u8) Console.Write(c); Console.WriteLine();
// 埼玉
// 25287137115
// 229159188231142137
// 229159188231142137

・必須メンバー
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/required
 クラスや構造体のメンバーを required キーワードで修飾すると必須にできます。必須とは初期化が必須ということで、null で初期化することは OK です。

new Class1 { P1 = null, P2 = null };
new Class1 { P1 = null };  // エラー

class Class1
{
    internal string? P1 { get; init; }
    internal required string? P2 { get; init; }
}

・ref フィールド
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/ref-struct
 ref 構造体のフィールドを ref にできます。

ref struct Saitama
{
    public ref string Omiya;
    public Saitama(ref string omiya) => Omiya = ref omiya;
}

・scoped ref
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/statements/declarations#scoped-ref
 ref の値の範囲を現在のスコープに限定します。

static ref Saitama Method1(scoped ref Saitama saitama)
{
    return ref saitama; // エラー CS9075  現在のメソッドに範囲指定されているため、参照渡し 'saitama' でパラメーターを返すことはできません
}

static Saitama Method2(scoped Saitama saitama)
{
    return saitama;  // エラー CS8352 参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'scoped Saitama' を使用することはできません
}

static Saitama Method3(ref string saiko)
{
    scoped Saitama saitama = new(ref saiko);
    return saitama; // エラー CS8352  参照される変数が宣言のスコープ外に公開される可能性があるため、このコンテキストで変数 'saitama' を使用することはできません
}

ref struct Saitama
{
    public ref string Omiya;
    public Saitama(ref string omiya) => Omiya = ref omiya;
}

・未加工の文字リテラル
 https://learn.microsoft.com/ja-jp/dotnet/csharp/programming-guide/strings/#raw-string-literals
 """ で囲うと改行やエスケープが必要な文字列をそのまま書けます。""" を文字列に含みたい場合は """" で囲みます。"""" を含みたい場合は """"" ......。機能を表す言葉が 未加工の文字リテラル で機能名が 生文字列リテラル のようです。

var ramen = """
    "手打ち風" ラーメン
    \550-
    """;
Console.WriteLine(ramen);
// "手作り風" ラーメン
// \550-
var ramen = """"
    """手打ち風""" ラーメン
    \550-
    """";
Console.WriteLine(ramen);
// """手作り風""" ラーメン
// \550-

・改善された、メソッド グループからデリゲートへの変換
 ※公式 Learn の記事は C# 11 の新機能の記事しか見つけることができませんでした。
 この機能は C# 新機能の記述を読んでも良くわかりませんでした。ジェネリックのメソッドからそうでないデリゲート型への暗黙の変換が行われます? その際にキャッシュされて複数回同じ変換をしてもキャッシュが使われる......? と書いてあるような気がします。

// こういうこと、なのでしょうか?
DInt dint1 = Method<int?>;
DInt dint2 = Method;
DString dstring1 = Method<string?>;
DString dstring2 = Method;

Console.WriteLine(object.Equals(dint1, dint2));         // True
Console.WriteLine(object.Equals(dint1, dstring1));      // False
Console.WriteLine(object.Equals(dstring1, dstring2));   // True

T? Method<T>(T? t) => default;

delegate int? DInt(int? i);
delegate string? DString(string? i);

・"警告ウェーブ 7"
 https://rksoftware.hatenablog.com/entry/2022/12/04/220000
 型名がすべて英小文字だと警告になります。

class saitama { }   // 警告 CS8981    型名 'saitama' には、小文字の ASCII 文字のみが含まれています。このような名前は、プログラミング言語用に予約されている可能性があります。

class saitama1 { }  // 数字が含まれているので OK
class saitamaA { }  // 大文字が含まれているので OK
class saitama_ { }  // 記号が含まれているので OK
class 埼玉 { }      // 非 ASCII 文字が含まれているので OK

・汎用属性
 ※公式 Learn の記事は C# 11 の新機能の記事しか見つけることができませんでした。
 日本語よりも英語の機能の名前の方が分かりやすいと思います。「 Generic attributes 」です。名前のまま。属性のクラスを Generic にできます。

var typeString = typeof(SampleStringClass).GetCustomAttributes(false)[0].GetType();
var typeInt = typeof(SampleIntClass).GetCustomAttributes(false)[0].GetType();
Console.WriteLine(typeString);  // SampleAttribute`1[System.String]
Console.WriteLine(typeInt);     // SampleAttribute`1[System.Int32]

class SampleAttribute<T> : Attribute { }

[Sample<string>] class SampleStringClass { }
[Sample<int>] class SampleIntClass { }

・文字列補間式の改行
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/tokens/interpolated
 文字列補間式の中で改行ができます。

var prefectures = new[] { "埼玉", "東京" };

foreach (var prefecture in prefectures)
    Console.WriteLine(
        $"{prefecture}{
            prefecture switch
            {
                "埼玉" => "最高",
                _ => "もっとがんばりましょう"
            }
        } です。"
    );

// 埼玉 は 最高 です。
// 東京 は もっとがんばりましょう です。

・リスト パターン
 https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/patterns#list-patterns
 リスト ( や配列など ) の要素でパターンマッチできます。使える型は IList<T>IReadOnlyList<T> 。細かくは int 型のインデクサーと int Count プロパティがある型で使える。

IList<int> data = new[] { 1, 2, 3 };

var result = data switch
{
    [1, 2, 3] => 1,
    _ => 2
};
Console.WriteLine(result);  // 1
// IList<T> 、 IReadOnlyList<T> で使える
IList<int> dataA = new[] { 1, 2, 3 };
IReadOnlyList<int> dataB = new[] { 1, 2, 3 };
Console.WriteLine(dataA is [1, 2, 3]);
Console.WriteLine(dataB is [1, 2, 3]);
// int 型のインデクサーと int Count プロパティがある型で使える
Sample data = new();
Console.WriteLine(data is [1, 2, 3]);

class Sample
{
    public int this[int index]{ get => 0; }
    public int Count => 0;
}

■ 出典/参考文献

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

C# のガイド | Microsoft Docs
learn.microsoft.com