C#は、.NETプラットフォームの進化と共に着実な進歩を遂げてきました。

C# 11は、開発者の生産性を高めるための構文の簡素化、型システムの柔軟性向上、そしてパフォーマンスの最適化という3つの軸を中心に多くの新機能が導入された重要なアップデートです。

特に、生文字列リテラルやリストパターンといった新構文は、日常的なコーディングにおける「書きにくさ」や「読み取りにくさ」を劇的に改善します。

また、汎用数学(Generic Math)の基盤となるインターフェースの静的抽象メンバーの導入は、ライブラリ設計者にとって極めて強力な武器となりました。

本記事では、C# 11で導入された主要な新機能を網羅し、実務でどのように活用すべきかを具体的なソースコードと共に詳しく解説します。

開発を劇的に効率化する文字列操作の新機能

C# 11では、文字列操作に関する機能が大幅に強化されました。

これらは日常的なプログラミングにおいて最も恩恵を感じやすいアップデートと言えるでしょう。

生文字列リテラル (Raw String Literals)

これまで、JSONやXML、SQLなどの長い文字列をC#コード内に記述する場合、ダブルクォーテーションの「エスケープ」や「改行の扱い」に悩まされてきました。

C# 11で導入された生文字列リテラルは、これらの問題を一挙に解決します。

生文字列リテラルは、最低3つのダブルクォーテーション """ で囲むことで定義されます。

この中では、シングルクォーテーションもダブルクォーテーションもエスケープなしでそのまま記述可能です。

C#
using System;

public class RawStringExample
{
    public static void Main()
    {
        // JSON形式の文字列を定義
        // 従来のように \" と書く必要がなくなり、可読性が向上
        string json = """
        {
            "Name": "C# 11",
            "Features": ["Raw String", "List Patterns"],
            "Description": "It's a "powerful" update."
        }
        """;

        Console.WriteLine(json);
    }
}
実行結果
{
    "Name": "C# 11",
    "Features": ["Raw String", "List Patterns"],
    "Description": "It's a "powerful" update."
}

この機能の素晴らしい点は、インデントの自動調整です。

閉じ記号の """ の位置に基づいて、それより左側にある空白は自動的にトリミングされます。

これにより、コードのインデントを崩すことなく、美しい文字列定義が可能になりました。

文字列補完内での改行

従来のC#では、文字列補完(Interpolated Strings)の { } 内で式を書く際、改行が許可されていませんでした。

C# 11からは、この制限が撤廃されました。

C#
using System;
using System.Linq;

public class InterpolationExample
{
    public static void Main()
    {
        var scores = new[] { 85, 92, 78, 95, 88 };

        // 補完文字列の中で複雑なLINQクエリなどを改行して記述可能
        string message = $"平均スコアは {
            scores
                .Where(s => s >= 80)
                .Average()
            :F1} です。";

        Console.WriteLine(message);
    }
}
実行結果
平均スコアは 90.0 です。

このように、ロジックを読みやすく整理できるようになったため、長い三項演算子やLINQを無理に1行に詰め込む必要がなくなりました。

パターンマッチングの進化:リストパターン

C# 11の目玉機能の一つがリストパターン (List Patterns)です。

配列やリストなどのコレクションに対して、その要素の並びや特定の値を直感的にマッチングさせることができます。

基本的なマッチングと破棄パターン

リストパターンでは、[] を使用して要素を記述します。

C#
using System;

public class ListPatternExample
{
    public static void Main()
    {
        int[] numbers = { 1, 2, 3 };

        // 完全一致のマッチング
        if (numbers is [1, 2, 3])
        {
            Console.WriteLine("完全に一致しました。");
        }

        // 破棄パターン (_) を使ったマッチング
        if (numbers is [1, _, 3])
        {
            Console.WriteLine("1番目と3番目が一致しました(2番目は何でも可)。");
        }
    }
}
実行結果
完全に一致しました。
1番目と3番目が一致しました(2番目は何でも可)。

