今日の C# 話はちょこっとした修正の話になります。 これまで new C { [^1] = 1 }; がコンパイル通らなかったみたいで、これが最近修正されました。

(Visual Studio 17.9 Preview 3 (1月17日リリース済み)の時点で実装されていました。 気づいてはいたけども、小さすぎてブログにするかどうか迷ってるうちに3週間ほど経過。)

以下のコードで示すような修正内容です。

// これがコンパイル エラーを起こす。
// (Visual Studio 17.9 Preview 3 以降を使うとコンパイルできるようになった。)
var c = new C { [^1] = 1 };

// これなら昔からコンパイル通る。
// (オブジェクト初期化子はこれと同じコードに展開されるはずなのに。)
c[^1] = 1;

class C
{
    // インデクサーと Length さえ持っていれば c[^i] と書けるようになる。
    // c[c.Length - i] 扱い。
    public int Length => 1;
    public int this[int i] { get => i; set { } }
}

まあ、^ を導入した時にオブジェクト初期化子は考慮漏れしてたんですかね。

こんなのでも一応悩むポイントはありまして。 1つは、例えば入れ子で new C() { [^1] = { [2] = 42, [3] = 43 } } とか書いたとき、

// 2行に分かれる = Length - 1 の計算が2度走る。
var c = new C();
c[^1][2] = 42;
c[^1][3] = 43;

// ^ の結果をキャッシュする。
var c = new C();
var cachedIndexArgument = ^1;
c[cachedIndexArgument][2] = 42;
c[cachedIndexArgument][3] = 43;

か、どちらがいいかという問題。

もう1つ、new C() { [^1] = { } } みたいに入れ子の部分が空っぽの場合、Length を評価する必要はあるかどうかとかも。

Length が副作用を持っている」とか「c[^1] が副作用で Length を書き換える」みたいな変なことをしているとこの辺りの結果が変わるわけで。

結局、以下のような選択をしたそうです。

  • 前者は、「^ の結果をキャッシュする」の方を選択
  • 後者は、[^1] = { } の時は評価しない(Length - 1 の計算自体せず、Length の getter は呼ばない) を選択

おまけ: ちょっと予告

あと、ちょこっと次回以降の予告。

しばらくブログ化していませんでしが C# 13 向けの作業がちらほら。 特に、2月に入ったくらいからアクティブで、結構検討が進んでいるみたいです。

最近見かけている話題を見出しだけ出しておくと以下の通り。

  • コレクション式の改善
  • ジェネリクスで ref struct を使えるように
  • Extensions
  • ジェネリクスの部分型推論 _
  • partial プロパティ
  • 破壊的変更がらみ

しばらくネタをため込んでしまったために大量に… 一気に書くと大変なので、次回以降、1個ずつブログにしようかと思います。