Microsoftは、.NET 5開発プラットフォームリリースの一部としてC# 9.0の提供を開始した。.NET 5とC# 9.0はペアになることで、言語に数多くの新機能を提供する。新たな言語機能は、record型、init-onlyセッタ、トップレベルステートメント、パターンマッチングの拡張、target-typed new式、共変戻り値(covariant returns)、他にもたくさんだ!
C# 9の大きな目標は、データシェイプ(data shape)の不変的表現にある。これを最も表しているのが、recordタイプとinit-onlyプロパティだ。一般的にrecordは、C# 9.0バージョンの最も興味深い新機能にひとつに分類されている。
recordは、C#の新しい不変参照型(immutable reference type)である。値の設定や変更はオブジェクトのコピーの生成になる。これは.NETが値型や文字列を扱うのと同じ方法である。今回リリースのもうひとつの不変機能がinit-onlyプロパティ、より正確にはinit
アクセサだ。これはset
アクセサのひとつとして、特定のプロパティのオブジェクト初期化においてのみコールすることができる。
クラスとの最大の違いは、record型が値を基準とした等値比較を行うことと、デフォルトで不変(immutable)であることだ。これを使うことで、個々のプロパティではなく、全体として不変なオブジェクトが生成される。通常であれば、不変の参照型を記述する場合、C#ではある程度のコード記述が必要になる。
recordの使用には、いくつかのアドバンテージがある。並行プログラミングでは、共有データの使用に大きな意味がある。このようなケースでの答は、recordを使用することである。C#で型を不変にするためには、最もエレガントで便利な方法なのだ。
record型を作るには、キーワードrecordを使用する必要がある。下記のコードは、Personというrecord型を作成する最も単純な例である。
例中のrecord型であるPerson
には、FirstNameとLastNameという、2つの読み取り専用プロパティが含まれている。不変参照型であるためm、生成後、これらのプロパティはすべて変更不能になる。
public record Person
{
public string LastName { get; }
public string FirstName { get; }
public Person(string first, string last) => (FirstName, LastName) = (first, last);
}
新たに値を生成して新しい状態を表現するために、C# 9.0ではwith-expressionのサポートが導入された。with-expressionはオペランド指定されたrecordに対して、指定されたプロパティやフィールドを変更したコピーを生成する。プロパティにはinitあるいはsetアクセサが定義されている必要がある。
var person = new Person { FirstName = "Almir", LastName = "Vuk" };
var otherPerson = person with { LastName = "Casals" };
record不変参照型に関する完全な仕様は、こちらで確認することができる。
init-onlyセッタはrecordに限らず、任意の型に対して宣言の可能な新機能である。山のような定型コードを書かずにオブジェクトを初期化するための優れたアプローチだ。init-onlyセッタはプロパティ初期化構文において、生成式において値を設定したプロパティが、生成後は読み取り専用であることを強調するために使用することができる。これによってコードのメンテナンスが容易になると同時に、可読性も向上する。一般的なユースケースのシナリオはデータ転送オブジェクトである。以下の例はMicrosoftの公式資料に含まれているもので、initセッタを持つ3つのプロパティを含むWeatherObservation
というクラスを示している。プロパティにはToString
メソッドによって簡単にアクセスできる。
public struct WeatherObservation
{
public DateTime RecordedAt { get; init; }
public decimal TemperatureInCelsius { get; init; }
public decimal PressureInMillibars { get; init; }
public override string ToString() =>
$"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
$"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure";
}
init-onlyセッタに関する詳しい情報は、言語の公式資料にある。
簡単なプログラムやコードファイルをC#で書く場合でも、その中には多くの定型コードが含まれている。コードが乱雑になり、インデントのレベルが深くなっていくと、初心者はそれだけで圧倒されてしまうこともある。C# 9.0ではトップレベルステートメントがサポートされていて、単純なhello worldならばこのように記述できる。
using System;
Console.WriteLine("Hello World!");
重要なのは、トップレベルステートメントを使用できるのはプロジェクト内のひとつのファイルのみである、ということだ。これを守らなければ、コンパイラがエラーを報告する。トップレベルステートメントと、Mainメソッドなどプログラムのエントリメソッド宣言が併用されていた場合にも、エラーが報告される。ステートメントを含むことができるのは1ファイルのみで、通常はProgramクラスのMainメソッド内である。
Microsoftの資料にあるように、"この機能が最も一般的に利用されるのは、教材の作成においてです。C#開発の初心者は、標準的な'Hello World!'プログラムを1~2行のコードで書くことができます。その他のセレモニは必要ありません。一方で、経験を積んだ開発者は、この機能の使い道をたくさん見つけるでしょう。トップレベルステートメントは、Jupyterノートブックが提供するのと同じような、スクリプトライクなエクスペリエンスを提供してくれます。小さなコンソールプログラムやユーティリティに、トップレベルステートメントはうってつけです。Azure Functionsは、トップレベルステートメントの理想的なユースケースです。"
C# 9.0では、代入される式に明確な型が存在する場合には、右側の型を省略することも可能である。例えば、配列やオブジェクトの初期化のように、繰り返しの多い場合だ。
Point p = new (3, 5);
Point[] ps = { new (1, 2), new (5, 2), new (5, -3), new (1, -3) };
target-typed new式の詳細は、言語の公式仕様の中にある。
今回のリリースで地味ながら重要な機能が、ローカル関数宣言に属性を付加できるようになったことだ。これによってコードがクリアに、より読みやすいものになる。MicrosoftのソフトウェアアーキテクトであるDavid Fowler氏はすでに、この機能のよい使い道を見つけている。このTwitter記事で氏は、UseEndpoints
ミドルウェアセットアップ内部のローカル関数に属性を使用している。この機能に関しては、非常に多くのコメントやフィードバックが取り交わされていた。機能に関する詳細は、言語の公式資料の中にある。
C# 9.0では新たなパターンマッチングの拡張も行われている。詳細は言語の公式仕様で確認してほしい。
C# 9.0の提案仕様はすべて公開されている。また、.NET Conf 2020で実施された、言語の新機能に重点を置いたテクニカルセッションがYouTubeにある。C# 9.0は、最新バージョンのVisual Studio 2019に含まれている。