目次

Ver. 12.0
リリース時期 2023/11
同世代技術
  • .NET 8.0

コレクション式

[] 記号を使って配列などの初期化ができるようになりました。 配列だけではなく、コレクション(List<T> 型など)、Span<T> なども全く同じ書き方で初期化できます。 これをコレクション式(collection expression)と言います。

using System.Collections.Immutable;

int[] array = [1, 2, 3];
List<int> list = [1, 2, 3];
Span<int> span = [1, 2, 3];
ReadOnlySpan<int> ros = [1, 2, 3];
ImmutableArray<int> immutable = [1, 2, 3];

また、コレクション式中では、.. を使うことで「別のコレクションの中身の展開」ができます。 これを スプレッド (spread)演算子と言います。

int[] array1 = [1, 2, 3];
int[] array2 = [4, 5, 6];

// 0, 1, 2, 3, 4, 5, 6, 7
int[] combined = [0, ..array1, ..array2, 7];

詳しくは「コレクション式」で説明します。

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

通常のクラス、構造体に対してプライマリ コンストラクターが使えるようになりました。

class A(int x)
{
    public int X { get; } = x;
}

レコード型の方を先に実装してしまったがために混乱があるんですが、 通常クラス・構造体の場合はプライマリ コンストラクター引数からプロパティを自動生成する機能はありません。

また、これに伴い、class C; というように、メンバーを1つも持たないでいい場合に {} を書く必要がなくなりました。

詳しくは「プライマリ コンストラクター」で説明します。

using エイリアスに任意の型を書けるように

C# 11 ではエラーになっていた以下のようなコードをコンパイルできるようになりました。

using Primitive = int;
using Array = int[];
using Nullable = int?;
using Tuple = (int, int);

詳しくは「任意の型に対する using エイリアス」で説明します。

ラムダ式のデフォルト引数

ラムダ式の引数にオプション引数にできる(既定値を与えられる)ようになりました。 また、params 引数も使えるようになりました。

// オプション引数(既定値値指定)。
var f1 = (int x = 1) => 0;

// params 引数。
var f2 = (params int[] x) => 0;

// 混在も OK。
var f3 = (int x = 1, params int[] y) => 0;

詳しくは「ラムダ式のオプション引数(既定値)と params 引数」で説明します。

ref readonly 引数

ref 引数、in 引数の亜種として、 「書き換えはしないけども、右辺値は受け付けたくない」ということを表す ref readonly 引数というものを導入しました。

// in 引数の代わりに ref readonly 引数。
void m(ref readonly int x) { }

m(10); // リテラルは警告に。

var a = 1;
var b = 2;
m(a + b); // 式も警告に。

// in や ref を付けないのも警告。
m(a);

// in を付けると警告が出ない。
m(in a);

// in 引数と違って、ref 修飾でも OK。
m(ref a);

ちなみに、呼び出し側の書き方が変わる以外に差はなく、コンパイル結果の挙動は in 引数と全く同じです。 呼び出し側の差は以下の通りです。

呼び方 in ref readonly
m(ref x) 警告 OK
m(in x) OK OK
m(x), m(x + y), m(123) OK 警告

詳しくは「ref readonly 引数」で説明します。

その他

InlineArray

.NET 8 で、InlineArray 属性 (System.Runtime.CompilerServices 名前空間) というものが入って、「値型の固定長配列」みたいなものを作れるようになりました。

using System.Runtime.CompilerServices;

// この属性を付けると、 .NET ランタイムが特別扱いして、構造体のサイズを拡大する。
// (コンストラクター引数で Length 指定。)
[InlineArray(3)]
struct FixedBuffer<T>
{
    private T _value;
}

基本的には .NET ランタイム側の機能ですが、 いくつか、C# 側にもこの InlineArray 向けの特殊対応が入っています。

FixedBuffer<string> buffer = new();

// InlineArray に対して直接インデクサーを書ける。
buffer[0] = "zero";
buffer[1] = "one";

// Span/ReadOnlySpan に暗黙的に変換できる。
Span<string> span = buffer;
span[2] = "two";

// foreach で列挙できる。
foreach (var x in buffer)
{
    Console.WriteLine(x);
}

詳しくは「[雑記] InlineArray」で説明します。

nameof の微修正

nameof 演算子にちょっとした修正が入りました。

C# 11 以前だと、以下の例の最後の行のように、 静的メンバー内から「インスタンス メンバーのインスタンス メンバー」みたいな名前の参照ができなかったようです。

class A
{
    public string? Instance { get; }

    // これは元から行けた。
    public string InstanceM() => nameof(Instance.Length);
    public static string StaticM1() => nameof(string.Length);
    public static string StaticM2() => nameof(Instance);

    // これが今までダメだったらしい。
    public static string StaticM() => nameof(Instance.Length);
}

これが、C# 12 ではコンパイルできるようになりました。

正直、バグ修正扱い(最新コンパイラーを使うと C# 11 以下でもコンパイルが通るようになる)でもいいレベルだとは思いますが、一応は C# 12 以上限定です。

更新履歴

ブログ