5日に、Visual Studio 2019 の 16.7 と、16.8 Preview 1 がリリースされました。

ということで、先週、ライブ配信もしていました。

16.7 が正式リリースになった記念に、Preview の頃に触れてた話題を改めてちょこっと振り返ったのと、16.8 Preview 1 で新たに追加された C# 9.0 の3つの機能の話でした。

C# 9.0 に今回追加されたのは以下の3つです。

今日は主にこの3つについて説明。

Module Initializers

モジュール(exe (アプリ)や dll (ライブラリ))が読み込まれた時点で必ず1回呼ばれるメソッドを書けるようになりました。

以下のように、ModuleInitializer 属性を付けた静的メソッドが、モジュール読み込み時に呼ばれます。

using System;
using System.Runtime.CompilerServices;

class Init
{
    [ModuleInitializer]
    internal static void M1() => Console.WriteLine("Init.M1");

    [ModuleInitializer]
    internal static void M2() => Console.WriteLine("Init.M2");
}

静的コンストラクターでも近いことができるんですが、

  • 静的コンストラクター
    • そのクラスのメンバーに触れた時点で初めて呼ばれる ‐ 1度も使っていないクラスの静的コンストラクターは結局呼ばれない
    • 1つのクラスに1つ限り
  • Module Initializers ‐ クラスのメンバーを使っていようが使っていまいが、モジュール読み込み時に必ず呼ばれる ‐ 1クラスに複数持てる

みたいな差があります。 確実に、確定タイミングで呼ばれるというのもメリットですし、個別に静的コンストラクターを持つよりはちょっとだけパフォーマンス的にも都合がいいみたいです。

今、Source Generatorって機能の実装も進められていて、これが入ると、たぶん「各クラスについて1回限り走らせたい処理」みたいなものは結構あると思います。 例えば自分が必要に迫られているものだと、リフレクションが使えない環境で自前でリフレクションに代わる型情報を持つみたいなコードなんですけども、 これが「確実に、確定タイミング」になってくれるのは結構ありがたかったりします。

Static anonymous functions

匿名関数 (ラムダ式匿名メソッド式)に対して static 修飾を付けて、キャプチャの抑止ができるようになりました。

using System;

// OK
Action staticLambda = static () => { };
Action staticAnonymousMethod = static delegate () { };

// コンパイル エラー
int local = 1;
Action badStaticLambda = static () => Console.WriteLine(local);
Action badStaticAnonymousMethod = static delegate () { Console.WriteLine(local); };

これは割かし、「工数的な問題で 8.0 に入らなかっただけ」系の機能です。 C# 8.0 時点でローカル関数に関しては同様の機能が入っていて、 匿名関数でも同様の需要があることはわかっていましたが、 文法的にちょっとめんどくさいので後回しになっていたものです。

Target-Typed Conditional Expression

条件演算子 (? :)で、第2項と第3項で共通の型を決められないときに、ターゲット型を見て型を決定できるようになりました。

void targetTypedConditional(bool x)
{
    // target-typed で、1 : null の部分がちゃんと int? になる。
    int? v1 = x ? 1 : null;

    // あくまで target-typed で判定してるので、以下のような推論は働かない(コンパイル エラー)。
    // 1 と null の「共通型」は確定できない。
    //var v2 = x ? 1 : null;
}

switchの場合には C# 8.0 時点であった機能です。新しい文法である switch 式と違って、既存の文法に手を入れるのはリスクもある(というか、実際、ちょっと破壊的変更を起こしてる)ので 8.0 には間に合わなかった機能です。