はじめに
C#は、この11月に.NET 8のリリースとともにバージョン12となりました。C#は、.NETの標準プログラミング言語として絶え間ない進化を続けており、このバージョン12にも便利で使い勝手のよい機能が多数実装されました。本連載では、このC#の最新バージョンにフォーカスし、その新機能を紹介していきます。また、最新バージョン以前の機能にも関連の深いものについて言及することで、新機能を線として理解できることも意識します。
機能 | 概要 |
---|---|
プライマリコンストラクタ | record型のみでサポートされていたプライマリコンストラクタがクラスでも利用可能に |
コレクション式 | 配列やリストなどのコレクションをより簡潔な書式で記述可能に |
ref readonlyパラメータ | 左辺値を読み出し専用で関数に渡す場合の意図を明確に |
デフォルトのラムダ式パラメータ | ラムダ式のパラメータに既定値を定義できるように |
任意の型エイリアス | タプル型、配列型、ポインタ型などにも別名を設定可能に |
インライン配列 | struct型の固定長配列の利用が可能に |
Experimental属性 | 試験的機能を示す新しい属性を指定可能に |
インターセプタ | コンパイル時に置き換え可能なメソッドを定義可能に |
対象読者
- C#の最新バージョンの機能を把握したい方
- C#の経験者で、C#に改めて入門したい方
- プログラミング言語の最新パラダイムに関心のある方
必要な環境
本記事のサンプルコードは、以下の環境で動作を確認しています。
-
macOS Sonoma
- .NET 8 SDK(8.0.101)
サンプルの実行
掲載サンプルは、基本的にプロジェクトのProgram.csファイルに記述されています。簡略化のために、C# 9より使用可能になったトップレベルステートメントの記法を用いています。このため、クラスなどの定義がソースコードの後方に配置されます。また、.NET 8環境でのみ動作しますので、.NET 8環境を用意した上で、プロジェクトフォルダでdotnet runコマンドで実行してください。
C# 9で実装されたプライマリコンストラクタが、クラスと構造体でも利用可能に
C# 12では、レコード型にはすでに実装されていたプライマリコンストラクタ(Primary constructors)が、クラスと構造体でも利用可能になりました。
プライマリコンストラクタとは、よりシンプルに定義できるコンストラクタの一種です。その名(プライマリ=第一の、最優先の)の通り、他にコンストラクタを定義する場合、それらは必ずプライマリコンストラクタを呼び出す必要があります。また、プライマリコンストラクタをクラス内に複数定義することはできません。
C# 9で実装されたレコード型において、このプライマリコンストラクタがすでに導入されていました。機能の差異はありますが、レコード型と同様にクラスと構造体においてもプライマリコンストラクタが利用できるようになりました。
プライマリコンストラクタの定義例を示す前に、従来のコンストラクタ定義をリストに示します。特に解説の必要なコードではありません。
class ClassNoPrimaryConstructor { public int a; public ClassNoPrimaryConstructor(int _a) { a = _a; } }
プライマリコンストラクタを使うと、上記は以下のリストとなります。
class ClassUsePrimaryConstructor(int _a) (1) { public int a = _a; (2) }
(1)のように、コンストラクタの引数はクラス名に続けます。そして(2)のようにフィールド定義の初期値に引数を指定します。これで、初期値を伴うコンストラクタの定義ができたことになり、いたって記述がシンプルになります。
なお、プライマリコンストラクタが引数を必要としない場合、クラスはフィールドを全く持たないことも可能になりますが、その場合は「class ClassUsePrimaryConstructorWithNoArgs;」のように中かっこも省いた定義が可能です。
プライマリコンストラクタは、既述の通り最優先で呼び出すべきコンストラクタです。プライマリコンストラクタを定義すると、その他のコンストラクタにおいてプライマリコンストラクタの呼び出しが必須となります。これによって、クラスのフィールドを確実に初期化できます。
class ClassWithConstructor(int _a) { public int a = _a; // 引数なしコンストラクタ public ClassWithConstructor(): this(0) {} (1) }
(1)において、引数なしのコンストラクタにおいて、thisによるプライマリコンストラクタの呼び出しが強制されることで、安全にフィールドを0に初期化できます。仮にthisの記述を省くとコンパイルエラーとなります。
レコード型との違い
レコード型におけるプライマリコンストラクタとクラスにおけるそれとは大きな違いがあります。それは、レコード型ではプロパティやEqualsなどのメソッドが自動生成される点です。例えば上記の例をレコード型に置き換えると、以下のリストのようになります。
record RecordUsePrimaryConstructor(int a) { // aに相当するプロパティは自動生成される }
これで、ゲッタ、initアクセサを含むプロパティaが生成されます。クラスの場合は、プライマリコンストラクタの引数にあっても対応するフィールドの定義がない場合、引数は単に使われなくなるだけなので大きな違いです。レコード型はデータ保持が主な目的であり、イミュータブル(変更不可)という性質を持つことから、必要なアクセサやメソッドの実装が容易であるからと考えられます。クラスは基本的にミュータブル(変更可)であり、継承を含めた複雑な実装もあり得ることから、自動生成は無理だということでしょう。
プライマリコンストラクタにより、インスタンス生成をより安全に確実に実行できるようになりました。