個人的なメモ

Tomohiro Suzuki @hiro128_777 のブログです。Microsoft MVP for Developer Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

C# 10 の新機能 - レコード構造体

C# 10 の新機能の情報の目次は以下をご覧ください。
hiro128.hatenablog.jp 
 

レコード構造体

C# 9 でレコードクラスが追加されましたが、C# 10 ではレコード構造体が追加されました。
C# 10 ではレコード構造体が追加されたことで、クラスより速度が出る構造体でレコード型を利用可能になりました。
 

導入の意図

レコード型(レコード構造体、レコードクラス)は、簡潔なコードで便利にデータを格納するための型です。

通常のクラスや構造体でデータモデルを作成しようとすると、データとしての等価性をサポートするための Equals のオーバーライドや operator == のオーバーロード、データ表示用の書式設定のための ToString のオーバーライドやデータを不変にするための構文など面倒な手間が生じます。

レコード型を利用すると、不変なプロパティを持つ型を簡潔な構文で作成でき、データ指向の型に役立つ動作が組み込みでサポートされているので、データモデルを簡潔に定義できます。
 

値の等価性

レコード構造体(レコードクラスも含む)は等価性に特徴があります。

  • record class 型の場合、型が同じで同じ値が格納されていれば、2 つのオブジェクトは等しい。
  • record struct 型の場合、型が同じで同じ値が格納されていれば、2 つのオブジェクトは等しい。

比較として、レコード型ではないクラスや構造体の等価性は以下の通りです。

  • class 型の場合、メモリ内の同じオブジェクトを参照していれば、2 つのオブジェクトは等しい。
  • struct 型の場合、型が同じで同じ値が格納されていれば、2 つのオブジェクトは等しい。

 

コード
// レコード構造体
PersonStruct person01 = new("太郎", "山田", new DateOnly(1990, 4, 15));
PersonStruct person02 = new("太郎", "山田", new DateOnly(1990, 4, 15));

Console.WriteLine("レコード構造体");
Console.WriteLine(person01 == person02);


// レコードクラス
PersonClass person03 = new("太郎", "山田", new DateOnly(1990, 4, 15));
PersonClass person04 = new("太郎", "山田", new DateOnly(1990, 4, 15));

Console.WriteLine("レコードクラス");
Console.WriteLine(person03 == person04);

record struct PersonStruct(string FirstName, string LastName, DateOnly Birthday);
record class PersonClass(string FirstName, string LastName, DateOnly Birthday);

 

実行結果
レコード構造体
True
-----------
レコードクラス
True

 

表示用の組み込みの書式設定

レコード型では既定で ToString メソッドで、パブリック プロパティとフィールドの名前と値が表示されます。
これは、データを取り扱う型としてはとても便利です。

<record type name> { <property name> = <value>, <property name> = <value>, ...}

 

コード
PersonStruct person01 = new("太郎", "山田", new DateOnly(1990, 4, 15));
Console.WriteLine(person01);

record struct PersonStruct(string FirstName, string LastName, DateOnly Birthday);

 

実行結果
PersonStruct { FirstName = 太郎, LastName = 山田, Birthday = 4/15/1990 }

 

with 式

with 式を使うと、元のインスタンスの値はそのままに、指定したプロパティとフィールドが変更された、コピーを作成できます。
膨大なプロパティを持つレコード型の一部のプロパティだけを変更したコピーを簡潔な構文で作成できるので非常に便利です。

補足ですが、C#10 以降ではレコード型ではない、struct 型も with 式 をサポートしています。
 

コード
PersonStruct person01 = new("太郎", "山田", new DateOnly(1990, 4, 15));
PersonStruct person02 = new("太郎", "山田", new DateOnly(1990, 4, 15));

person02 = person01 with { FirstName = "花子" };
Console.WriteLine(person02);
Console.WriteLine(person01 == person02);

record struct PersonStruct(string FirstName, string LastName, DateOnly Birthday);

 

実行結果
PersonStruct { FirstName = 花子, LastName = 山田, Birthday = 4/15/1990 }
False

 
なお、レコード構造体はC# 9 のクラスベースのレコードと似ていますが、重要な違いがあります。

  • レコード構造体のプロパティは、デフォルトで変更可能(get / set)です。
  • レコードクラスのプロパティは、デフォルトでは不変(get / init)です

