10yroの開発日記

福岡にある株式会社10yro(トイロ)のエンジニアが書いています

C# 12 新機能

2023年にリリースされたC# 12の新機能について、簡単に纏めました。Visual Studio 2022で使用可能です。

C# 12の新機能は、コードの簡潔さと可読性の向上を目的としており、特に初期化やラムダ式シンタックスが改善されています。

ただし、各機能の特性や使用するコンテキストに応じて、適切に選択することが重要です。デメリットや懸念点も考慮しつつ、プロジェクトの要件に合わせた利用を検討しましょう。

プライマリ コンストラクター

クラスやレコード型の定義内でコンストラクターパラメーターを直接指定することができる機能です。 これにより、コンストラクターの定義を簡潔化し、可読性が向上します。

メリット デメリット
  • 冗長な初期化コードが不要になり、クラスやレコード型の初期化がシンプルになります。
  • コンストラクターパラメーターが直接フィールドやプロパティにバインドされるため、初期化漏れが減少します。
  • 複雑な初期化ロジックや条件付きの初期化を行う場合、可読性が低下し従来のコンストラクターを使用した方がよい場合があります。
  • 初期化パラメーターが増えると可読性が低下する可能性があります。
// C# 11 以前
public class Person
{
    public string Name { get; }
    public int Age { get; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

// C# 12
public class Person(string name, int age) {
    public string Name { get; } = name;
    public int Age { get; } = age;
}

コンストラクター内で明示的にフィールドやプロパティの初期化を行う必要がありましたが、C# 12ではプライマリ コンストラクターを使用することでその必要がなくなります。

コレクション式

コレクション式は、コンパクトな構文を使ってリストや配列などのコレクションを初期化する機能です。これにより、複数の要素を簡単に追加することができます。

メリット デメリット
  • 初期化が簡潔になり、コードの可読性が向上します。
  • 複数の要素を一度に追加できるため、初期化の効率が良くなります。
  • 静的な初期化に適しており、動的な初期化には従来の方法でコレクションを初期化する方がよい場合があります。
  • 初期化する要素が多い場合、可読性低下する可能性があります。
  • // C# 11 以前
    var numbers = new List<int>();
    numbers.Add(1);
    numbers.Add(2);
    numbers.Add(3);
    
    // C# 12
    var numbers = new List<int> { 1, 2, 3 };
    

    コレクションを初期化するためにはコンストラクターを呼び出し、Add メソッドを使って要素を追加する必要がありました。コレクション式を使うと、初期化がより簡潔になります。

    インライン配列

    コンパクトに配列を宣言および初期化する機能です。配列のサイズが固定されている場合に適しています。

    メリット デメリット
    • 配列のサイズが固定の場合に初期化が簡単になり、コードがシンプルになります。
    • 可変長のデータ構造には適さないため、要素数が動的に変化する場合には適していません。
    • 要素の初期化が複雑な場合には、コードの可読性を低下する可能性があります。
    // C# 11 以前
    int[] numbers = new int[] { 1, 2, 3 };
    
    // C# 12
    int[] numbers = { 1, 2, 3 };
    

    配列を初期化する場合には、配列を宣言してから要素を一つずつ指定する必要がありましたが、インライン配列を使うことで一度に要素を宣言および初期化が可能になりました。

    ラムダ式の省略可能なパラメーター

    ラムダ式において、パラメーターに規定値を設定できるようにする機能です。

    メリット デメリット
    • 単純なラムダ式をよりコンパクトに表現でき、可読性が向上します。
    • パラメーターを省略することで一貫性が保たれない場合があり、コードの可読性を損なう可能性があります。
    // C# 11 以前
    var s = (String s) => (s == null) ? "xxx" : s;
    Console.WriteLine(s(null));     // xxx
    Console.WriteLine(s("aaa"));    // aaa
    
    // C# 12
    var s = (String s = "xxx", String[] m) => s;
    Console.WriteLine(s());         // xxx
    Console.WriteLine(s("aaa"));    // aaa
    

    ラムダ式のパラメーターに規定値の指定が可能になり、可変キーワードのparamsにも対応されました。

    ref readonly パラメーター

    メソッドの引数として、読み取り専用にする機能です。これにより、値を変更することなく効率的にメモリを使用できます。

    メリット デメリット
    • 値の変更を防ぎつつ、メモリの使用量を削減します。
    • 安全性を高め、意図しない値の変更を防止します。
    • メソッドの柔軟性が低下し、一部の場面で不都合が生じることがあります。
    • 値型の場合には一時的な変数の作成が必要であり、コードの冗長性が増すことがあります。
    // C# 11 以前
    void ChangeValue(ref int value)
    {
        // valueを更新する処理
    }
    
    
    // C# 12
    void ReaOnlyValue(ref readonly int value)
    {
        // valueを取得する処理のみ
    }
    

    これまでのC#では、refキーワードを使用して値を変更できましたが、ref readonlyを使用することで変更を禁止し、安全性を向上させることが可能になります。

    任意の型の別名設定

    using文で任意の型にエイリアスを設定する機能です。特定の型に対して理解しやすい名前を使用できます。

    メリット デメリット
    • 特定の型に対して直感的なエイリアスを提供し、コードの可読性を向上させます。
    • 適切な命名と運用が行われない場合、可読性が低下する可能性があります。
    • 複数のエイリアスが同じ型を指す場合、コードの理解が難しくなることがあります。
    // C# 11 以前
    using PrimitiveInt = System.Int32;
    using NullableInt = System.Nullable<System.Int32>;
    using TupleInt = System.ValueTuple<System.Int32, System.Int32>;
    
    // C# 12
    using PrimitiveInt = int;
    using ArrayInt = int[];     // C# 11以前で配列は指定不可
    using NullableInt = int?;
    using TupleInt = (int, int);
    

    using 文での型エイリアスは、特定の名前空間内の型に限定されていましたが、任意の型に対してエイリアスを指定することができます。 これらの新機能を使用する際には、それぞれの特性や適用されるコンテキストを正しく理解し、適切に活用することが重要です。特にデメリットや懸念点を考慮しつつ、プロジェクトの要件に合わせて利用することが推奨されます。