スライスパターン (..)

さらに強力なのが、任意の数の要素にマッチするスライスパターン .. です。

C#
using System;

public class SlicePatternExample
{
    public static void Main()
    {
        int[] values = { 1, 2, 3, 4, 5 };

        string result = values switch
        {
            [1, .. var middle, 5] => $"最初が1、最後が5。中間要素数: {middle.Length}",
            [1, 2, ..] => "1, 2から始まります。",
            [.., 5] => "最後が5で終わります。",
            _ => "その他のパターン"
        };

        Console.WriteLine(result);
    }
}
実行結果
最初が1、最後が5。中間要素数: 3

このスライスパターンは、再帰的な処理やデータの構文解析において絶大な威力を発揮します。

例えば、CSVの解析やパケットデータの先頭・末尾チェックなどが非常に簡潔に記述できます。

型システムと安全性の強化

実務においてバグを減らし、より堅牢なコードを書くための機能も追加されています。

required メンバー

これまで、クラスのプロパティを「初期化時に必須」にするには、コンストラクタで引数として受け取るしかありませんでした。

しかし、これではオブジェクト初期化子(Object Initializers)の利便性が損なわれます。

C# 11の required 修飾子は、プロパティの初期化をコンパイル時に強制します。

C#
using System;
using System.Diagnostics.CodeAnalysis;

public class User
{
    // 必須プロパティ
    public required string Id { get; init; }
    public required string Name { get; init; }
    
    // 任意プロパティ
    public int Age { get; set; }

    public User() { }

    // SetsRequiredMembers属性を使えば、コンストラクタで初期化済みであることを示せる
    [SetsRequiredMembers]
    public User(string id, string name)
    {
        Id = id;
        Name = name;
    }
}

public class RequiredExample
{
    public static void Main()
    {
        // OK: 必須プロパティをすべて指定
        var user1 = new User { Id = "A001", Name = "田中" };

        // コンパイルエラー: Id と Name が指定されていないため
        // var user2 = new User { Age = 25 }; 

        Console.WriteLine($"User: {user1.Name}");
    }
}

これにより、「値の入れ忘れ」によるランタイムエラーを未然に防ぐことが可能になり、特にEntity Framework CoreやDapperなどのORMを利用する際のデータモデル定義が非常に安全になります。

汎用属性 (Generic Attributes)

属性(Attributes)に対してジェネリクスを使用できるようになりました。

従来は Type 型の引数を渡して内部でリフレクションを行う必要がありましたが、より型安全な記述が可能になります。

C#
using System;

// C# 11以前の書き方
public class OldValidatorAttribute : Attribute
{
    public OldValidatorAttribute(Type validatorType) { }
}

// C# 11以降(汎用属性)
public class ValidatorAttribute<T> : Attribute where T : class
{
    public T Validator { get; }
    // ...
}

[Validator<StringValidator>] // 型引数として直接渡せる
public class MyData { }

public class StringValidator { }

この機能により、IDEのコード補完やコンパイル時の型チェックが効くようになり、メタプログラミングの品質が向上します。

パフォーマンス最適化と低レイヤー機能

.NET 7とともに、C# 11はパフォーマンスを極限まで引き出すための機能も提供しています。

UTF-8 文字列リテラル

Web開発や通信プロトコルではUTF-8が標準ですが、C#の string 型はUTF-16です。

従来、UTF-8のバイト配列を定義するには手間がかかりましたが、C# 11では u8 サフィックスを付けるだけでReadOnlySpan<byte>としてUTF-8文字列を扱えます。

C#
using System;

