20210419のC#に関する記事は8件です。

C# Directory と File (1)

これから、スピードアップの為に、自分がどうだったっけ?と Google に聞くのではなく、一般的に使うものは正確に記憶していこうと思う。 今後、この分類のブログを書いていくときは、質問を先に書いて、後で復習出来るようにしてみたい。私のチームの扱うものから考えると下記のような内容がカバー出来ればかなり早くコーディングが出来そうだ。 ファイル操作 リフレクションと、アセンブリの操作 LINQ ネットワークオペレーション (HttpClient, HttpClientFactory) SDKの使用 (主に Azure SDK) テスティングフレームワーク ASP.NET データベースアクセス 証明書 コマンドの実行 IDE (Visual Studio) 上記のものに似た内容を Go lang で。 ファイル・ディレクトリの操作 ファイルの書き込み、読み込みの方法とそのインターフェイスは? ファイルの存在チェックの方法は? ファイルのメタデータの取得方法、およびセットの方法は? カレントディレクトリの取得 ディレクトリの取得、作成、移動の方法とそのインターフェイスは? ディレクトリの存在チェックの方法は? ディレクトリのメタデータの取得方法、およびセットの方法は? ディレクトリに含まれる、ファイルおよび、ディレクトリのの取得の方法は? サブディレクトリの内容の取得 おそらく上記のことが出来れば十分であろう。これらを何も見ずに書けるようにしてみる。 ファイルの書き込み、読み込み、およびそのインターフェイス ファイルの書き込みには2つの方法がある StreamWriter コンストラクタは下記の通り、基本テキストファイルを読み込むもので、デフォルトは、UTF-8 になる。バッファサイズは、デフォルト 1024. StreamWriter を使うととても簡単に書ける。static File クラスを使うことが出来る。File クラスのメソッドは、基本的に便利メソッドの集まりと考えてよい。 using (var writer = new StreamWriter(fileName)) { writer.WriteLine("Hello "); } もしくはFile.WriteAllText() でもファイルを書ける。 var heredoc = @"This is an overwriten hello world! "; File.WriteAllText(fileName, heredoc); ここで、ふと思ったのだけど、Append モードで書くときはどうするのかな?と思った。 FileStream クラスを使うと、Append モードでオープン出来る。 byte[] data = new UTF8Encoding(true).GetBytes("Additional text"); using (var fs = new FileStream(fileName, FileMode.Append)) { fs.Write(data, 0, data.Length); // We can also use ReadOnlySpan } 正直めんどい。低レベルのコーディングではこのインターフェイスが使えそう。Lockなどのメソッドもあるので、おそらくネイティブな使い道だろう。 もっと楽な方法がある。StreamWriter のコンストラクタには、Stream を受け付けるものがあるので、それを使って、Append モードの FileStreamを渡してあげればオッケー。簡単である。 using(var fs = new FileStream(fileName, FileMode.Append)) using (var writer = new StreamWriter(fs)) { writer.Write("more additional line"); } こういうことをしていると、Stream のクラス構造が気になるところ。 TutorialsTeacher - C# Streamより 実際にはもっとたくさんあるが、System.IO.Streamこの程度は覚えておきたい。 FileStream: 物理的なファイルに、同期もしくは、非同期読み書きを行う BufferedStream: 他のストリームに対して、バッファリングレイヤを設けてパフォーマンスを向上させる。継承は出来ない。Discussion BufferedStream Class NetworkStream: ネットワークアクセスのストリーム NetworkStream Class PipeStream: パイプオペレーションのストリーム PipeStream Class MemoryStream: MemoryStream Class インメモリのストリーム。 CryptoStream: CryptoStream Class 暗号化用のストリーム 他にも、gzip 用のストリームやデータベース用のものなど様々なものが用意されている。ここでは、オーバービューをとらえる程度で止めておこう。 ファイルの存在チェックは? if (File.Exists(fileName)) { File.Delete(fileName); } ファイルのメタデータの取得とセット Console.WriteLine($"Creation Time: {File.GetCreationTimeUtc(fileName)} LastAccessTime: {File.GetLastAccessTimeUtc(fileName)} LastWriteTime: {File.GetLastWriteTimeUtc(fileName)}"); Creation Time: 4/19/2021 12:48:24 AM LastAccessTime: 4/19/2021 8:04:11 AM LastWriteTime: 4/19/2021 8:04:11 AM カレントディレクトリ Console.WriteLine(Directory.GetCurrentDirectory()); 調べているうちに、深く潜りすぎたので、一旦ここで、アウトします。 public void Execute() { Console.WriteLine(Directory.GetCurrentDirectory()); string fileName = "hello.txt"; // Clean up the file. if (File.Exists(fileName)) { File.Delete(fileName); } using (var writer = new StreamWriter(fileName)) { writer.WriteLine("Hello "); } Console.WriteLine($"{fileName}: {File.ReadAllText(fileName)}"); // Read FileInfo. FileInfo fi = new FileInfo(fileName); using (var writer = fi.CreateText()) { writer.WriteLine("World!"); } Console.WriteLine($"{fileName}: {File.ReadAllText(fileName)}"); var heredoc = @"This is an overwriten hello world! "; File.WriteAllText(fileName, heredoc); Console.WriteLine("last one, "); Console.WriteLine($"{fileName}: {File.ReadAllText(fileName)}"); byte[] data = new UTF8Encoding(true).GetBytes("Additional text"); using (var fs = new FileStream(fileName, FileMode.Append)) { fs.Write(data, 0, data.Length); // We can also use ReadOnlySpan } Console.WriteLine("---"); Console.WriteLine($"{fileName}: {File.ReadAllText(fileName)}"); using(var fs = new FileStream(fileName, FileMode.Append)) using (var writer = new StreamWriter(fs)) { writer.Write("more additional line"); } Console.WriteLine("---"); Console.WriteLine($"{fileName}: {File.ReadAllText(fileName)}"); Console.WriteLine("--- metadata"); Console.WriteLine($"Creation Time: {File.GetCreationTimeUtc(fileName)} LastAccessTime: {File.GetLastAccessTimeUtc(fileName)} LastWriteTime: {File.GetLastWriteTimeUtc(fileName)}"); } result hello.txt: World! last one, hello.txt: This is an overwriten hello world! --- hello.txt: This is an overwriten hello world! Additional text --- hello.txt: This is an overwriten hello world! Additional textmore additional line --- metadata Creation Time: 4/19/2021 12:48:24 AM LastAccessTime: 4/19/2021 8:04:11 AM LastWriteTime: 4/19/2021 8:04:11 AM
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【開発メモ】C#でSQLを実行

現在c#で開発していて、よく使う処理をメモとして残します。(随時更新予定) コネクションは普段Configに書いていますが、今回はソースコード上にべた書きしています。 ※完全に自分用のメモです。ご了承ください。 前準備 開発環境 ・Visual Studio 2019 ・Microsoft SQL Server Management Studio 18 usingディレクティブ 使用するパッケージはSqlClientです。 Visual Studioの「ツール」→「NuGetパッケージ マネージャー」→「ソリューションのパッケージの管理」よりSqlClientをインストールしてください。 usingディレクティブは以下のように記述。 using System.Data.SqlClient; データ抽出 SELECT文でテーブルからデータを取得する。 public string Conn = @"Data Source = <サーバー名>; Initial Catalog = <DB名>; User ID = <ユーザID>;             Password = <パスワード>; Encrypt=False; TrustServerCertificate=True"; /// <param name="Conn">コネクション</param> public void Get_TableDatas(string Conn) { //クエリ string query = "SELECT * FROM <テーブル名> "; using (SqlConnection con = new SqlConnection(Conn)) { try { SqlCommand comm = new SqlCommand(query, con); //コネクション開始 con.Open(); using (SqlDataReader dr = comm.ExecuteReader()) { //値取得 if (dr.Read()) { var Data = ((データ型)dr["<カラム名>"]); } } } catch (Exception e) { //エラーメッセージ Console.WriteLine(e.Message); } finally { //コネクション終了 con.Close(); } } } データ挿入 INSERT文でテーブルへデータを格納する。 テーブルは以下のような構成で行います。 SAMPLE_TABLE DATA_DATETIME DATA_ID DATA_VALUE 2021-01-01 00:00:00.000 1 90.6 2021-01-01 00:30:00.000 2 78.3 public string Conn = @"Data Source = <サーバー名>; Initial Catalog = <DB名>; User ID = <ユーザID>;             Password = <パスワード>; Encrypt=False; TrustServerCertificate=True"; /// <param name="Conn">コネクション</param> public void Insert_TableDatas(string Conn) { using (SqlConnection con = new SqlConnection(Conn)) using (var command = con.CreateCommand()) { try { con.Open(); // クエリ command.CommandText = @"INSERT INTO <テーブル名> VALUES('" + <DATA_DATETIME> + "'," + <DATA_ID> + ","+ <DATA_VALUE> + ")"; // SQLの実行 command.ExecuteNonQuery(); } catch (Exception e) { //エラーメッセージ Console.WriteLine(e.Message); } finally { //コネクション終了 con.Close(); } } } 最後に 時間があるときに随時更新していきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】.NET標準のJSONシリアライザの選定方法+パフォーマンスの比較検討

概要 本記事は、.NETの標準的なJSONシリアライザ(下記3つ)を次の観点で調査したものです。 1.System.Text.Json 2.Newtonsoft.Json(左記は正式名です。以後はJSON.NETと表記) 3.DataContractJsonSerializer 調査の観点 ・「どのJSONシリアライザを選ぶべきか?」 ・「パフォーマンスはどうか?」 まとめ いきなりですが、先にまとめを書きます。 1. System.Text.Jsonを使えるなら、使う 理由は2つある。 ①Microsoft社はJSON.NETを共通フレームワークから除こうしている。 .NET Core 3.0の破壊的変更より抜粋 認証:Newtonsoft.Json 型の置き換え ASP.NET Core 3.0 では、Authentication API で使用される Newtonsoft.Json 型が System.Text.Json 型に置き換えられました。 次の場合を除き、Authentication パッケージの基本的な使用方法は影響を受けません。 dotnet/aspnetcore #7289より引用 As part of the ongoing effort to remove Newtonsoft.Json from the shared framework these types have now been replaced on the Authentication APIs. (機械翻訳)共有フレームワークからNewtonsoft.Jsonを削除するための継続的な取り組みの一環として、これらのタイプは認証APIで置き換えられました。 ②Microsoft社はDataContractJsonSerializerを推奨していない。 Microsoft社:DataContractJsonSerializerのページより引用 JSON へのシリアル化と JSON からの逆シリアル化を含むほとんどのシナリオでは、 名前空間の System.Text.Jsonのapiを使用することをお勧めします。 サポートバージョンの一覧 Microsoft社 JSONシリアル化 概要のライブラリ入手方法より引用 .NET Standard 2.0 以降のバージョン .NET Framework 4.7.2 以降のバージョン .NET Core 2.0、2.1、および 2.2 2 System.Text.Jsonを使えない場合、JSON.NETを使う System.Text.Jsonをサポートしないバージョンの場合は、JSON.NETの使用を考える。 書籍「実践で役立つC#プログラミングのイディオム/定石&パターン」P317 より引用 ASP.NET MVCではNetsoftのJSON.NETが標準でプロジェクトに組み込まれています。そのため、ASP.NET MVCの場合は、DataContractJsonSerializerではなく、JSON.NETを使い、JSON形式のシリアル化/逆シリアル化を行うのが一般的です。 ASP.NET MVCでなくてもJSON.NETを使うことができますので、可能であればJSON.NETの利用も検討してみてください。 JSON.NETの使い方は下記が参考になります。 @IT JSONデータを作成/解析するには?[C#/VB] 3.System.Text.Jsonは使い方次第で、パフォーマンスが低下する Json文字列を日本語で出力させるために、System.Text.Jsonにエスケープを抑止の設定(※)を追加したところ、抑止の設定を追加しな場合に比べて、パフォーマンスが低下した。 System.Text.Json以外のライブラリと比較しても、抑止設定を追加した場合のパフォーマンスは他に比べて低い。 ① 実行速度の低下(下記表のmeanを参照。) ② メモリ使用量の増加(下記表のAllocatedを参照) ※System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 Method Mean StdDev Allocated DataContractJsonSerializer 8.887 us 0.1165 us 14152 B JSON.Net 3.912 us 0.0457 us 4304 B SystemTextJson(エスケープ抑止あり) 889.540 us 8.6991 us 44213 B SystemTextJson(エスケープ抑止なし) 3.106 us 0.0236 us 672 B この結果は下記の手順を基に作成したものです。 (a) 3つのライブラリを用いて、「同一データに対してシリアライズ→デシリアライズ」を行う関数を作成した。 (b) 前記の関数に、最終的な出力が同じになるように調整を加えた。 (c) benchMarkdotNetで計測を開始した。 (d) 結果を比較する。 動作環境について .NET Core 3.1.8 BenchmarkDotNet V0.12.1 JSON.NET(Newtonsoft.Json) V13.0.1 OS: Windows 10.0.19041.867 Intel Core i7-7700HQ CPU 2.80GHz パフォーマンス計測に関して 方針 「計測対象の最終出力結果が同一になる関数」を作成して、それぞれを比較する。 同一の入力データに対して、シリアライズ後にデシリアライズする。 それぞれをコンソール出力した時に下記の通りになるように調整する(※)。 ※計測時にはコンソール出力しない。 //調整結果 //シリアライズ後 {"company":"株式会社 自宅警備","department":"開発部","name":"山本太郎","sex":"男","age":30} //デシリアライズ後 company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 遭遇した問題:System.Text.Jsonが日本語表記されない シリアライズ後、Unicodeコードで出力される。 //シリアライズ Unicodeを日本語にエンコードできてない {"Company":"\u682A\u5F0F\u4F1A\u793E \u81EA\u5B85\u8B66\u5099","Department":"\u958B\u767A\u90E8","Name":"\u5C71\u672C\u592A\u90CE","Sex":"\u7537","Age":30} //デシリアライズ company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 原因は以下。 System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 すべての言語セットをエスケープせずにシリアル化するには、UnicodeRanges.All を使用します。 下記サイトを参考にエスケープを抑止するオプション指定を追加した。 .NET Core:JsonSerializerの実践的な使い方 また、計測の観点にオプション指定の有無を追加することを決めた。 コード 計測対象 using System; using BenchmarkDotNet.Attributes; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Unicode; using System.Runtime.Serialization.Json; using System.Text.Encodings.Web; namespace StudyJsonSerializer { [MemoryDiagnoser] public class MeasurementJsonSerializer { /// <summary> /// DataContractJsonSerializerによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingDataContractJsonSerializer() { //シリアル化 using var serializerStream = new MemoryStream(); var serializer = new DataContractJsonSerializer(OfficeWorkerContract.GetType()); serializer.WriteObject(serializerStream, OfficeWorkerContract); var jsonString = Encoding.UTF8.GetString(serializerStream.ToArray()); //デシリアル化 var byteArray = Encoding.UTF8.GetBytes(jsonString); using var deserializerStreamtream = new MemoryStream(byteArray); var deserializer = new DataContractJsonSerializer(typeof(OfficeWorkerContractModel)); var officeWorker = serializer.ReadObject(deserializerStreamtream) as OfficeWorkerContractModel; } /// <summary> /// Json.NETによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingJsonNet() { var jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(OfficeWorker); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingSystemTextJson() { var options = new JsonSerializerOptions() { // すべての言語セットをエスケープせずにシリアル化させる Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) }; var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker, options); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のオプション指定を外す /// </summary> [Benchmark] public void UsingSystemTextJsonWithoutSerializeOption() { var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// DataContractJsonSerializer用(他と異なるため念の為、分ける) /// </summary> private static readonly OfficeWorker OfficeWorker = new OfficeWorker() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; /// <summary> /// DataContractJsonSerializer以外で使用 /// </summary> private static readonly OfficeWorkerContractModel OfficeWorkerContract = new OfficeWorkerContractModel() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; } /// <summary> /// DataContractJsonSerializer用の会社員モデル(属性指定が必要なため、念の為分ける) /// </summary> [DataContract(Name = "officeWorker")] public class OfficeWorkerContractModel { /// <summary> /// 会社 /// </summary> [DataMember(Name = "company",Order = 0)] public string Company { get; set; } /// <summary> /// 部署 /// </summary> [DataMember(Name = "department", Order = 1)] public string Department { get; set; } /// <summary> /// 名前 /// </summary> [DataMember(Name = "name", Order = 2)] public string Name { get; set; } /// <summary> /// 性別 /// </summary> [DataMember(Name = "sex", Order = 3)] public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> [DataMember(Name = "age", Order = 4)] public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } /// <summary> /// 会社員(DataContractJsonSerializer以外で使用) /// </summary> public class OfficeWorker { /// <summary> /// 会社 /// </summary> public string Company { get; set; } /// <summary> /// 部署 /// </summary> public string Department { get; set; } /// <summary> /// 名前 /// </summary> public string Name { get; set; } /// <summary> /// 性別 /// </summary> public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } } より正確に測るなら、もっと自前の実装を極力排除すべきである。しかし、そこまでやり切る技量は私にはない。 メイン関数 using BenchmarkDotNet.Running; namespace StudyJsonSerializer { class Program { static void Main( string[] args ) { //計測開始 var summary = BenchmarkRunner.Run<MeasurementJsonSerializer>(); } } } 測定結果のサマリ Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated UsingDataContractJsonSerializer 8.887 us 0.1315 us 0.1165 us 4.5013 - - 14152 B UsingJsonNet 3.912 us 0.0515 us 0.0457 us 1.3695 - - 4304 B UsingSystemTextJson 889.540 us 11.1422 us 8.6991 us 15.6250 7.8125 - 44213 B UsingSystemTextJsonWithoutSerializeOption 3.106 us 0.0252 us 0.0236 us 0.2136 - - 672 B ラベルの説明は下記の通り Mean : Arithmetic mean of all measurements Error : Half of 99.9% confidence interval StdDev : Standard deviation of all measurements Gen 0 : GC Generation 0 collects per 1000 operations Gen 1 : GC Generation 1 collects per 1000 operations Gen 2 : GC Generation 2 collects per 1000 operations Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) 1 us : 1 Microsecond (0.000001 sec) 測定結果の詳細(一応控える) // * Detailed results * MeasurementJsonSerializer.UsingDataContractJsonSerializer: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 8.887 us, StdErr = 0.031 us (0.35%), N = 14, StdDev = 0.117 us Min = 8.754 us, Q1 = 8.792 us, Median = 8.873 us, Q3 = 8.940 us, Max = 9.203 us IQR = 0.148 us, LowerFence = 8.570 us, UpperFence = 9.162 us ConfidenceInterval = [8.756 us; 9.019 us] (CI 99.9%), Margin = 0.131 us (1.48% of Mean) Skewness = 1.17, Kurtosis = 4.18, MValue = 2 -------------------- Histogram -------------------- [8.691 us ; 8.977 us) | @@@@@@@@@@@@ [8.977 us ; 9.266 us) | @@ --------------------------------------------------- MeasurementJsonSerializer.UsingJsonNet: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.912 us, StdErr = 0.012 us (0.31%), N = 14, StdDev = 0.046 us Min = 3.848 us, Q1 = 3.878 us, Median = 3.912 us, Q3 = 3.934 us, Max = 4.004 us IQR = 0.056 us, LowerFence = 3.795 us, UpperFence = 4.018 us ConfidenceInterval = [3.860 us; 3.963 us] (CI 99.9%), Margin = 0.052 us (1.32% of Mean) Skewness = 0.32, Kurtosis = 2.08, MValue = 2 -------------------- Histogram -------------------- [3.823 us ; 4.029 us) | @@@@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJson: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 889.540 us, StdErr = 2.511 us (0.28%), N = 12, StdDev = 8.699 us Min = 875.936 us, Q1 = 883.029 us, Median = 890.843 us, Q3 = 895.243 us, Max = 904.026 us IQR = 12.213 us, LowerFence = 864.709 us, UpperFence = 913.563 us ConfidenceInterval = [878.398 us; 900.682 us] (CI 99.9%), Margin = 11.142 us (1.25% of Mean) Skewness = -0.15, Kurtosis = 1.7, MValue = 2 -------------------- Histogram -------------------- [870.949 us ; 909.013 us) | @@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJsonWithoutSerializeOption: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.106 us, StdErr = 0.006 us (0.20%), N = 15, StdDev = 0.024 us Min = 3.052 us, Q1 = 3.097 us, Median = 3.104 us, Q3 = 3.122 us, Max = 3.142 us IQR = 0.025 us, LowerFence = 3.060 us, UpperFence = 3.160 us ConfidenceInterval = [3.081 us; 3.131 us] (CI 99.9%), Margin = 0.025 us (0.81% of Mean) Skewness = -0.49, Kurtosis = 2.76, MValue = 2 -------------------- Histogram -------------------- [3.039 us ; 3.155 us) | @@@@@@@@@@@@@@@ --------------------------------------------------- 執筆の際に参考にしたサイト/書籍 .NET Core 3.0の破壊的変更 dotnet/aspnetcore #7289 Microsoft社:DataContractJsonSerializer Microsoft社 JSONシリアル化 概要 書籍:実践で役立つC#プログラミングのイディオム/定石&パターン 12.3 JSONデータのシリアル化と逆シリアル化 @IT JSONデータを作成/解析するには?[C#/VB] System.Text.Json で文字エンコードをカスタマイズする方法 .NET 内で JSON のシリアル化と逆シリアル化 (マーシャリングとマーシャリングの解除) を行う方法 .NET Core:JsonSerializerの実践的な使い方 Newtonsoft.Json と System.Text.Json の相違点の表 【.NET/C#】メソッドのパフォーマンスを簡単に集計するライブラリの紹介
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】.NETの標準JSONシリアラザは、System.Text.JSONを使う。

概要 本記事は、.NETの標準的なJSONシリアライザ(下記3つ)を次の観点で調査したものです。 1.System.Text.Json 2.Newtonsoft.Json(左記は正式名です。以後はJSON.NETと表記) 3.DataContractJsonSerializer 調査の観点 ・「どのJSONシリアライザを選ぶべきか?」 ・「パフォーマンスはどうか?」 まとめ いきなりですが、先にまとめを書きます。 1. System.Text.Jsonを使えるなら、使う 理由は2つある。 ①Microsoft社はJSON.NETを共通フレームワークから除こうしている。 .NET Core 3.0の破壊的変更より抜粋 認証:Newtonsoft.Json 型の置き換え ASP.NET Core 3.0 では、Authentication API で使用される Newtonsoft.Json 型が System.Text.Json 型に置き換えられました。 次の場合を除き、Authentication パッケージの基本的な使用方法は影響を受けません。 dotnet/aspnetcore #7289より引用 As part of the ongoing effort to remove Newtonsoft.Json from the shared framework these types have now been replaced on the Authentication APIs. (機械翻訳)共有フレームワークからNewtonsoft.Jsonを削除するための継続的な取り組みの一環として、これらのタイプは認証APIで置き換えられました。 ②Microsoft社はDataContractJsonSerializerを推奨していない。 Microsoft社:DataContractJsonSerializerのページより引用 JSON へのシリアル化と JSON からの逆シリアル化を含むほとんどのシナリオでは、 名前空間の System.Text.Jsonのapiを使用することをお勧めします。 サポートバージョンの一覧 Microsoft社 JSONシリアル化 概要のライブラリ入手方法より引用 .NET Standard 2.0 以降のバージョン .NET Framework 4.7.2 以降のバージョン .NET Core 2.0、2.1、および 2.2 2 System.Text.Jsonを使えない場合、JSON.NETを使う System.Text.Jsonをサポートしないバージョンの場合は、JSON.NETの使用を考える。 書籍「実践で役立つC#プログラミングのイディオム/定石&パターン」P317 より引用 ASP.NET MVCではNetsoftのJSON.NETが標準でプロジェクトに組み込まれています。そのため、ASP.NET MVCの場合は、DataContractJsonSerializerではなく、JSON.NETを使い、JSON形式のシリアル化/逆シリアル化を行うのが一般的です。 ASP.NET MVCでなくてもJSON.NETを使うことができますので、可能であればJSON.NETの利用も検討してみてください。 JSON.NETの使い方は下記が参考になります。 @IT JSONデータを作成/解析するには?[C#/VB] 3.System.Text.Jsonは使い方次第で、パフォーマンスが低下する Json文字列を日本語で出力させるために、System.Text.Jsonにエスケープを抑止の設定(※)を追加したところ、抑止の設定を追加しな場合に比べて、パフォーマンスが低下した。 System.Text.Json以外のライブラリと比較しても、抑止設定を追加した場合のパフォーマンスは他に比べて低い。 ① 実行速度の低下(下記表のmeanを参照。) ② メモリ使用量の増加(下記表のAllocatedを参照) ※System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 Method Mean StdDev Allocated DataContractJsonSerializer 8.887 us 0.1165 us 14152 B JSON.Net 3.912 us 0.0457 us 4304 B SystemTextJson(エスケープ抑止あり) 889.540 us 8.6991 us 44213 B SystemTextJson(エスケープ抑止なし) 3.106 us 0.0236 us 672 B この結果は下記の手順を基に作成したものです。 (a) 3つのライブラリを用いて、「同一データに対してシリアライズ→デシリアライズ」を行う関数を作成した。 (b) 前記の関数に、最終的な出力が同じになるように調整を加えた。 (c) benchMarkdotNetで計測を開始した。 (d) 結果を比較する。 動作環境について .NET Core 3.1.8 BenchmarkDotNet V0.12.1 JSON.NET(Newtonsoft.Json) V13.0.1 OS: Windows 10.0.19041.867 Intel Core i7-7700HQ CPU 2.80GHz パフォーマンス計測に関して 方針 「計測対象の最終出力結果が同一になる関数」を作成して、それぞれを比較する。 同一の入力データに対して、シリアライズ後にデシリアライズする。 それぞれをコンソール出力した時に下記の通りになるように調整する(※)。 ※計測時にはコンソール出力しない。 //調整結果 //シリアライズ後 {"company":"株式会社 自宅警備","department":"開発部","name":"山本太郎","sex":"男","age":30} //デシリアライズ後 company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 遭遇した問題:System.Text.Jsonが日本語表記されない シリアライズ後、Unicodeコードで出力される。 //シリアライズ Unicodeを日本語にエンコードできてない {"Company":"\u682A\u5F0F\u4F1A\u793E \u81EA\u5B85\u8B66\u5099","Department":"\u958B\u767A\u90E8","Name":"\u5C71\u672C\u592A\u90CE","Sex":"\u7537","Age":30} //デシリアライズ company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 原因は以下。 System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 すべての言語セットをエスケープせずにシリアル化するには、UnicodeRanges.All を使用します。 下記サイトを参考にエスケープを抑止するオプション指定を追加した。 .NET Core:JsonSerializerの実践的な使い方 また、計測の観点にオプション指定の有無を追加することを決めた。 コード 計測対象 using System; using BenchmarkDotNet.Attributes; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Unicode; using System.Runtime.Serialization.Json; using System.Text.Encodings.Web; namespace StudyJsonSerializer { [MemoryDiagnoser] public class MeasurementJsonSerializer { /// <summary> /// DataContractJsonSerializerによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingDataContractJsonSerializer() { //シリアル化 using var serializerStream = new MemoryStream(); var serializer = new DataContractJsonSerializer(OfficeWorkerContract.GetType()); serializer.WriteObject(serializerStream, OfficeWorkerContract); var jsonString = Encoding.UTF8.GetString(serializerStream.ToArray()); //デシリアル化 var byteArray = Encoding.UTF8.GetBytes(jsonString); using var deserializerStreamtream = new MemoryStream(byteArray); var deserializer = new DataContractJsonSerializer(typeof(OfficeWorkerContractModel)); var officeWorker = serializer.ReadObject(deserializerStreamtream) as OfficeWorkerContractModel; } /// <summary> /// Json.NETによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingJsonNet() { var jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(OfficeWorker); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingSystemTextJson() { var options = new JsonSerializerOptions() { // すべての言語セットをエスケープせずにシリアル化させる Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) }; var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker, options); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のオプション指定を外す /// </summary> [Benchmark] public void UsingSystemTextJsonWithoutSerializeOption() { var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// DataContractJsonSerializer用(他と異なるため念の為、分ける) /// </summary> private static readonly OfficeWorker OfficeWorker = new OfficeWorker() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; /// <summary> /// DataContractJsonSerializer以外で使用 /// </summary> private static readonly OfficeWorkerContractModel OfficeWorkerContract = new OfficeWorkerContractModel() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; } /// <summary> /// DataContractJsonSerializer用の会社員モデル(属性指定が必要なため、念の為分ける) /// </summary> [DataContract(Name = "officeWorker")] public class OfficeWorkerContractModel { /// <summary> /// 会社 /// </summary> [DataMember(Name = "company",Order = 0)] public string Company { get; set; } /// <summary> /// 部署 /// </summary> [DataMember(Name = "department", Order = 1)] public string Department { get; set; } /// <summary> /// 名前 /// </summary> [DataMember(Name = "name", Order = 2)] public string Name { get; set; } /// <summary> /// 性別 /// </summary> [DataMember(Name = "sex", Order = 3)] public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> [DataMember(Name = "age", Order = 4)] public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } /// <summary> /// 会社員(DataContractJsonSerializer以外で使用) /// </summary> public class OfficeWorker { /// <summary> /// 会社 /// </summary> public string Company { get; set; } /// <summary> /// 部署 /// </summary> public string Department { get; set; } /// <summary> /// 名前 /// </summary> public string Name { get; set; } /// <summary> /// 性別 /// </summary> public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } } より正確に測るなら、もっと自前の実装を極力排除すべきである。しかし、そこまでやり切る技量は私にはない。 メイン関数 using BenchmarkDotNet.Running; namespace StudyJsonSerializer { class Program { static void Main( string[] args ) { //計測開始 var summary = BenchmarkRunner.Run<MeasurementJsonSerializer>(); } } } 測定結果のサマリ Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated UsingDataContractJsonSerializer 8.887 us 0.1315 us 0.1165 us 4.5013 - - 14152 B UsingJsonNet 3.912 us 0.0515 us 0.0457 us 1.3695 - - 4304 B UsingSystemTextJson 889.540 us 11.1422 us 8.6991 us 15.6250 7.8125 - 44213 B UsingSystemTextJsonWithoutSerializeOption 3.106 us 0.0252 us 0.0236 us 0.2136 - - 672 B ラベルの説明は下記の通り Mean : Arithmetic mean of all measurements Error : Half of 99.9% confidence interval StdDev : Standard deviation of all measurements Gen 0 : GC Generation 0 collects per 1000 operations Gen 1 : GC Generation 1 collects per 1000 operations Gen 2 : GC Generation 2 collects per 1000 operations Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) 1 us : 1 Microsecond (0.000001 sec) 測定結果の詳細(一応控える) // * Detailed results * MeasurementJsonSerializer.UsingDataContractJsonSerializer: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 8.887 us, StdErr = 0.031 us (0.35%), N = 14, StdDev = 0.117 us Min = 8.754 us, Q1 = 8.792 us, Median = 8.873 us, Q3 = 8.940 us, Max = 9.203 us IQR = 0.148 us, LowerFence = 8.570 us, UpperFence = 9.162 us ConfidenceInterval = [8.756 us; 9.019 us] (CI 99.9%), Margin = 0.131 us (1.48% of Mean) Skewness = 1.17, Kurtosis = 4.18, MValue = 2 -------------------- Histogram -------------------- [8.691 us ; 8.977 us) | @@@@@@@@@@@@ [8.977 us ; 9.266 us) | @@ --------------------------------------------------- MeasurementJsonSerializer.UsingJsonNet: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.912 us, StdErr = 0.012 us (0.31%), N = 14, StdDev = 0.046 us Min = 3.848 us, Q1 = 3.878 us, Median = 3.912 us, Q3 = 3.934 us, Max = 4.004 us IQR = 0.056 us, LowerFence = 3.795 us, UpperFence = 4.018 us ConfidenceInterval = [3.860 us; 3.963 us] (CI 99.9%), Margin = 0.052 us (1.32% of Mean) Skewness = 0.32, Kurtosis = 2.08, MValue = 2 -------------------- Histogram -------------------- [3.823 us ; 4.029 us) | @@@@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJson: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 889.540 us, StdErr = 2.511 us (0.28%), N = 12, StdDev = 8.699 us Min = 875.936 us, Q1 = 883.029 us, Median = 890.843 us, Q3 = 895.243 us, Max = 904.026 us IQR = 12.213 us, LowerFence = 864.709 us, UpperFence = 913.563 us ConfidenceInterval = [878.398 us; 900.682 us] (CI 99.9%), Margin = 11.142 us (1.25% of Mean) Skewness = -0.15, Kurtosis = 1.7, MValue = 2 -------------------- Histogram -------------------- [870.949 us ; 909.013 us) | @@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJsonWithoutSerializeOption: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.106 us, StdErr = 0.006 us (0.20%), N = 15, StdDev = 0.024 us Min = 3.052 us, Q1 = 3.097 us, Median = 3.104 us, Q3 = 3.122 us, Max = 3.142 us IQR = 0.025 us, LowerFence = 3.060 us, UpperFence = 3.160 us ConfidenceInterval = [3.081 us; 3.131 us] (CI 99.9%), Margin = 0.025 us (0.81% of Mean) Skewness = -0.49, Kurtosis = 2.76, MValue = 2 -------------------- Histogram -------------------- [3.039 us ; 3.155 us) | @@@@@@@@@@@@@@@ --------------------------------------------------- 執筆の際に参考にしたサイト/書籍 .NET Core 3.0の破壊的変更 dotnet/aspnetcore #7289 Microsoft社:DataContractJsonSerializer Microsoft社 JSONシリアル化 概要 書籍:実践で役立つC#プログラミングのイディオム/定石&パターン 12.3 JSONデータのシリアル化と逆シリアル化 @IT JSONデータを作成/解析するには?[C#/VB] System.Text.Json で文字エンコードをカスタマイズする方法 .NET 内で JSON のシリアル化と逆シリアル化 (マーシャリングとマーシャリングの解除) を行う方法 .NET Core:JsonSerializerの実践的な使い方 Newtonsoft.Json と System.Text.Json の相違点の表 【.NET/C#】メソッドのパフォーマンスを簡単に集計するライブラリの紹介
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】.NET標準のJSONシリアラザは、System.Text.JSONを使おう。

概要 本記事は、.NETの標準的なJSONシリアライザ(下記3つ)を次の観点で調査したものです。 1.System.Text.Json 2.Newtonsoft.Json(左記はNugetに表示される名称です。以後はJSON.NETと表記) 3.DataContractJsonSerializer 調査の観点 ・「どのJSONシリアライザを選ぶべきか?」 ・「パフォーマンスはどうか?」 まとめ いきなりですが、先にまとめを書きます。 1. System.Text.Jsonを使おう 理由は2つある。 ①Microsoft社はJSON.NETを共通フレームワークから除こうしている。 .NET Core 3.0の破壊的変更より抜粋 認証:Newtonsoft.Json 型の置き換え ASP.NET Core 3.0 では、Authentication API で使用される Newtonsoft.Json 型が System.Text.Json 型に置き換えられました。 次の場合を除き、Authentication パッケージの基本的な使用方法は影響を受けません。 dotnet/aspnetcore #7289より引用 As part of the ongoing effort to remove Newtonsoft.Json from the shared framework these types have now been replaced on the Authentication APIs. (機械翻訳)共有フレームワークからNewtonsoft.Jsonを削除するための継続的な取り組みの一環として、これらのタイプは認証APIで置き換えられました。 ②Microsoft社はDataContractJsonSerializerを推奨していない。 Microsoft社:DataContractJsonSerializerのページより引用 JSON へのシリアル化と JSON からの逆シリアル化を含むほとんどのシナリオでは、 名前空間の System.Text.Jsonのapiを使用することをお勧めします。 サポートバージョンの一覧 Microsoft社 JSONシリアル化 概要のライブラリ入手方法より引用 .NET Standard 2.0 以降のバージョン .NET Framework 4.7.2 以降のバージョン .NET Core 2.0、2.1、および 2.2 2 System.Text.Jsonを使えない場合、JSON.NETを使う System.Text.Jsonをサポートしないバージョンの場合は、JSON.NETの使用を考える。 書籍「実践で役立つC#プログラミングのイディオム/定石&パターン」P317 より引用 ASP.NET MVCではNetsoftのJSON.NETが標準でプロジェクトに組み込まれています。そのため、ASP.NET MVCの場合は、DataContractJsonSerializerではなく、JSON.NETを使い、JSON形式のシリアル化/逆シリアル化を行うのが一般的です。 ASP.NET MVCでなくてもJSON.NETを使うことができますので、可能であればJSON.NETの利用も検討してみてください。 JSON.NETの使い方は下記が参考になります。 @IT JSONデータを作成/解析するには?[C#/VB] 3.System.Text.Jsonは使い方に気をつけよう 使い方次第で、パフォーマンスが低下することがある。 Json文字列を日本語で出力させるために、System.Text.Jsonにエスケープを抑止の設定(※)を追加したところ、抑止の設定を追加しな場合に比べて、パフォーマンスが低下した。 System.Text.Json以外のライブラリと比較しても、抑止設定を追加した場合のパフォーマンスは他に比べて低い。 ① 実行速度の低下(下記表のmeanを参照。) ② メモリ使用量の増加(下記表のAllocatedを参照) つまり、ASCII以外の文字列を使わなければ、高いパフォーマンスに使える。少なくともUnicodeでエスケープされることは頭の片隅においた方が良い。 ※System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 Method Mean StdDev Allocated DataContractJsonSerializer 8.887 us 0.1165 us 14152 B JSON.Net 3.912 us 0.0457 us 4304 B SystemTextJson(エスケープ抑止あり) 889.540 us 8.6991 us 44213 B SystemTextJson(エスケープ抑止なし) 3.106 us 0.0236 us 672 B この結果は下記の手順を基に作成したものです。 (a) 3つのライブラリを用いて、「同一データに対してシリアライズ→デシリアライズ」を行う関数を作成した。 (b) 前記の関数に、最終的な出力が同じになるように調整を加えた。 (c) benchMarkdotNetで計測を開始した。 (d) 結果を比較する。 動作環境について .NET Core 3.1.8 BenchmarkDotNet V0.12.1 JSON.NET(Newtonsoft.Json) V13.0.1 OS: Windows 10.0.19041.867 Intel Core i7-7700HQ CPU 2.80GHz パフォーマンス計測に関して 方針 「計測対象の最終出力結果が同一になる関数」を作成して、それぞれを比較する。 同一の入力データに対して、シリアライズ後にデシリアライズする。 それぞれをコンソール出力した時に下記の通りになるように調整する(※)。 ※計測時にはコンソール出力しない。 //調整結果 //シリアライズ後 {"company":"株式会社 自宅警備","department":"開発部","name":"山本太郎","sex":"男","age":30} //デシリアライズ後 company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 遭遇した問題:System.Text.Jsonが日本語表記されない シリアライズ後、Unicodeコードで出力される。 //シリアライズ Unicodeを日本語にエンコードできてない {"Company":"\u682A\u5F0F\u4F1A\u793E \u81EA\u5B85\u8B66\u5099","Department":"\u958B\u767A\u90E8","Name":"\u5C71\u672C\u592A\u90CE","Sex":"\u7537","Age":30} //デシリアライズ company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 原因は以下。 System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 すべての言語セットをエスケープせずにシリアル化するには、UnicodeRanges.All を使用します。 下記サイトを参考にエスケープを抑止するオプション指定を追加した。 .NET Core:JsonSerializerの実践的な使い方 また、計測の観点にオプション指定の有無を追加することを決めた。 コード 計測対象 using System; using BenchmarkDotNet.Attributes; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Unicode; using System.Runtime.Serialization.Json; using System.Text.Encodings.Web; namespace StudyJsonSerializer { [MemoryDiagnoser] public class MeasurementJsonSerializer { /// <summary> /// DataContractJsonSerializerによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingDataContractJsonSerializer() { //シリアル化 using var serializerStream = new MemoryStream(); var serializer = new DataContractJsonSerializer(OfficeWorkerContract.GetType()); serializer.WriteObject(serializerStream, OfficeWorkerContract); var jsonString = Encoding.UTF8.GetString(serializerStream.ToArray()); //デシリアル化 var byteArray = Encoding.UTF8.GetBytes(jsonString); using var deserializerStreamtream = new MemoryStream(byteArray); var deserializer = new DataContractJsonSerializer(typeof(OfficeWorkerContractModel)); var officeWorker = serializer.ReadObject(deserializerStreamtream) as OfficeWorkerContractModel; } /// <summary> /// Json.NETによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingJsonNet() { var jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(OfficeWorker); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingSystemTextJson() { var options = new JsonSerializerOptions() { // すべての言語セットをエスケープせずにシリアル化させる Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) }; var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker, options); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のオプション指定を外す /// </summary> [Benchmark] public void UsingSystemTextJsonWithoutSerializeOption() { var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// DataContractJsonSerializer用(他と異なるため念の為、分ける) /// </summary> private static readonly OfficeWorker OfficeWorker = new OfficeWorker() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; /// <summary> /// DataContractJsonSerializer以外で使用 /// </summary> private static readonly OfficeWorkerContractModel OfficeWorkerContract = new OfficeWorkerContractModel() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; } /// <summary> /// DataContractJsonSerializer用の会社員モデル(属性指定が必要なため、念の為分ける) /// </summary> [DataContract(Name = "officeWorker")] public class OfficeWorkerContractModel { /// <summary> /// 会社 /// </summary> [DataMember(Name = "company",Order = 0)] public string Company { get; set; } /// <summary> /// 部署 /// </summary> [DataMember(Name = "department", Order = 1)] public string Department { get; set; } /// <summary> /// 名前 /// </summary> [DataMember(Name = "name", Order = 2)] public string Name { get; set; } /// <summary> /// 性別 /// </summary> [DataMember(Name = "sex", Order = 3)] public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> [DataMember(Name = "age", Order = 4)] public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } /// <summary> /// 会社員(DataContractJsonSerializer以外で使用) /// </summary> public class OfficeWorker { /// <summary> /// 会社 /// </summary> public string Company { get; set; } /// <summary> /// 部署 /// </summary> public string Department { get; set; } /// <summary> /// 名前 /// </summary> public string Name { get; set; } /// <summary> /// 性別 /// </summary> public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } } より正確に測るなら、もっと自前の実装を極力排除すべきである。しかし、そこまでやり切る技量は私にはない。 メイン関数 using BenchmarkDotNet.Running; namespace StudyJsonSerializer { class Program { static void Main( string[] args ) { //計測開始 var summary = BenchmarkRunner.Run<MeasurementJsonSerializer>(); } } } 測定結果のサマリ Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated UsingDataContractJsonSerializer 8.887 us 0.1315 us 0.1165 us 4.5013 - - 14152 B UsingJsonNet 3.912 us 0.0515 us 0.0457 us 1.3695 - - 4304 B UsingSystemTextJson 889.540 us 11.1422 us 8.6991 us 15.6250 7.8125 - 44213 B UsingSystemTextJsonWithoutSerializeOption 3.106 us 0.0252 us 0.0236 us 0.2136 - - 672 B ラベルの説明は下記の通り Mean : Arithmetic mean of all measurements Error : Half of 99.9% confidence interval StdDev : Standard deviation of all measurements Gen 0 : GC Generation 0 collects per 1000 operations Gen 1 : GC Generation 1 collects per 1000 operations Gen 2 : GC Generation 2 collects per 1000 operations Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) 1 us : 1 Microsecond (0.000001 sec) 測定結果の詳細(一応控える) // * Detailed results * MeasurementJsonSerializer.UsingDataContractJsonSerializer: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 8.887 us, StdErr = 0.031 us (0.35%), N = 14, StdDev = 0.117 us Min = 8.754 us, Q1 = 8.792 us, Median = 8.873 us, Q3 = 8.940 us, Max = 9.203 us IQR = 0.148 us, LowerFence = 8.570 us, UpperFence = 9.162 us ConfidenceInterval = [8.756 us; 9.019 us] (CI 99.9%), Margin = 0.131 us (1.48% of Mean) Skewness = 1.17, Kurtosis = 4.18, MValue = 2 -------------------- Histogram -------------------- [8.691 us ; 8.977 us) | @@@@@@@@@@@@ [8.977 us ; 9.266 us) | @@ --------------------------------------------------- MeasurementJsonSerializer.UsingJsonNet: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.912 us, StdErr = 0.012 us (0.31%), N = 14, StdDev = 0.046 us Min = 3.848 us, Q1 = 3.878 us, Median = 3.912 us, Q3 = 3.934 us, Max = 4.004 us IQR = 0.056 us, LowerFence = 3.795 us, UpperFence = 4.018 us ConfidenceInterval = [3.860 us; 3.963 us] (CI 99.9%), Margin = 0.052 us (1.32% of Mean) Skewness = 0.32, Kurtosis = 2.08, MValue = 2 -------------------- Histogram -------------------- [3.823 us ; 4.029 us) | @@@@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJson: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 889.540 us, StdErr = 2.511 us (0.28%), N = 12, StdDev = 8.699 us Min = 875.936 us, Q1 = 883.029 us, Median = 890.843 us, Q3 = 895.243 us, Max = 904.026 us IQR = 12.213 us, LowerFence = 864.709 us, UpperFence = 913.563 us ConfidenceInterval = [878.398 us; 900.682 us] (CI 99.9%), Margin = 11.142 us (1.25% of Mean) Skewness = -0.15, Kurtosis = 1.7, MValue = 2 -------------------- Histogram -------------------- [870.949 us ; 909.013 us) | @@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJsonWithoutSerializeOption: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.106 us, StdErr = 0.006 us (0.20%), N = 15, StdDev = 0.024 us Min = 3.052 us, Q1 = 3.097 us, Median = 3.104 us, Q3 = 3.122 us, Max = 3.142 us IQR = 0.025 us, LowerFence = 3.060 us, UpperFence = 3.160 us ConfidenceInterval = [3.081 us; 3.131 us] (CI 99.9%), Margin = 0.025 us (0.81% of Mean) Skewness = -0.49, Kurtosis = 2.76, MValue = 2 -------------------- Histogram -------------------- [3.039 us ; 3.155 us) | @@@@@@@@@@@@@@@ --------------------------------------------------- 執筆の際に参考にしたサイト/書籍 .NET Core 3.0の破壊的変更 dotnet/aspnetcore #7289 Microsoft社:DataContractJsonSerializer Microsoft社 JSONシリアル化 概要 書籍:実践で役立つC#プログラミングのイディオム/定石&パターン 12.3 JSONデータのシリアル化と逆シリアル化 @IT JSONデータを作成/解析するには?[C#/VB] System.Text.Json で文字エンコードをカスタマイズする方法 .NET 内で JSON のシリアル化と逆シリアル化 (マーシャリングとマーシャリングの解除) を行う方法 .NET Core:JsonSerializerの実践的な使い方 Newtonsoft.Json と System.Text.Json の相違点の表 【.NET/C#】メソッドのパフォーマンスを簡単に集計するライブラリの紹介
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】.NET標準のJSONシリアライザの選定方法+パフォーマンス比較の検討

概要 本記事は、.NETの標準的なJSONシリアライザ(下記3つ)を次の観点で調査したものです。 1.System.Text.Json 2.Newtonsoft.Json(左記は正式名です。以後はJSON.NETと表記) 3.DataContractJsonSerializer 調査の観点 ・「どのJSONシリアライザを選ぶべきか?」 ・「パフォーマンスはどうか?」 まとめ いきなりですが、先にまとめを書きます。 1. System.Text.Jsonを使えるなら、使う 理由は2つある。 ①Microsoft社はJSON.NETを共通フレームワークから除こうしている。 .NET Core 3.0の破壊的変更より抜粋 認証:Newtonsoft.Json 型の置き換え ASP.NET Core 3.0 では、Authentication API で使用される Newtonsoft.Json 型が System.Text.Json 型に置き換えられました。 次の場合を除き、Authentication パッケージの基本的な使用方法は影響を受けません。 dotnet/aspnetcore #7289より引用 As part of the ongoing effort to remove Newtonsoft.Json from the shared framework these types have now been replaced on the Authentication APIs. (機械翻訳)共有フレームワークからNewtonsoft.Jsonを削除するための継続的な取り組みの一環として、これらのタイプは認証APIで置き換えられました。 ②Microsoft社はDataContractJsonSerializerを推奨していない。 Microsoft社:DataContractJsonSerializerのページより引用 JSON へのシリアル化と JSON からの逆シリアル化を含むほとんどのシナリオでは、 名前空間の System.Text.Jsonのapiを使用することをお勧めします。 サポートバージョンの一覧 Microsoft社 JSONシリアル化 概要のライブラリ入手方法より引用 .NET Standard 2.0 以降のバージョン .NET Framework 4.7.2 以降のバージョン .NET Core 2.0、2.1、および 2.2 2 System.Text.Jsonを使えない場合、JSON.NETを使う System.Text.Jsonをサポートしないバージョンの場合は、JSON.NETの使用を考える。 書籍「実践で役立つC#プログラミングのイディオム/定石&パターン」P317 より引用 ASP.NET MVCではNetsoftのJSON.NETが標準でプロジェクトに組み込まれています。そのため、ASP.NET MVCの場合は、DataContractJsonSerializerではなく、JSON.NETを使い、JSON形式のシリアル化/逆シリアル化を行うのが一般的です。 ASP.NET MVCでなくてもJSON.NETを使うことができますので、可能であればJSON.NETの利用も検討してみてください。 JSON.NETの使い方は下記が参考になります。 @IT JSONデータを作成/解析するには?[C#/VB] 3.System.Text.Jsonは使い方次第で、パフォーマンスが低下する Json文字列を日本語で出力させるために、System.Text.Jsonにエスケープを抑止の設定(※)を追加したところ、抑止の設定を追加しな場合に比べて、パフォーマンスが低下した。 System.Text.Json以外のライブラリと比較しても、抑止設定を追加した場合のパフォーマンスは他に比べて低い。 ① 実行速度の低下(下記表のmeanを参照。) ② メモリ使用量の増加(下記表のAllocatedを参照) ※System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 Method Mean StdDev Allocated DataContractJsonSerializer 8.887 us 0.1165 us 14152 B JSON.Net 3.912 us 0.0457 us 4304 B SystemTextJson(エスケープ抑止あり) 889.540 us 8.6991 us 44213 B SystemTextJson(エスケープ抑止なし) 3.106 us 0.0236 us 672 B この結果は下記の手順を基に作成したものです。 (a) 3つのライブラリを用いて、「同一データに対してシリアライズ→デシリアライズ」を行う関数を作成した。 (b) 前記の関数に、最終的な出力が同じになるように調整を加えた。 (c) benchMarkdotNetで計測を開始した。 (d) 結果を比較する。 動作環境について .NET Core 3.1.8 BenchmarkDotNet V0.12.1 JSON.NET(Newtonsoft.Json) V13.0.1 OS: Windows 10.0.19041.867 Intel Core i7-7700HQ CPU 2.80GHz パフォーマンス計測に関して 方針 「計測対象の最終出力結果が同一になる関数」を作成して、それぞれを比較する。 同一の入力データに対して、シリアライズ後にデシリアライズする。 それぞれをコンソール出力した時に下記の通りになるように調整する(※)。 ※計測時にはコンソール出力しない。 //調整結果 //シリアライズ後 {"company":"株式会社 自宅警備","department":"開発部","name":"山本太郎","sex":"男","age":30} //デシリアライズ後 company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 遭遇した問題:System.Text.Jsonが日本語表記されない シリアライズ後、Unicodeコードで出力される。 //シリアライズ Unicodeを日本語にエンコードできてない {"Company":"\u682A\u5F0F\u4F1A\u793E \u81EA\u5B85\u8B66\u5099","Department":"\u958B\u767A\u90E8","Name":"\u5C71\u672C\u592A\u90CE","Sex":"\u7537","Age":30} //デシリアライズ company=株式会社 自宅警備,department=開発部,name=山本太郎,sex=男,age=30 原因は以下。 System.Text.Json で文字エンコードをカスタマイズする方法より 既定では、シリアライザーでは ASCII 以外のすべての文字がエスケープされます。 つまり、\uxxxx に置き換えられます。xxxx は文字の Unicode コードです。 すべての言語セットをエスケープせずにシリアル化するには、UnicodeRanges.All を使用します。 下記サイトを参考にエスケープを抑止するオプション指定を追加した。 .NET Core:JsonSerializerの実践的な使い方 また、計測の観点にオプション指定の有無を追加することを決めた。 コード 計測対象 using System; using BenchmarkDotNet.Attributes; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Text.Json; using System.Text.Unicode; using System.Runtime.Serialization.Json; using System.Text.Encodings.Web; namespace StudyJsonSerializer { [MemoryDiagnoser] public class MeasurementJsonSerializer { /// <summary> /// DataContractJsonSerializerによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingDataContractJsonSerializer() { //シリアル化 using var serializerStream = new MemoryStream(); var serializer = new DataContractJsonSerializer(OfficeWorkerContract.GetType()); serializer.WriteObject(serializerStream, OfficeWorkerContract); var jsonString = Encoding.UTF8.GetString(serializerStream.ToArray()); //デシリアル化 var byteArray = Encoding.UTF8.GetBytes(jsonString); using var deserializerStreamtream = new MemoryStream(byteArray); var deserializer = new DataContractJsonSerializer(typeof(OfficeWorkerContractModel)); var officeWorker = serializer.ReadObject(deserializerStreamtream) as OfficeWorkerContractModel; } /// <summary> /// Json.NETによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingJsonNet() { var jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(OfficeWorker); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のサンプル /// </summary> [Benchmark] public void UsingSystemTextJson() { var options = new JsonSerializerOptions() { // すべての言語セットをエスケープせずにシリアル化させる Encoder = JavaScriptEncoder.Create(UnicodeRanges.All) }; var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker, options); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// System.Text.Json.Serializationによるシリアル化/デシリアル化のオプション指定を外す /// </summary> [Benchmark] public void UsingSystemTextJsonWithoutSerializeOption() { var jsonString = System.Text.Json.JsonSerializer.Serialize(OfficeWorker); var result = System.Text.Json.JsonSerializer.Deserialize<OfficeWorker>(jsonString); } /// <summary> /// DataContractJsonSerializer用(他と異なるため念の為、分ける) /// </summary> private static readonly OfficeWorker OfficeWorker = new OfficeWorker() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; /// <summary> /// DataContractJsonSerializer以外で使用 /// </summary> private static readonly OfficeWorkerContractModel OfficeWorkerContract = new OfficeWorkerContractModel() { Company = "株式会社 自宅警備", Department = "開発部", Name = "山本太郎", Sex = "男", Age = 30, }; } /// <summary> /// DataContractJsonSerializer用の会社員モデル(属性指定が必要なため、念の為分ける) /// </summary> [DataContract(Name = "officeWorker")] public class OfficeWorkerContractModel { /// <summary> /// 会社 /// </summary> [DataMember(Name = "company",Order = 0)] public string Company { get; set; } /// <summary> /// 部署 /// </summary> [DataMember(Name = "department", Order = 1)] public string Department { get; set; } /// <summary> /// 名前 /// </summary> [DataMember(Name = "name", Order = 2)] public string Name { get; set; } /// <summary> /// 性別 /// </summary> [DataMember(Name = "sex", Order = 3)] public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> [DataMember(Name = "age", Order = 4)] public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } /// <summary> /// 会社員(DataContractJsonSerializer以外で使用) /// </summary> public class OfficeWorker { /// <summary> /// 会社 /// </summary> public string Company { get; set; } /// <summary> /// 部署 /// </summary> public string Department { get; set; } /// <summary> /// 名前 /// </summary> public string Name { get; set; } /// <summary> /// 性別 /// </summary> public string Sex { get; set; } /// <summary> /// 年齢 /// </summary> public uint Age { get; set; } /// <summary> /// 文字列変換 /// </summary> /// <returns></returns> public override string ToString() { return $"company={Company},department={Department},name={Name},sex={Sex},age={Age}"; } } } より正確に測るなら、もっと自前の実装を極力排除すべきである。しかし、そこまでやり切る技量は私にはない。 メイン関数 using BenchmarkDotNet.Running; namespace StudyJsonSerializer { class Program { static void Main( string[] args ) { //計測開始 var summary = BenchmarkRunner.Run<MeasurementJsonSerializer>(); } } } 測定結果のサマリ Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated UsingDataContractJsonSerializer 8.887 us 0.1315 us 0.1165 us 4.5013 - - 14152 B UsingJsonNet 3.912 us 0.0515 us 0.0457 us 1.3695 - - 4304 B UsingSystemTextJson 889.540 us 11.1422 us 8.6991 us 15.6250 7.8125 - 44213 B UsingSystemTextJsonWithoutSerializeOption 3.106 us 0.0252 us 0.0236 us 0.2136 - - 672 B ラベルの説明は下記の通り Mean : Arithmetic mean of all measurements Error : Half of 99.9% confidence interval StdDev : Standard deviation of all measurements Gen 0 : GC Generation 0 collects per 1000 operations Gen 1 : GC Generation 1 collects per 1000 operations Gen 2 : GC Generation 2 collects per 1000 operations Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B) 1 us : 1 Microsecond (0.000001 sec) 測定結果の詳細(一応控える) // * Detailed results * MeasurementJsonSerializer.UsingDataContractJsonSerializer: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 8.887 us, StdErr = 0.031 us (0.35%), N = 14, StdDev = 0.117 us Min = 8.754 us, Q1 = 8.792 us, Median = 8.873 us, Q3 = 8.940 us, Max = 9.203 us IQR = 0.148 us, LowerFence = 8.570 us, UpperFence = 9.162 us ConfidenceInterval = [8.756 us; 9.019 us] (CI 99.9%), Margin = 0.131 us (1.48% of Mean) Skewness = 1.17, Kurtosis = 4.18, MValue = 2 -------------------- Histogram -------------------- [8.691 us ; 8.977 us) | @@@@@@@@@@@@ [8.977 us ; 9.266 us) | @@ --------------------------------------------------- MeasurementJsonSerializer.UsingJsonNet: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.912 us, StdErr = 0.012 us (0.31%), N = 14, StdDev = 0.046 us Min = 3.848 us, Q1 = 3.878 us, Median = 3.912 us, Q3 = 3.934 us, Max = 4.004 us IQR = 0.056 us, LowerFence = 3.795 us, UpperFence = 4.018 us ConfidenceInterval = [3.860 us; 3.963 us] (CI 99.9%), Margin = 0.052 us (1.32% of Mean) Skewness = 0.32, Kurtosis = 2.08, MValue = 2 -------------------- Histogram -------------------- [3.823 us ; 4.029 us) | @@@@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJson: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 889.540 us, StdErr = 2.511 us (0.28%), N = 12, StdDev = 8.699 us Min = 875.936 us, Q1 = 883.029 us, Median = 890.843 us, Q3 = 895.243 us, Max = 904.026 us IQR = 12.213 us, LowerFence = 864.709 us, UpperFence = 913.563 us ConfidenceInterval = [878.398 us; 900.682 us] (CI 99.9%), Margin = 11.142 us (1.25% of Mean) Skewness = -0.15, Kurtosis = 1.7, MValue = 2 -------------------- Histogram -------------------- [870.949 us ; 909.013 us) | @@@@@@@@@@@@ --------------------------------------------------- MeasurementJsonSerializer.UsingSystemTextJsonWithoutSerializeOption: DefaultJob Runtime = .NET Core 3.1.8 (CoreCLR 4.700.20.41105, CoreFX 4.700.20.41903), X64 RyuJIT; GC = Concurrent Workstation Mean = 3.106 us, StdErr = 0.006 us (0.20%), N = 15, StdDev = 0.024 us Min = 3.052 us, Q1 = 3.097 us, Median = 3.104 us, Q3 = 3.122 us, Max = 3.142 us IQR = 0.025 us, LowerFence = 3.060 us, UpperFence = 3.160 us ConfidenceInterval = [3.081 us; 3.131 us] (CI 99.9%), Margin = 0.025 us (0.81% of Mean) Skewness = -0.49, Kurtosis = 2.76, MValue = 2 -------------------- Histogram -------------------- [3.039 us ; 3.155 us) | @@@@@@@@@@@@@@@ --------------------------------------------------- 執筆の際に参考にしたサイト/書籍 .NET Core 3.0の破壊的変更 dotnet/aspnetcore #7289 Microsoft社:DataContractJsonSerializer Microsoft社 JSONシリアル化 概要 書籍:実践で役立つC#プログラミングのイディオム/定石&パターン 12.3 JSONデータのシリアル化と逆シリアル化 @IT JSONデータを作成/解析するには?[C#/VB] System.Text.Json で文字エンコードをカスタマイズする方法 .NET 内で JSON のシリアル化と逆シリアル化 (マーシャリングとマーシャリングの解除) を行う方法 .NET Core:JsonSerializerの実践的な使い方 Newtonsoft.Json と System.Text.Json の相違点の表 【.NET/C#】メソッドのパフォーマンスを簡単に集計するライブラリの紹介
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SourceGeneratorのデバッグ

概要 Visual Studio 2019 バージョン 16.10 preview 2の新機能でSourceGeneratorのデバッガーサポートが追加されました。 デバッグに難があったので朗報ですね。 方法 ソース ジェネレーターのデバッガー サポートが追加されました。 まず、[Project Properties Debug](プロジェクト プロパティのデバッグ) ページで、"Roslyn コンポーネント" デバッガー オプションを有効にする必要があります。 そして、適切なターゲット プロジェクトを選択する必要があります。 次に、ジェネレーター プロジェクトで F5 キーを押します。これにより、ターゲット プロジェクトをビルドする CSC.exe/VBC.exe が開始され、ジェネレーター コードをデバッグできるようになります。 プロジェクトのプロパティを開いてもデバッガーオプションには見当たりません。 そこでSourceGeneratorのプロジェクトファイルを編集して<IsRoslynComponent>true</IsRoslynComponent>を加えます。 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>library</OutputType> <TargetFramework>netstandard2.0</TargetFramework> <IsRoslynComponent>true</IsRoslynComponent> </PropertyGroup> </Project> 改めてプロジェクトファイルのプロパティを開くと、デバッグページから無事にRoslyn Componentが選択できるようになります。 あとはリリースノート通りにターゲットプロジェクトを指定するだけですね。 ターゲットプロジェクトからのSourceGeneratorプロジェクトの参照に<OutputItemType>Analyzer</OutputItemType>がないと選択できないので注意しましょう。 参考 ‍https://github.com/dotnet/roslyn-sdk/pull/726
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【.NET】SQL構文解析ライブラリーのTSQL Parserを使用する

はじめに 2018年からサーバーリプレース作業(Windows Server 2008R2 → Windows Server 2016)でデータベースを Oracle 11g から PostgreSQL 9.6 に移行作業をしました。 PostgreSQL 9.6 のサポート終了期限が 2021年11月となっているため、PostgreSQL 12 に移行する準備を進めています。 最新のPostgreSQL 13 にしないのは、現時点(2021/04/18)で某N社のサポートがPostgreSQL 12 までだからです。 バージョン 初期リリース日 サポート終了期限 13 2020年09月 2025年11月 12 2019年10月 2024年11月 11 2018年10月 2023年11月 10 2017年10月 2022年11月 9.6 2016年09月 2021年11月 SQLの構文解析 20近くの既存アプリケーションのSQLを抜き出して、PostgreSQL 9.6とPostgreSQL 12で性能検証をする必要があります。1000近くのSQLがあるため、プログラムを組んで自動で性能検証をしたいと考えました。 自動でやるためには、登録・更新・削除するデータをDB上から抜き出し、該当のSQLに抽出条件や登録・更新データのパラメーターとして当てはめて実行したいのです。 当初は正規表現を多様してSQLを解析しようと考えていたのですが、他言語でSQL文を構文解析するという記事を見つけて、それならC#版でも似たようなのがあるだろうと、NuGetで見つけたのが「TSQL Parser」になります。 .NET Fiddleの紹介 オンライン上でC#のコードを簡単に試せる「.NET Fiddle」を初めて使用してみました。 他のC#オンラインエディタと違って、NuGet Packageを指定することで「TSQL.Parser」を使用できます。 ソースコード using System; using TSQL; using TSQL.Statements; using TSQL.Tokens; using System.Collections.Generic; namespace TSQLParserExample { public class ParseRule { public string Field {get;set;} public string Operator {get;set;} public string FieldValue {get;set;} public string SearchOperator { get; set; } } public class Program { public static void Main(string[] args) { TSQLSelectStatement select = TSQLStatementReader.ParseStatements(@" SELECT id, name FROM hoge h WHERE h.id = @id AND h.name = '@name' ")[0] as TSQLSelectStatement; List<ParseRule> rules = new List<ParseRule>(); if (select.Where != null) { Console.WriteLine("WHERE:"); ParseRule ruleValue = new ParseRule(); foreach (TSQLToken token in select.Where.Tokens) { string tokenType = token.Type.ToString(); string tokenText = token.Text; switch(tokenType) { case "Keyword": if(string.IsNullOrEmpty(ruleValue.SearchOperator)) { ruleValue.SearchOperator = tokenText; } else { ruleValue.Operator = tokenText; } break; case "Identifier": ruleValue.Field = tokenText; break; case "Variable": case "StringLiteral": case "NumericLiteral": ruleValue.FieldValue = tokenText; if(!string.IsNullOrEmpty(ruleValue.SearchOperator) && ruleValue.SearchOperator.Equals("WHERE")) { ruleValue.SearchOperator = "AND"; } rules.Add(ruleValue); ruleValue = new ParseRule(); break; default: break; } } for(var i = 0; i < rules.Count; i++) { var rule1 = rules[i]; Console.WriteLine("\t Field: " + rule1.Field + "\t Operator: " + rule1.Operator + " \t SearchOperator: " + rule1.SearchOperator + "\t Value: " + rule1.FieldValue); } } } } } 結果 ちなみにまだ研究中です。 PostgreSQLのNpgsqlのプレースホルダーは「: (コロン)」なのですが、TSQL Parserで変数として認識させるなら「@」に置き換えるとか必要かも知れません。 WHERE: Field: id Operator: SearchOperator: AND Value: @id Field: name Operator: SearchOperator: AND Value: '@name' 最後に 実は開発はこれからなので、何か気付いたことがあれば追記していきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む