初期化後に init キーワードが設定されているプロパティを再割り当てしようとすると、コンパイルエラーが発生します。
※readonly キーワードを追加することでレコード構造体を不変にできます。

レコード構造体はレコード クラスに取って代わるものではなく、レコード クラスからレコード構造体への移行を推奨するものではありませんので、クラスと構造体のどちらのレコードを使用するかは、利用シナリオに合わせ選択する必要があります。
 
公式情報:
Record structs
レコード (C# リファレンス)
 
 

C# 10 の新機能

はじめに

C# 10 の新機能の特長は、「簡潔なコードを書くための機能」が多く追加されていることです。
 
本記事では、C# 10 の新機能のうち便利なものをいくつかピックアップしました。新しい機能を利用することでお作法的なコードを排除でき、簡潔で直感的なコードを記述できるのでぜひ確認してみてください。

C# 10 の新機能の全てをご確認したい場合については以下の公式情報をご参照ください。
docs.microsoft.com
 
.NET 6 の新機能の情報は以下をご覧ください。
hiro128.hatenablog.jp
 

レコード構造体

レコード構造体については以下の記事を参照ください。
hiro128.hatenablog.jp
 

global using

global using については以下の記事を参照ください。
hiro128.hatenablog.jp
 

暗黙的な using ディレクティブ

暗黙的な using ディレクティブについては以下の記事を参照ください。
hiro128.hatenablog.jp
 

ファイルスコープの名前空間宣言

ファイルスコープの名前空間宣言については以下の記事を参照ください。
hiro128.hatenablog.jp
  

ラムダ式の自然型

ラムダ式の自然型については以下の記事を参照ください。
hiro128.hatenablog.jp
 

const および文字列補間

const および文字列補間については以下の記事を参照ください。
hiro128.hatenablog.jp
 

拡張プロパティパターン

拡張プロパティパターンについては以下の記事を参照ください。
hiro128.hatenablog.jp

 
  

ASP.NET Core Update(.NET 6)の整理(随時更新中)

.NET 6 の RC 2 がリリースされました。ASP.NET Core についても Preview 1 から RC 2 まで段階的に情報や新機能がリリースされており、情報が分散して探しにくいので重要な更新情報を整理してみました。
  
公式情報も更新されているため、内容は随時更新中です。
 
.NET 6 の更新情報は以下をご覧ください。
hiro128.hatenablog.jp
 
 

 
 

Minimal API の導入(.NET 6 Preview 4)

公式情報:
Introducing minimal APIs
関連記事:
hiro128.hatenablog.jp
 

Minimal API のアップデート(.NET 6 RC 2)

Minimal API に対し、以下のいくつかのアップデートがあります。
公式情報:Minimal API updates
 

パラメータのバインディングの対応強化(.NET 6 RC 2)

  • TryParseBindAsync に継承されたメソッドに対するサポートを追加
  • パブリックの TryParseBindAsync メソッドが正しい形式かどうかをチェックする機能が追加
  • これらのメソッドに間違った構文を使っている場合エラーがスローされるようになった
  • カスタムタイプにルート、ヘッダ属性、およびクエリ文字列から値をバインドしたい場合、カスタムタイプに静的な TryParse メソッドを追加することができる

公式情報:Parameter Binding
 

OpenAPI の対応強化(.NET 6 RC 2)

  • Accepts() 拡張メソッドや [Consumes(typeof(Todo), "application/json", IsRequired = false)] 属性を使って、リクエストボディが必須かどうかを記述できる
  • Accepts 拡張メソッドとConsumes 属性で、生成された open-api docs に対して、Todo タイプと application/json の両方を表現することができる。

公式情報:OpenAPI
 

ソースコードの分析(.NET 6 RC 2)
  • ルートハンドラの問題を素早く発見したり、ミドルウェアの設定ミスを警告したりするためのアナライザを追加
  • アナライザは、WebApplicationBuilder のミドルウェアの順序を検証し、誤ったミドルウェアの構成や順序が検出された場合に警告する
  • IActionResult を実装した型がルートハンドラから返された場合に検出し、結果が意図せずに JSON としてシリアル化された場合に警告する機能を追加

公式情報:Source Code Analysis
 

APIの名前変更(破壊的変更)(.NET 6 RC 2)

  • 意図を適切に説明するために以下のAPIの名称を変更した。
    • DelegateEndpointConventionBuilder -> RouteHandlerBuilder
    • OpenApiDelegateEndpointConventionBuilderExtensions -> OpenApiRouteHandlerBuilderExtensions
    • DelegateEndpointRouteBuilderExtensions は、既存のEndpointRouteBuilderExtensions と統合した。

(上記の変更により、DelegateEndpointRouteHandlerに置き換え、クラス名のConventionを削除した)

公式情報:Breaking Changes (APIs Renames)
 

非同期ストリーミング(.NET 6 Preview 4)

  • コントローラのアクションからレスポンスのJSONフォーマッタに至るまで、非同期ストリーミングがサポートされるようになった
  • アクションからIAsyncEnumerableを返しても、レスポンスの内容が送信される前にメモリにバッファリングされなくなった
  • 非同期に列挙できる大規模なデータセットを返す際のメモリ使用量が削減される

公式情報:Async streaming
 

HTTP logging ミドルウェア(.NET 6 Preview 4)

  • HTTPリクエストとHTTPレスポンスの情報(ヘッダーとボディ全体を含む)を記録する、新しい組み込みミドルウェア
  • HTTP logging は以下のログを提供する
    • HTTP リクエスト情報
    • 共通のプロパティ
    • ヘッダー
    • ボディ
    • HTTP レスポンス情報

公式情報:HTTP logging middleware
 

.NET MAUI Update の整理(随時更新中)

.NET MAUI の Preview 14 がリリースされました。Preview 1 から Preview 14 まで段階的に情報や新機能がリリースされており、情報が分散して探しにくいので重要な更新情報を整理してみました。
  
公式情報も更新されているため、内容は随時更新中です。
 
.NET 6 の新機能の情報は以下をご覧ください。
hiro128.hatenablog.jp
 
 

.NET MAUI、 Xamarn.Forms からの改良ポイント

.NET MAUI、 Xamarn.Forms からの改良ポイントの記事はこちらです。
hiro128.hatenablog.jp
 

.NET MAUI Preview 14 の更新情報

.NET MAUI Preview 14 の更新情報はこちらです。
hiro128.hatenablog.jp
 

.NET MAUI Preview 13 の更新情報

.NET MAUI Preview 13 の更新情報はこちらです。
hiro128.hatenablog.jp
 

.NET MAUI Preview 12 の更新情報

.NET MAUI Preview 12 の更新情報はこちらです。
hiro128.hatenablog.jp
 

.NET MAUI Preview 11 の更新情報

.NET MAUI Preview 11 の更新情報はこちらです。
hiro128.hatenablog.jp
 

.NET MAUI Preview 10 の更新情報

.NET MAUI Preview 10 の更新情報はこちらです。
hiro128.hatenablog.jp
 

.NET MAUI Preview 9 の更新情報

.NET MAUI Preview 9 の更新情報はこちらです。
hiro128.hatenablog.jp
 

.NET MAUI Preview 8 の更新情報

.NET MAUI Preview 8 の更新情報はこちらです。
hiro128.hatenablog.jp 
 

.NET MAUI Preview 7 の更新情報

.NET MAUI Preview 7 の更新情報はこちらです。
hiro128.hatenablog.jp

 
以下、随時更新中です。
 
 

.NET 6 の Scatter/Gather IO の効果を Visual Studio for Mac 2022 Preview 1 on M1 Mac で試してみましたが、まだ、闇が深かったです(訂正済)

2021/10/05 22:40 検証方法に間違いがあったため(VS for Mac がデバッグありの実行になっていました)、再度検証し直し記事も訂正しております。ご指摘ありがとうございました。
 
 

はじめに

.NET 6 では FileStream がほぼ完全に書き直されており、速度と信頼性のが高まりました。また、複数のバッファーに対応した(Scatter / Gather IO)新しい API が導入されています。Scatter / Gather IO を使用すると、単一の sys-call で複数のバッファーを渡すことにより、高コストの sys-call の数を減らすことができます。
 
devblogs.microsoft.com
 
 

Visual Studio for Mac 2022 Preview 1 on M1 Mac で試したら...

WindowsLinux でのベンチマーク結果は公式ブログにデータがありましたので、Mac ではどのような結果になるか試してみました。
 
公式ブログの記事内のコードから、サンプルプロジェクトを作成しました。
github.com
 
ベンチマークのライブラリは、BenchmarkDotNet を利用しているようなので、同じように構成しました。
github.com
 
Visual Studio for Mac 2022 Preview 1 で実行した結果ですが...

// * Summary *

BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.6 (20G165) [Darwin 20.6.0]
Apple M1 2.40GHz, 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.100-rc.1.21463.6
  [Host]     : .NET 6.0.0 (6.0.21.45113), X64 RyuJIT
  DefaultJob : .NET 6.0.0 (6.0.21.45113), X64 RyuJIT


|      Method |     Mean |    Error |   StdDev |
|------------ |----------|----------|----------|
|       Write | 47.92 ms | 1.109 ms | 3.271 ms |
| WriteGather | 48.51 ms | 1.567 ms | 4.621 ms |

 
なんと上記の通り、WriteGather()の方が遅いという結果となりました...
 
 

ベクトル化 IO がうまく機能していない

詳細を解析していないのでなんとも言えないですが、公式ブログでは、Linux ではベクトル化 IO が機能しているとのことでしたが、Mac ではなんらかの理由でうまくベクトル化 IO が機能していない感じです。

一応、.NET 6 と Darwin のソースを調べてみましたが、pwritev() はサポートしているようですが、ベクトル化 IO がうまく機能していない詳細な原因まではわかりませんでした。
 
...と、いったんは思ったのですが、よく見ると、ベンチマーク結果が、X64 となっているので、Rosetta 2 による変換が入っているのが気になります。

ちなみに、意識せず x64 版がインストールされていたのは、dotnet-maui-check でインストールしたからでした。
 
 

Arm64 版 SDK をインストールして再度チャレンジ

というわけで、.NET 6 RC1 SDK をいったん全て削除して、再度 Arm64 版 SDK をインストールして Visual Studio for Mac 2022 Preview 1 で再度試したところ、今度は、無事成功しました。しかも、WriteGather()の方が速く、ベクトル化 IO も効果を発揮しています。

// * Summary *

BenchmarkDotNet=v0.13.1, OS=macOS Big Sur 11.6 (20G165) [Darwin 20.6.0]
Apple M1, 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.100-rc.1.21463.6
  [Host]     : .NET 6.0.0 (6.0.21.45113), Arm64 RyuJIT
  DefaultJob : .NET 6.0.0 (6.0.21.45113), Arm64 RyuJIT


|      Method |     Mean |    Error |   StdDev |
|------------ |----------|----------|----------|
|       Write | 45.11 ms | 0.950 ms | 2.772 ms |
| WriteGather | 40.23 ms | 1.304 ms | 3.844 ms |

 
 

今回得た知見

今回の検証で以下のような知見を得ました。

  • M1 Mac で .NET 6 の、Scatter/Gather IO は Arm64 版 でしか有効にならない。x64 版では効果がない。
  • M1 Mac で .NET 6 SDK の、x64, Arm64 を両方インストールした場合、同じ場所に配置され、後にインストールした方が有効となる。side by side にはならない。
  • M1 Mac で .NET 6 SDK の、x64, Arm64 を切り替えたい場合、念のため SDK をいったん削除してから再インストールした方が無難そう。
  • M1 Mac で Visual Studio for Mac 2022 Preview 1 では Arm64 版 SDK でのデバッグ実行がまだ正常動作しない。x64 版 SDK/Rosetta 2 ならデバッグ実行できる。

 
というわけで、Visual Studio for Mac 2022 の今後のリリースに期待します。
 
 
.NET 6 の更新情報は以下をご覧ください。
hiro128.hatenablog.jp