C# 11 の新機能をいくつか試してみました

# C# 11 の新機能をいくつか試してみました

C# 11 で導入された新機能をいくつか試してみました。
詳細は以下のMicrosoftによる解説をご確認ください。

https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-11


## 生文字列リテラル

C# 11 の中でも個人的な目玉機能です。
3つ以上の二重引用符(`"""`) で囲むことで、改行等を含む文字列をより自然に記述することが出来ます。
逐語的識別子(`@`) を付与した文字列リテラルと似ていますが、インデントを崩さずにより自然に記述できるようになりました。

```csharp: 生文字列リテラル (例)
string query = """
    SELECT Id, Name
      FROM TableName
     WHERE Id = @id
    """;
```

```:格納文字列 (生文字列リテラル)
SELECT Id, Name
  FROM TableName
 WHERE Id = @id
```

以下の記事でも紹介しましたので、よろしければご参照ください。

https://qiita.com/haruka_kotani/items/a274b982d6d42c5540f0


## 文字列補間の改行

文字列補間(`$""`) の `{` と `}` で囲まれた 補間式 の中で改行が出来るようになりました。
長くなりがちな `switch`式 や LINQクエリ を補間式に取り入れる場合に、文字列補間が読みやすくする効果がありそうです。

```csharp
// 適当な配列(1から100までの連番)
List<int> list = Enumerable.Range(1, 100).ToList();

// 配列の上位5件を抽出 : 改行無し(従来)
string str1 = $"Top 5 : [{string.Join(", ", list.OrderByDescending(x => x).Take(5).Select((x, i) => $"{i + 1} : {x}"))}]";

// 配列の上位5件を抽出 : 改行有り(C# 11 から可能)
string str2 = $"Top 5 : [{string.Join(", ", 
        list.OrderByDescending(x => x)
            .Take(5)
            .Select((x, i) => $"{i + 1} : {x}")
    )}]";

// 出力例
// Top 5 : [1 : 100, 2 : 99, 3 : 98, 4 : 97, 5 : 96]
```


## UTF-8 の文字列リテラル

文字列リテラルに接尾辞 `u8` を指定することで、文字列をUTF-8でエンコードしたバイナリ(`byte`列)を取得できるようになりました。

```csharp
// [ 0x61, 0x62, 0x63 ]
ReadOnlySpan<byte> a1 = "abc"u8;
// [ 0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0x86 ]
ReadOnlySpan<byte> a2 = "あいう"u8;
```

以下のコードと同じような結果が得られます。

```csharp
byte[] b1 = Encoding.UTF8.GetBytes("abc");
byte[] b2 = Encoding.UTF8.GetBytes("あいう");
```


## 必須メンバー

プロパティやフィールドに `required` 修飾子を付与することで、オブジェクト初期化子でそのプロパティ等に値を代入することを強制するようになります。
例えば、以下のような `required` 修飾子を付与した `X`、`Y` プロパティを持つクラスを例とします。

```csharp: クラス定義
public class Hoge
{
    public required int X { get; init; }
    public required int Y { get; init; }

    public Hoge()
    {
    }

    public Hoge(int x)
    {
        this.X = x;
    }

    public Hoge(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}
```

この場合、オブジェクト初期化子で `X`、`Y` の両方に値を指定した場合のみコンパイルが通るようになります。

```csharp
Hoge hoge1 = new Hoge { X = 1, Y = 2 };  // OK

Hoge hoge2 = new Hoge();      // エラー (X, Yが指定されていない)
Hoge hoge3 = new Hoge(1);     // エラー (Yが指定されていない)
Hoge hoge4 = new Hoge(1, 2);  // エラー (初期化子で指定する必要がある)

Hoge hoge5 = new Hoge { X = 1 };     // エラー (Yが指定されていない)
Hoge hoge6 = new Hoge(1) { Y = 1 };  // エラー (Xも初期化子で指定する必要がある)
```

コンストラクタで何らかの値が代入される場合でも、オブジェクト初期化子で改めて指定する必要があります。
(その為、`required` 修飾子を付与したプロパティ等はコンストラクタで値を設定してもあまり意味が無さそうです[^1])

[^1]: コンストラクタ中でプロパティやフィールドを参照する場合くらいでしょうか……？


## ファイルローカル型

アクセス修飾子 `file` を使用して、同じファイルからのみアクセス可能な型(ファイルローカル型)を定義できるようになりました。
例えば、部分クラス(`partial class`) は従来では参照可能な型が同一になるケースがほとんどですが、ファイルローカル型を用いることで参照範囲を制限することが可能になります。

```csharp: File1.cs
internal partial class Class1
{
    // 同じファイルで定義されたファイルローカル型 Class2 が参照できる
    public static int X1 => Class2.X;
}

// ファイルローカル型(file アクセス修飾子を付与したクラス)
file class Class2
{
    public static int X => 1;
}
```

```csharp: File2.cs
internal partial class Class1
{
    // 異なるファイルで定義されたファイルローカル型 Class2 が参照できず、エラー
    public static int X2 => Class2.X;
}
```

あまり積極的に活用する例はなさそうですが、自動生成されたソースコードとの名称の競合を回避する為に役立つそうです。
なお、`file` は トップレベル(globalな場所 または 名前空間の直下) で使用可能です。


## その他

他にも 汎用属性、リストパターン 等々 新しい機能が導入されていますが、今のところ私が理解出来ていないので割愛いたします。
良い活用例等があれば挑戦してみたいですね。