public class Utf8Example
{
    public static void Main()
    {
        // u8サフィックスを付けると、UTF-8エンコードされたバイト配列(ReadOnlySpan<byte>)になる
        var utf8Name = "C# 11 Update"u8;

        Console.WriteLine($"Type: {utf8Name.GetType()}");
        Console.WriteLine($"Length: {utf8Name.Length} bytes");
    }
}
実行結果
Type: System.ReadOnlySpan`1[System.Byte]
Length: 12 bytes

これにより、メモリの割り当て(アロケーション)を削減し、高パフォーマンスなネットワーク処理やシリアライザを実装する際のオーバーヘッドを最小限に抑えることができます。

インターフェースの静的抽象メンバー (Generic Math)

C# 11の最も高度な機能の一つが、インターフェース内での static abstract メンバーの定義です。

これにより、数値型全般に対して動作するジェネリックなアルゴリズムを書くことができるようになりました。

機能名概要主な用途
static abstractインターフェースで静的メソッドを強制する演算子のオーバーロードの共通化
INumber<T>算術演算をサポートする標準インターフェース汎用的な計算ロジックの作成
C#
using System;
using System.Numerics;

public class MathExample
{
    // INumber<T> インターフェースを使用することで、int, double, decimal 全てに対応
    public static T AddAll<T>(IEnumerable<T> values) where T : INumber<T>
    {
        T sum = T.Zero;
        foreach (var value in values)
        {
            sum += value; // + 演算子がインターフェースで定義されている
        }
        return sum;
    }

    public static void Main()
    {
        int[] ints = { 1, 2, 3 };
        double[] doubles = { 1.1, 2.2, 3.3 };

        Console.WriteLine($"Int sum: {AddAll(ints)}");
        Console.WriteLine($"Double sum: {AddAll(doubles)}");
    }
}
実行結果
Int sum: 6
Double sum: 6.6

これまで、int 用と double 用で別々に用意していた計算メソッドが、一つのジェネリックメソッドに集約できるようになったのは、数学的処理を扱うアプリケーションにおいて画期的な出来事です。

その他の便利なアップデート

細かいながらも、開発体験を向上させる機能がいくつか追加されています。

ファイルローカル型 (File-local types)

file 修飾子を使用すると、その型はそのファイル内からしかアクセスできなくなります。

C#
// File1.cs
file class HiddenHelper
{
    public void DoWork() => Console.WriteLine("Working...");
}

public class PublicClass
{
    public void Execute() => new HiddenHelper().DoWork();
}

これは主にソースジェネレーター (Source Generators)などで、生成されたコードが他のコードと名前衝突を起こさないようにするために非常に有用です。

オートプロパティの初期化における「field」キーワード(プレビュー)

※C# 11のリリースサイクルで議論され、後のバージョンでも洗練されている機能ですが、オートプロパティのバッキングフィールドに直接アクセスしようとする試みが進んでいます。

チェック済みユーザー定義演算子

オーバーフローチェック(checked)を行うかどうかで、異なる挙動をする演算子を定義できるようになりました。

C#
public static MyType operator +(MyType left, MyType right) { ... }
public static MyType operator checked +(MyType left, MyType right) { ... }

計算の正確性が求められる金融系システムなどで、意図的なオーバーフロー制御を行う際に役立ちます。

まとめ

C# 11は、単なるマイナーアップデートに留まらない、強力な進化を遂げたバージョンです。

生文字列リテラルリストパターンは、コードの記述量を減らすだけでなく、読み手の認知負荷を大幅に下げてくれます。

また、required メンバーによる安全性向上や、静的抽象メンバーによる高度な抽象化は、大規模なシステム開発やライブラリ開発においてその真価を発揮します。

実務においては、まず導入しやすい「文字列リテラル」や「required プロパティ」から活用を始め、徐々に「リストパターン」や「UTF-8リテラル」による最適化を取り入れていくのが良いでしょう。

.NET 7以降の環境を利用しているプロジェクトであれば、これらの機能を積極的に活用することで、よりモダンでメンテナンス性の高いC#コードを実現できるはずです。

C# 11で導入されたこれらの新機能を武器に、よりクリーンで、より高速なアプリケーション開発を目指しましょう。