.NET 5での嬉しい変更点

こちらは[DeNA 21 新卒 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/dena-21-shinsotsu)の10日目の記事です。

正式リリースされたばかりの.NET 5についての情報を自分の中でまとめたかったため、今回の記事を書きました。
主に[Announcing .NET 5.0](https://devblogs.microsoft.com/dotnet/announcing-net-5-0/)の中から個人的に嬉しい変更点を紹介・説明していきます。
## .NET 5について
2020/11/20に.NET 5.0が正式リリースされました。

.NET系について簡単に整理すると、

- 今後、.NET Frameworkにはバグ修正とセキュリティ修正が行われる
    - 明記されていないが、新機能は恐らく追加されない
        - [.NET Core is the Future of .NET](https://devblogs.microsoft.com/dotnet/net-core-is-the-future-of-net/)
- .NET Core 3.0の次期バージョンが.NET 5.0となる

となっています。

.NET Coreがバージョン4を飛ばした理由は、[Introducing .NET 5](https://devblogs.microsoft.com/dotnet/introducing-net-5/)に明記されています。
> We’re skipping the version 4 because it would confuse users that are familiar with the .NET Framework, which has been using the 4.x series for a long time. Additionally, we wanted to clearly communicate that .NET 5 is the future for the .NET platform.

要約すると「長年使われている.NET Framework 4.x系と混同しちゃうかもしれないから」ということのようです。

.NET 5.0では、主にパフォーマンス向上が重点的に行われました。加えて、いくつか新機能も追加されたようです。
今回は、その中でも個人的に嬉しかった変更点についてピックアップしたいと思います。

## HttpClientにJSON拡張メソッド追加
[HttpClient extension methods](https://devblogs.microsoft.com/dotnet/announcing-net-5-0/#httpclient-extension-methods)

C#でhttp経由の通信を行う際に、よくHttpClientが使われます。
いままで、JSON形式のレスポンスを取得する際は、以下のように3ステップに分けて実装を行う必要がありました。

```cs
// .NET Core 3以前
var client = new HttpClient();
var response = await client.GetAsync("https://example.com/example.json"); // 1.レスポンスを受け取る
var jsonBody = await response.content.ReadAsStringAsync(); // 2.コンテンツを文字列として読み出し
var data = JsonSerializer.Deserialize<Message>(jsonBody); // 3.デシリアライズ
```

これだと、API通信をよく行うアプリでは、書き方が冗長で非常に面倒です。
.NET 5.0では、上記の実装がよりシンプルになりました。

```cs
// .NET 5.0
using System.Net.Http.Json;

var client = new HttpClient();
var data = await client.GetFromJsonAsync<Message>("https://example.com/example.json");
```

たったこれだけです。すごい！

加えて、忘れがちな以下の処理が実装されています。

- ステータスコードの判別
  - Success(200~299)以外では`HttpRequestException`が発生
- 文字コードの自動解析
  - デフォルトではUTF-8
  - `Content-Type`に`charset`が含まれる場合は、その文字コードでデコード

ただし、レスポンスの`Content-Type`が`application/json`になっているかは判定していない模様です。`text/plain`で試したところ、エラー無しでJSONとしてパースされました。（3/31時点の[記事](https://www.stevejgordon.co.uk/sending-and-receiving-json-using-httpclient-with-system-net-http-json)では判定していたようですが、その後削除された？）
 
上記では、`GET`メソッドを例として書きましたが、`POST`と`PUT`でも同様の拡張メソッドが追加されました。(`PATCH`や`DELETE`などには追加されていない模様です)
もちろん、`POST`と`PUT`では引数に何らかのオブジェクトを渡すとJSONシリアライズして送信してくれます。
### HttpContent拡張メソッド
HttpContentクラスに拡張メソッドとして`ReadFromJsonAsync`が追加されました。
こちらを使うことで、任意のHttpContentからJSONデータを扱えるようになります。

使用する場面としては、以下が挙げられます。

- `PATCH`や`DELETE`リクエストのレスポンスをJSONとして扱いたい
- Success(200~299)以外のステータスコードでも、レスポンス内容をJSONとして扱いたい

```cs
var client = new HttpClient();
var response = await client.GetAsync("https://example.com/example.json");

if (response.IsSuccessStatusCode)
{
  // OK処理
  var data = await response.content.ReadFromJsonAsync<Message>();
}
else
{
  // NG処理
  var data = await response.content.ReadFromJsonAsync<ErrorInfo>();
}
```

### JsonContent
HttpContentを継承した[JsonContent](https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.json.jsoncontent?view=net-5.0)クラスが、拡張メソッドの追加に伴って追加されました。

以下のようなコードを書くことで、`Content-Type: application/json`を設定済みのJsonContentを生成できます。(`charset`に応じてエンコードしてくれます。デフォルトではUTF-8)

```cs
var content = JsonContent.Create<Message>(message); // Content-Type: application/json; charset=utf-8
```

### .NET 5.0以前でも使いたい！
.NET 5.0に変更せずとも、.NET Core 2.0または.NET Framework 4.6.1以上であれば[System.Net.Http.Json](https://www.nuget.org/packages/System.Net.Http.Json/)パッケージを追加すれば、同様の機能が使用できます。

## Windows ARM64対応
[Windows Arm64](https://devblogs.microsoft.com/dotnet/announcing-net-5-0/#windows-arm64)

ARM64環境において、いままでの.NET Coreと.NET Frameworkはx86エミュレーション環境で動作していました。
.NET 5.0では、ARM64にネイティブ対応するため、より高速に動作するようになるようです。

ただし、Windows Forms及びWPFに関する機能は対応しておらず、.NET 5.0の今後のアップデートで提供するかもしれないみたいです。

### M1 Mac(Apple Silicon)対応は？
Apple Siliconには、現状ネイティブ対応していません。ネイティブ対応は.NET 6で提供予定とのことです。([該当Issue](https://github.com/dotnet/runtime/issues/43313))
加えて、Rosetta 2でのデバッグにも対応しておらず、コンパイルはできますがデバッグすると以下のエラーで落ちます。

```
Stack overflow.
   at System.Collections.HashHelpers.GetPrime(Int32)
   at System.Collections.Generic.Dictionary`2[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].Initialize(Int32)
   at System.Collections.Generic.Dictionary`2[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]..ctor(Int32, System.Collections.Generic.IEqualityComparer`1<System.__Canon>)
   at System.AppContext.Setup(Char**, Char**, Int32)
```

既に[Issue](https://github.com/dotnet/runtime/issues/44897)は上がっており、.NET 5のうちにRosetta 2上で対応する予定のようです。
手元の環境（M1 Mac mini）でもデバッグできませんでした。（デバッグなしで実行すれば動作しました）
## シングルファイルアプリケーション
[Single file applications](https://devblogs.microsoft.com/dotnet/announcing-net-5-0/#single-file-applications)

シングルファイルアプリケーションとは、その名の通り単一の実行ファイルで動くアプリのことです。

いままでは、各種バイナリを一つのファイルに圧縮し、実行時にそれらを一時ディレクトリに展開することで、単一ファイルを実現していました。ですが、.NET 5.0から一時ディレクトリに展開せずとも動作するようになります。
ただし、.NET 5.0からは単一ファイルと言いつつ複数ファイルが出力されるパターンがあります。以下の表に構成とファイル数を記しておきます。

| | Windows x64 | Linux x64 | macOS x64 |
| --- | ------- | ------- | ------- |
| Self-Contained[^1] | 5 | 1 | 8 |
| Framework-Dependent[^1] | 1 | 1 | 1 |

[^1]: パブリッシュ方法。Self-Containedは.NETランタイムも含める。Framework-Dependentは.NETランタイムを除く。

複数ファイルが出力される原因は、CLRなどのネイティブバイナリが別で出力されてしまうためです。これを解決するためには、ネイティブバイナリも含めて出力する必要があります。
これにはプロジェクトファイルの`IncludeNativeLibrariesForSelfExtract`を`true`に設定することで実現できます。ただし、この方法を用いると.NET Core 3.1と同じように一時ディレクトリに展開してから実行されるようになります。([参考](https://github.com/dotnet/designs/blob/main/accepted/2020/single-file/design.md#optional-settings))

```XML
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
  </PropertyGroup>

</Project>
```

## 終わりに
.NET 5.0では、上述した機能の追加が行われました。加えて、パフォーマンスもかなり改善されました。RyuJITやGCレベルでのパフォーマンス改善のため、利用者側としては.NET 5.0に切り替えるだけでパフォーマンス向上が期待できます。
Apple Siliconにネイティブ対応するであろう.NET 6も楽しみです！

この記事を読んで「面白かった」「学びがあった」と思っていただけた方、よろしければ LGTM、Twitter や Facebook、はてなブックマークにてコメントをお願いします！

また [DeNA 公式 Twitter アカウント @DeNAxTech](https://twitter.com/DeNAxTech) では、 Blog 記事だけでなく色々な勉強会での登壇資料も発信しています。ぜひフォローして下さい！
<a href="https://twitter.com/DeNAxTech?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-size="large" data-lang="ja" data-show-count="false">Follow @DeNAxTech</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>