銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

.NET 5 関連の新機能をまとめてみた

11月の.NET Confでリリースが予定されているされた .NET 5 関連の新機能をできるだけまとめてみました。ASP.NET CoreとEF Coreは除いています。

リリースにあわせていくつか更新しています。

そしてそんな .NET 5に興味のある方はぜひこちらのイベントにご参加ください。

csharp-tokyo.connpass.com

.NET 5 のダウンロード

dotnet.microsoft.com

hub.docker.com

.NET 5 のリリースモデル、ロードマップなど

  • リリースは、メジャー、マイナー、サービス更新の3種類から構成されます。
  • .NET 5が「Current」リリースで、1年後にリリース予定の.NET 6が「LTS」となる予定。その後、1年ごとに「Current」「LTS」を交互にリリース予定。
  • Currentは次のメジャーもしくはマイナーリリースがでて3か月、LTSのサポートは3年もしくは次のLTSが出てから1年の長いほう(つまり、最低3年間はサポート)。
  • サポートはマイクロソフトが提供するサポートおよびコミュニティによるサポートが提供されます。Red Hatなどが提供するもの(RHEL向け.NETなど)は別途サポートが提供される場合があります。
  • .NET Framework は4.8が最後のメジャーリリース。ただし、Windowsから削除する予定はなく、サポートとサービス提供は継続予定。

.NET 5のアップデート

  • C# 9とF# 5へアップデート
    • コンパイル時にコード生成を可能にするC# Source Generatorの導入
  • Target Framework (TFM)はクロスプラットフォームなものとOS固有のものを表すものの大きく2種類へ
  • .NETがAPIとして提供しているのに特定のOS(特にWindows)のみで動作するAPI(例えばRegistry.CurrentUser)に対して、使用を検出するアナライザーとコードフィクサーを提供する
  • さまざまなパフォーマンス改善
  • ARM64対応
  • 配布オプションの追加
    • ClickOnceでのクライアントアプリの展開
    • 単一の実行ファイルへの対応
    • コンテナイメージサイズの縮小とcgroup v2への対応
    • Server Coreイメージの追加
  • JsonSerializer の改善 JsonSerializer improvements in .NET 5 · Issue #41313 · dotnet/runtime · GitHub
  • null許容参照型のアノテーション導入の官僚 Annotate remainder of .NET Core assemblies for nullable reference types · Issue #2339 · dotnet/runtime · GitHub
  • .NET5の一部としてWinRT APIをサポート
  • ネイティブコード運用の改善
  • IEEE 754のbinary16 に対応する数値型としてHalf typeの導入
  • self-contained(自己完結型)アプリケーションの配布サイズを小さくするための アプリケーショントリミングの導入
  • アプリケーションコードの潜在的なバグを見つける分析機能を.NET SDKにデフォルトで導入
  • オブザーバビリティ(OpenTelemetry)やマイクロサービス(Tye)対応への取り組みを進める

破壊的変更については以下のドキュメントでまとめられています。

docs.microsoft.com

参考資料

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

blogs.windows.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

devblogs.microsoft.com

System.Text.JsonやJson.NET で一部だけキーが可変なJSONを処理する

とあるREST APIのエンドポイントが /values/xxx みたいなエンドポイントに対して次のような形式のJSONを返します(実際にはこの3倍くらい属性があり、listの中の配列も多数あります)。XXXで指定した名前の値のリストを取得するというイメージで、この名前のパターンは無数にあります。

{
  "Value": {
    "Key1": "value1",
    "Data": [
      {
        "Timestamp": "2020-10-29T13:02:00.000Z",
        "XXX": 123.45
      },
      {
        "Timestamp": "2020-10-29T13:03:00.000Z",
        "XXX": 456.78
      }
    ]
  }
}

つまり、XXXの部分が可変であるため、単純にC#のクラスにマッピングさせることができないのです。かといって、属性1つのためにJSON全体の構造をdynamicなりJsonDocument(JObject)として扱うのもつらいものがあります。 そこで使える機能が、クラスのメンバーに存在しないJSONのキーをC#側でDictionary型のオブジェクトとして保持することができます。この機能は、System.Text.JsonでもJson.NetでもJsonExtensionDataという同じ名前の属性で使えるのですが、名前空間と指定できる対象が異なります。

Json.NET では名前空間がNewtonsoft.Jsonであり、プロパティもしくはフィールドに指定できます。

www.newtonsoft.com

つぎのようなクラスを用意しておくと、JSONからC#に変換するときはTimestamp以外のJSONのキーとその値は_additionalDataに格納され、C#からJSONに変換するときはこの辞書のキーと値がJSONのキーと値として追加sれます。

public class Data
{
    public DateTime Timestamp{ get; set; }

    [JsonExtensionData]
    private Dictionary<string, JToken> _additionalData;
}

System.Text.Jsonでは名前空間がSystem.Text.Json.Serializationであり、プロパティにしか指定できません。

docs.microsoft.com

public class Data
{
    public DateTime Timestamp{ get; set; }

    [JsonExtensionData]
    public IDictionary<string, object> AdditionalData;
}

このクラスに最初の例のJSONを変換すると、DictionaryにはキーがXXXで値が数値のエントリーが一つ入った状態になります。さて、まずはこれで目的を達したのですが、便利さのためにこのXXXというキーの名前とその数値をKeyNameValueというプロパティに格納することを考えましょう。 Json.NETではドキュメントに書いてある通り、OnDeserialized属性を使って、C#からJSONへの変換が完了したときに実行できるコードをそのクラスの中に定義することができます。

public class Data
{
    public DateTime Timestamp{ get; set; }

    public string KeyName { get; set; }
    public double Value { get; set; }
    
    [JsonExtensionData]
    private Dictionary<string, JToken> _additionalData;

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        var first = segment.ExtensionData.FirstOrDefault();
        if (first.Key != null)
        {
            KeyName = first.Key;
            Value = (double)first.Value;
        }
    }

    public DirectoryAccount()
    {
        _additionalData = new Dictionary<string, JToken>();
    }
}

これに対し、System.Text.Jsonではこのドキュメントにあるとおり、カスタムコンバーターを作成することで対応できます。

docs.microsoft.com

今回のケースだとこのようになるでしょう。

public class GetMetricSegmentCallbacksConverter : JsonConverter<GetMetricSegment>
{
    public override GetMetricSegment Read(
        ref Utf8JsonReader reader,
        Type type,
        JsonSerializerOptions options)
    {
        GetMetricSegment result = JsonSerializer.Deserialize<GetMetricSegment>(ref reader);
            
        var first = result.ExtensionData.FirstOrDefault();
        if (first.Key != null)
        {
            result.Key = first.Key;
            result.Value = (double)first.Value;
        }

        return result;
    }

    public override void Write(
        Utf8JsonWriter writer,
        GetMetricSegment result, JsonSerializerOptions options)
    {
        result.ExtensionData = new Dictionary<string, object>{{result.Key, result.Value}};
        JsonSerializer.Serialize(writer, result);
    }
}

いずれのケースも2つ以上オーバーフローしたキーが格納されているケースなどには対応できないので、適宜APIの仕様に合わせて対応しましょう。