- 投稿日:2020-05-23T19:19:51+09:00
構造体,クラスの参照渡しのC,C++,C#の違いと比較
仮引数(parameter)の3つのパターン
C++のコードを眺めていると以下のような3つのパターンの構造体、クラスの仮引数に出くわします。
//① void hoge( TEST test) { test.a++; } //② void hoge( TEST *test) { test->a++; } //③ void hoge( TEST &test) { test.a++; }Cでは①②のパターン、
C#では①のパターンのみ出くわします。上記3つのパターンの仮引数は各々の言語で以下のように分類されます。
C C++ C# 値渡し ①(※) ①(※) ①(TESTが構造体の場合) 参照(ポインタ)渡し ② ②③ ①(TESTがクラスの場合) 特に、①はC#は一見値渡しのように見えて(TESTがClassの場合)参照渡しされているということに注意してください。
実は①はC/C++の場合も値渡しではないかもしれません。
※構造体,クラスは基本的に値渡しされない
基本的にサイズが大きい構造体,クラスは値型として引数がコピーされるのではなく、
参照(ポインタ)渡しをして使用されることが多いです。よって、C/C++で①が使われることはほぼありません。
それにもかかわらず、①が使われる場面がCで割と出てきます。
そういうときは、typedef void* TEST;voidポインタがTESTとして新しい名前に定義されている。
またはtypedef struct { int a; } *TEST;structのポインタがTESTとして新しい名前に定義されている。
というように一見値型のように見えてポインタ型として、どこかで再定義されています。
C++は上記のような再定義はせず素直にクラスを定義し、③がよく使用されます。
つまり、①~③は十中八九、参照(ポインタ)渡しされています。
intやdoubleは単純ですが、
結局のところ、TESTのようなオリジナルの型の場合、定義を調べないと
その仮引数が値型として使われているか、参照型として使われているかはわかりません。上記の関数に渡すときの型
C C++ C# ① [実体⇒実体]
TEST test;
hoge(test);
or
TEST *test;
hoge(*test);
※[実体⇒実体]
TEST test;
hoge(test);
or
TEST *test = new TEST();
hoge(*test);
※[実体⇒参照]
(クラスの場合)
TEST test = new TEST();
hoge(test);② [ポインタ⇒ポインタ]
TEST *test;
hoge(test)
or
TEST test;
hoge(&test)[ポインタ⇒ポインタ]
TEST *test = new TEST();
hoge(test)
or
TEST test;
hoge(&test)- ③ - [実体⇒参照]
TEST test;
hoge(test);
or
TEST *test = new TEST();
hoge(*test);
- あとがき
ポインタの概念をよく理解せず、オブジェクト指向のPython3やらC#をやって、C/C++をやると混乱します。
某漫画のように
「最初の入り口がオブジェクト指向なんだよね最近の子って」
「そうそう そんなんじゃ使いものにならねえっての」
となります。
- 投稿日:2020-05-23T19:19:51+09:00
その構造体、クラスの仮引数は値か参照か[C,C++,C#]
仮引数(parameter)の3つのパターン
C++のコードを眺めていると以下のような3つのパターンの構造体、クラスの仮引数に出くわします。
//① void hoge( TEST test) { test.a++; } //② void hoge( TEST *test) { test->a++; } //③ void hoge( TEST &test) { test.a++; }Cでは①②のパターン、
C#では①のパターンのみ出くわします。上記3つのパターンの仮引数は各々の言語で以下のように分類されます。
C C++ C# 値 ①(※) ①(※) ①(TESTが構造体の場合) 参照 ② ②③ ①(TESTがクラスの場合) 特に、①はC#は一見、値のように見えて(TESTがクラスの場合)参照されているということに注意してください。
実は①はC/C++の場合も値ではないかもしれません。
※構造体,クラスは基本的に値渡しされない
基本的にサイズが大きい構造体,クラスは値型として引数がコピーされるのではなく、
ポインタ渡し、または参照渡しをして使用されることが多いです。よって、C/C++で①が使われることはほぼありません。
それにもかかわらず、①が使われる場面がCで割と出てきます。
そういうときは、typedef void* TEST;voidポインタがTESTとして新しい名前に定義されている。
またはtypedef struct { int a; } *TEST;structのポインタがTESTとして新しい名前に定義されている。
というように一見値型のように見えてポインタ型として、どこかで再定義されています。
C++は上記のような再定義はせず素直にクラスを定義し、③がよく使用されます。
つまり、①~③の仮引数は十中八九、参照です。
intやdoubleは単純ですが、
結局のところ、TESTのようなオリジナルの型の場合、定義を調べないと
その仮引数が値として使われているか、参照として使われているかはわかりません。上記の関数に渡すときの型
C C++ C# ① [実体⇒実体]
TEST test;
hoge(test);
or
TEST *test;
hoge(*test);
※[実体⇒実体]
TEST test;
hoge(test);
or
TEST *test = new TEST();
hoge(*test);
※[実体⇒参照]
(クラスの場合)
TEST test = new TEST();
hoge(test);② [ポインタ⇒ポインタ]
TEST *test;
hoge(test)
or
TEST test;
hoge(&test)[ポインタ⇒ポインタ]
TEST *test = new TEST();
hoge(test)
or
TEST test;
hoge(&test)- ③ - [実体⇒参照]
TEST test;
hoge(test);
or
TEST *test = new TEST();
hoge(*test);
- あとがき
ポインタの概念をよく理解せず、オブジェクト指向のPython3やらC#をやって、C/C++をやると混乱します。
某漫画のように
「最初の入り口がオブジェクト指向なんだよね最近の子って」
「そうそう そんなんじゃ使いものにならねえっての」
となります。
- 投稿日:2020-05-23T19:19:51+09:00
その仮引数(構造体,クラス)は値か参照か[C,C++,C#]
仮引数(parameter)の3つのパターン
C++のコードを眺めていると以下のような3つのパターンの構造体、クラスの仮引数に出くわします。
//① void hoge( TEST test) { test.a++; } //② void hoge( TEST *test) { test->a++; } //③ void hoge( TEST &test) { test.a++; }Cでは①②のパターン、
C#では①のパターンのみ出くわします。上記3つのパターンの仮引数は各々の言語で以下のように分類されます。
C C++ C# 値 ①(※) ①(※) ①(TESTが構造体の場合) 参照 ② ②③ ①(TESTがクラスの場合) 特に、①はC#は一見、値のように見えて(TESTがクラスの場合)参照されているということに注意してください。
実は①はC/C++の場合も値ではないかもしれません。
※構造体,クラスは基本的に値渡しされない
基本的にサイズが大きい構造体,クラスは値型として引数がコピーされるのではなく、
ポインタ渡し、または参照渡しをして使用されることが多いです。よって、C/C++で①が使われることはほぼありません。
それにもかかわらず、①が使われる場面がCで割と出てきます。
そういうときは、
Cの場合、typedef void* TEST;voidポインタがTESTとして新しい名前に定義されている。
またはtypedef struct { int a; } *TEST;structのポインタがTESTとして新しい名前に定義されている。
C++の場合、
typedef shared_ptr<foo> TEST;fooのポインタがTESTとして新しい名前に定義されている。
というように一見値型のように見えてポインタ型として、どこかで再定義されています。
(区別できるように、Cの場合 PTEST、C++の場合 TESTPtrというように接頭語、接尾語が付けられることが多い)つまり、①~③の仮引数は十中八九、参照です。
intやdoubleのような値型は単純ですが、
結局のところ、TESTのようなオリジナルの型の場合、定義を調べないと
その仮引数が値として使われているか、参照として使われているかはわかりません。上記の関数に渡すときの型
C C++ C# ① [実体⇒実体]
TEST test;
hoge(test);
or
TEST *test;
hoge(*test);
※[実体⇒実体]
TEST test;
hoge(test);
or
TEST *test = new TEST();
hoge(*test);
※[実体⇒参照]
(クラスの場合)
TEST test = new TEST();
hoge(test);② [ポインタ⇒ポインタ]
TEST *test;
hoge(test)
or
TEST test;
hoge(&test)[ポインタ⇒ポインタ]
TEST *test = new TEST();
hoge(test)
or
TEST test;
hoge(&test)- ③ - [実体⇒参照]
TEST test;
hoge(test);
or
TEST *test = new TEST();
hoge(*test);
- あとがき
ポインタの概念をよく理解せず、オブジェクト指向のPython3やらC#をやって、C/C++をやると混乱します。
某漫画のように
「最初の入り口がオブジェクト指向なんだよね最近の子って」
「そうそう そんなんじゃ使いものにならねえっての」
となります。
- 投稿日:2020-05-23T16:06:05+09:00
Q.プリキュアを頭文字だけで区別することはできるか
Summary
Q.プリキュアを頭文字だけで区別することはできるか
A.作品によるお断り
本記事の内容は、2020年5月24日(「ヒーリングっど♥プリキュア」本編第12話まで&おさらいセレクション1第5話まで放送済み)時点での状況を元に記載しています。
以下、特記のない限り「現在」はこの日時点をさします。はじまり
現在放送中「ヒーリングっど♥プリキュア」(←ABCサイトへリンク)のプリキュアは次の3人です。
※第12話時点
- キュアグレース
- キュアフォンテーヌ
- キュアスパークル
それぞれ英語で書く2と、
- Cure Grace
- Cure Fontaine
- Cure Sparkle
となり、「G」「F」「S」で一意に識別することができます。
この1つ前の作品「スター☆トゥインクルプリキュア」(←東映アニメーションサイトへリンク)ではどうでしょうか?3
プリキュア名 英語 頭文字 キュアスター Cure Star S キュアミルキー Cure Milky M キュアソレイユ Cure Soleil S キュアセレーネ Cure Selene S キュアコスモ Cure Cosmo C Sで3人かぶりがあります。
というように、作品によってプリキュアを頭文字だけで区別することはできるか否かが変わります。
果たして、第1作「ふたりはプリキュア」(←東映アニメーションサイトへリンク)から、第17作「ヒーリングっど♥プリキュア」まで、どうなっているのでしょうか?
というのをいちいち調べているのもあれなので、プログラムを作って確認してみることとします。なお、頭文字を設定する際には、「キュア」は省くものとします(そうしないと「Cure」の「C」が多数を占めてしまうため)。
使用するもの
- Visual Studio 2019
古いバージョンでの動作は検証していません- @sue445 様謹製 プリキュアのRuby実装「rubicure」 の データ
Rubyは全くわからないのでYAMLのデータだけいただきます- C#
- aaubry/YamlDotNet: YamlDotNet is a .NET library for YAML
使い方については下記の記事を参考にしました:
.NET上でyamlファイルをデシリアライズしたくなったときはYamlDotNetを! by @toshi0607 様対象とするプリキュア
原則的に、テレビシリーズで登場したプリキュアに限定します。
なので、映画のみに登場している「キュアエコー」は対象外となります。プログラムを書く
プログラム置き場
プログラムはここに置いておきます。
Hokkaidosm/precure_initialシリーズデータを取得する
https://github.com/sue445/rubicure/tree/master/config/series.yml からシリーズデータを取得します。
シリーズデータには、「シリーズ名」と「登場するプリキュアのキー」を含みます。
長いので折りたたむルン
Program.csusing System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; using YamlDotNet.Serialization; namespace precure_initial { class Program { /// <summary> /// rubicure/config のパス /// </summary> static readonly string rubicureConfigPath = "../../rubicure/config/"; /// <summary> /// シリーズデータ. /// </summary> class Series { /// <summary> /// シリーズ名. /// </summary> public string Name { get; set; } /// <summary> /// シリーズに出演するプリキュアのキー. /// </summary> public List<string> PrecureKeys { get; set; } } static void Main(string[] args) { Dictionary<string, Series> seriesList = LoadSeries(); foreach (var series in seriesList) { Console.WriteLine($"シリーズキー:{series.Key}"); Console.WriteLine($"シリーズ名:{series.Value.Name}"); Console.WriteLine($"プリキュアキー:[{string.Join(", ", series.Value.PrecureKeys)}]"); Console.WriteLine(); } } /// <summary> /// rubicure/config/series.yml からシリーズキーとシリーズデータを取得する. /// </summary> /// <returns>シリーズキーをキーとする<code>Dictionary</code>.</returns> static Dictionary<string, Series> LoadSeries() { var filePath = rubicureConfigPath + "series.yml"; var input = new StreamReader(filePath, Encoding.UTF8); var deserializer = new Deserializer(); Dictionary<string, Dictionary<string, object>> series_tmp = deserializer.Deserialize<Dictionary<string, Dictionary<string, object>>>(input); var series_directory = new Dictionary<string, Series>(); foreach (var series in series_tmp) { if (series.Value.Count == 1) { // エイリアスは飛ばす continue; } series.Value.TryGetValue("title", out object seriesName); series.Value.TryGetValue("girls", out object precureList); List<string> precures = new List<string>(); foreach (var precure in (List<object>)precureList) { precures.Add((string)precure); } series_directory.Add(series.Key, new Series { Name = (string)seriesName, PrecureKeys = precures }); } return series_directory; } } }このときの出力はこんな感じになります4。
長いので折りたたむルン
シリーズキー:unmarked シリーズ名:ふたりはプリキュア プリキュアキー:[cure_black, cure_white] シリーズキー:max_heart シリーズ名:ふたりはプリキュア Max Heart プリキュアキー:[cure_black, cure_white, shiny_luminous] シリーズキー:splash_star シリーズ名:ふたりはプリキュア Splash☆Star プリキュアキー:[cure_bloom, cure_egret] シリーズキー:yes シリーズ名:Yes! プリキュア5 プリキュアキー:[cure_dream, cure_rouge, cure_lemonade, cure_mint, cure_aqua] シリーズキー:yes_gogo シリーズ名:Yes! プリキュア5 Go Go! プリキュアキー:[cure_dream, cure_rouge, cure_lemonade, cure_mint, cure_aqua, milky_rose] シリーズキー:fresh シリーズ名:フレッシュプリキュア! プリキュアキー:[cure_peach, cure_berry, cure_pine, cure_passion] シリーズキー:heart_catch シリーズ名:ハートキャッチプリキュア! プリキュアキー:[cure_blossom, cure_marine, cure_sunshine, cure_moonlight] シリーズキー:suite シリーズ名:スイートプリキュア♪ プリキュアキー:[cure_melody, cure_rhythm, cure_beat, cure_muse] シリーズキー:smile シリーズ名:スマイルプリキュア! プリキュアキー:[cure_happy, cure_sunny, cure_peace, cure_march, cure_beauty] シリーズキー:dokidoki シリーズ名:ドキドキ!プリキュア プリキュアキー:[cure_heart, cure_diamond, cure_rosetta, cure_sword, cure_ace] シリーズキー:happiness_charge シリーズ名:ハピネスチャージプリキュア! プリキュアキー:[cure_lovely, cure_princess, cure_honey, cure_fortune] シリーズキー:go_princess シリーズ名:Go!プリンセスプリキュア プリキュアキー:[cure_flora, cure_mermaid, cure_twinkle, cure_scarlet] シリーズキー:maho_girls シリーズ名:魔法つかいプリキュア! プリキュアキー:[cure_miracle, cure_magical, cure_felice] シリーズキー:a_la_mode シリーズ名:キラキラ☆プリキュアアラモード プリキュアキー:[cure_whip, cure_custard, cure_gelato, cure_macaron, cure_chocolat, cure_parfait] シリーズキー:hugtto シリーズ名:HUGっと!プリキュア プリキュアキー:[cure_yell, cure_ange, cure_etoile, cure_macherie, cure_amour] シリーズキー:star_twinkle シリーズ名:スター☆トゥインクルプリキュア プリキュアキー:[cure_star, cure_milky, cure_soleil, cure_selene, cure_cosmo] シリーズキー:healingood シリーズ名:ヒーリングっど?プリキュア プリキュアキー:[cure_grace, cure_fontaine, cure_sparkle]プリキュアデータを取得する
各シリーズのプリキュアのデータは、 https://github.com/sue445/rubicure/tree/master/config/girls/ 以下にYAML形式で入っています。
それぞれのファイルには、それぞれのシリーズで初めて登場したプリキュアのデータのみが含まれています。例)キュアブラックとキュアホワイトは「ふたりはプリキュア」と「ふたりはプリキュア Max Heart」の両方に登場していますが、最初に登場した「ふたりはプリキュア」のファイル
001_unmarked.yml
にのみデータが含まれています。ということで、いったん全てのプリキュアのデータを取り込みます。
長いので折りたたむニャン
Program.csusing System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; using YamlDotNet.Serialization; namespace precure_initial { class Program { /// <summary> /// rubicure/config のパス /// </summary> static readonly string rubicureConfigPath = "../../rubicure/config/"; class Series { // 省略 } /// <summary> /// プリキュアデータ. /// </summary> class Precure { /// <summary> /// プリキュア英語名. /// </summary> public string GirlName { get; set; } /// <summary> /// プリキュア日本語名. /// </summary> public string PrecureName { get; set; } /// <summary> /// イニシャル. /// </summary> public string Initial { get; set; } } static void Main(string[] args) { Dictionary<string, Series> seriesList = LoadSeries(); Dictionary<string, Precure> precureList = LoadPrecures(); foreach (var precure in precureList) { Console.WriteLine($"プリキュアキー:{precure.Key}"); Console.WriteLine($"プリキュア名:{precure.Value.PrecureName} / {precure.Value.GirlName}"); Console.WriteLine($"頭文字:{precure.Value.Initial}"); Console.WriteLine(); } } static Dictionary<string, Series> LoadSeries() { // 省略 } /// <summary> /// 全てのプリキュアのデータを読み込む. /// </summary> /// <returns>プリキュアキーをキーとする<code>Dictionary</code>.</returns> static Dictionary<string, Precure> LoadPrecures() { Dictionary<string, Precure> precures = new Dictionary<string, Precure>(); var rootPath = rubicureConfigPath + "girls/"; string[] files = Directory.GetFiles(rootPath, "*.yml", SearchOption.TopDirectoryOnly); foreach (string file in files) { var input = new StreamReader(file, Encoding.UTF8); var deserializer = new Deserializer(); Dictionary<string, Dictionary<string, object>> girls_tmp = deserializer.Deserialize<Dictionary<string, Dictionary<string, object>>>(input); foreach (var girl in girls_tmp) { if (girl.Value.Count == 1) { // エイリアスは飛ばす continue; } string girlName = girl.Key, precureName = null; if (girl.Value.TryGetValue("precure_name", out object precureNameObj)) { precureName = (string)precureNameObj; } girlName = ToPascal(girlName); string initial = girlName.Replace("Cure ", "").Substring(0, 1); precures.Add(girl.Key, new Precure { GirlName = girlName, PrecureName = precureName, Initial = initial }); } } return precures; } /// <summary> /// cure_black => Cure Blackのような変換 /// ref. https://increment-i.hateblo.jp/entry/csharp/regularexpression/pascal /// </summary> /// <param name="text">変換元</param> /// <returns>変換結果</returns> private static string ToPascal(string text) { return Regex.Replace( text.Replace("_", " "), @"\b[a-z]", match => match.Value.ToUpper()); } } }※上記
ToPascal
は 正規表現を利用して Pascal 形式に変換する - C# - インクリメンタルなカイハツにっき 記載のコードを一部変更したものです。このときの出力はこんな感じになります5。
長いので折りたたむニャン
プリキュアキー:cure_black プリキュア名:キュアブラック / Cure Black 頭文字:B プリキュアキー:cure_white プリキュア名:キュアホワイト / Cure White 頭文字:W プリキュアキー:shiny_luminous プリキュア名:シャイニールミナス / Shiny Luminous 頭文字:S プリキュアキー:cure_bloom プリキュア名:キュアブルーム / Cure Bloom 頭文字:B プリキュアキー:cure_egret プリキュア名:キュアイーグレット / Cure Egret 頭文字:E プリキュアキー:cure_bright プリキュア名:キュアブライト / Cure Bright 頭文字:B プリキュアキー:cure_windy プリキュア名:キュアウィンディ / Cure Windy 頭文字:W プリキュアキー:cure_dream プリキュア名:キュアドリーム / Cure Dream 頭文字:D プリキュアキー:cure_rouge プリキュア名:キュアルージュ / Cure Rouge 頭文字:R プリキュアキー:cure_lemonade プリキュア名:キュアレモネード / Cure Lemonade 頭文字:L プリキュアキー:cure_mint プリキュア名:キュアミント / Cure Mint 頭文字:M プリキュアキー:cure_aqua プリキュア名:キュアアクア / Cure Aqua 頭文字:A プリキュアキー:milky_rose プリキュア名:ミルキィローズ / Milky Rose 頭文字:M プリキュアキー:cure_peach プリキュア名:キュアピーチ / Cure Peach 頭文字:P プリキュアキー:cure_berry プリキュア名:キュアベリー / Cure Berry 頭文字:B プリキュアキー:cure_pine プリキュア名:キュアパイン / Cure Pine 頭文字:P プリキュアキー:cure_passion プリキュア名:キュアパッション / Cure Passion 頭文字:P プリキュアキー:cure_blossom プリキュア名:キュアブロッサム / Cure Blossom 頭文字:B プリキュアキー:cure_marine プリキュア名:キュアマリン / Cure Marine 頭文字:M プリキュアキー:cure_sunshine プリキュア名:キュアサンシャイン / Cure Sunshine 頭文字:S プリキュアキー:cure_moonlight プリキュア名:キュアムーンライト / Cure Moonlight 頭文字:M プリキュアキー:cure_melody プリキュア名:キュアメロディ / Cure Melody 頭文字:M プリキュアキー:cure_rhythm プリキュア名:キュアリズム / Cure Rhythm 頭文字:R プリキュアキー:cure_beat プリキュア名:キュアビート / Cure Beat 頭文字:B プリキュアキー:cure_muse プリキュア名:キュアミューズ / Cure Muse 頭文字:M プリキュアキー:cure_happy プリキュア名:キュアハッピー / Cure Happy 頭文字:H プリキュアキー:cure_sunny プリキュア名:キュアサニー / Cure Sunny 頭文字:S プリキュアキー:cure_peace プリキュア名:キュアピース / Cure Peace 頭文字:P プリキュアキー:cure_march プリキュア名:キュアマーチ / Cure March 頭文字:M プリキュアキー:cure_beauty プリキュア名:キュアビューティ / Cure Beauty 頭文字:B プリキュアキー:cure_heart プリキュア名:キュアハート / Cure Heart 頭文字:H プリキュアキー:cure_diamond プリキュア名:キュアダイヤモンド / Cure Diamond 頭文字:D プリキュアキー:cure_rosetta プリキュア名:キュアロゼッタ / Cure Rosetta 頭文字:R プリキュアキー:cure_sword プリキュア名:キュアソード / Cure Sword 頭文字:S プリキュアキー:cure_ace プリキュア名:キュアエース / Cure Ace 頭文字:A プリキュアキー:cure_lovely プリキュア名:キュアラブリー / Cure Lovely 頭文字:L プリキュアキー:cure_princess プリキュア名:キュアプリンセス / Cure Princess 頭文字:P プリキュアキー:cure_honey プリキュア名:キュアハニー / Cure Honey 頭文字:H プリキュアキー:cure_fortune プリキュア名:キュアフォーチュン / Cure Fortune 頭文字:F プリキュアキー:cure_flora プリキュア名:キュアフローラ / Cure Flora 頭文字:F プリキュアキー:cure_mermaid プリキュア名:キュアマーメイド / Cure Mermaid 頭文字:M プリキュアキー:cure_twinkle プリキュア名:キュアトゥインクル / Cure Twinkle 頭文字:T プリキュアキー:cure_scarlet プリキュア名:キュアスカーレット / Cure Scarlet 頭文字:S プリキュアキー:cure_miracle プリキュア名:キュアミラクル / Cure Miracle 頭文字:M プリキュアキー:cure_magical プリキュア名:キュアマジカル / Cure Magical 頭文字:M プリキュアキー:cure_felice プリキュア名:キュアフェリーチェ / Cure Felice 頭文字:F プリキュアキー:cure_whip プリキュア名:キュアホイップ / Cure Whip 頭文字:W プリキュアキー:cure_custard プリキュア名:キュアカスタード / Cure Custard 頭文字:C プリキュアキー:cure_gelato プリキュア名:キュアジェラート / Cure Gelato 頭文字:G プリキュアキー:cure_macaron プリキュア名:キュアマカロン / Cure Macaron 頭文字:M プリキュアキー:cure_chocolat プリキュア名:キュアショコラ / Cure Chocolat 頭文字:C プリキュアキー:cure_parfait プリキュア名:キュアパルフェ / Cure Parfait 頭文字:P プリキュアキー:cure_yell プリキュア名:キュアエール / Cure Yell 頭文字:Y プリキュアキー:cure_ange プリキュア名:キュアアンジュ / Cure Ange 頭文字:A プリキュアキー:cure_etoile プリキュア名:キュアエトワール / Cure Etoile 頭文字:E プリキュアキー:cure_macherie プリキュア名:キュアマシェリ / Cure Macherie 頭文字:M プリキュアキー:cure_amour プリキュア名:キュアアムール / Cure Amour 頭文字:A プリキュアキー:cure_star プリキュア名:キュアスター / Cure Star 頭文字:S プリキュアキー:cure_milky プリキュア名:キュアミルキー / Cure Milky 頭文字:M プリキュアキー:cure_soleil プリキュア名:キュアソレイユ / Cure Soleil 頭文字:S プリキュアキー:cure_selene プリキュア名:キュアセレーネ / Cure Selene 頭文字:S プリキュアキー:cure_cosmo プリキュア名:キュアコスモ / Cure Cosmo 頭文字:C プリキュアキー:cure_grace プリキュア名:キュアグレース / Cure Grace 頭文字:G プリキュアキー:cure_fontaine プリキュア名:キュアフォンテーヌ / Cure Fontaine 頭文字:F プリキュアキー:cure_sparkle プリキュア名:キュアスパークル / Cure Sparkle 頭文字:S プリキュアキー:cure_echo プリキュア名:キュアエコー / Cure Echo 頭文字:Eプリキュアリストを作成する
ここまででできあがったものは次の通りです:
- シリーズキーとシリーズ名、プリキュアキーの対応
- プリキュアキーとプリキュアの対応
これらを使って、いよいよプリキュアリストを作成していきます。
こんな出力が得られればと思います:スター☆トゥインクルプリキュア プリキュア名,英語,頭文字 キュアスター,Cure Star,S キュアミルキー,Cure Milky,M キュアソレイユ,Cure Soleil,S キュアセレーネ,Cure Selene,S キュアコスモ,Cure Cosmo,C 人数:5 かぶり:あり ヒーリングっど?プリキュア プリキュア名,英語,頭文字 キュアグレース,Cure Grace,G キュアフォンテーヌ,Cure Fontaine,F キュアスパークル,Cure Sparkle,S 人数:3 かぶり:なし
長いので折りたたむラビ
Program.csusing System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; using YamlDotNet.Serialization; namespace precure_initial { class Program { /// <summary> /// rubicure/config のパス /// </summary> static readonly string rubicureConfigPath = "../../rubicure/config/"; class Series { // 省略 } class Precure { // 省略 } static void Main(string[] args) { Dictionary<string, Series> seriesList = LoadSeries(); Dictionary<string, Precure> precureList = LoadPrecures(); foreach (var series in seriesList) { OutputPrecureList(series.Value.Name, series.Value.PrecureKeys, precureList); } } static Dictionary<string, Series> LoadSeries() { // 省略 } static Dictionary<string, Precure> LoadPrecures() { // 省略 } /// <summary> /// プリキュアリストを出力する. /// </summary> /// <param name="seriesName">シリーズ名</param> /// <param name="precures">シリーズプリキュアキーリスト</param> /// <param name="precureList">プリキュアリスト</param> static void OutputPrecureList(string seriesName, List<string> precures, Dictionary<string, Precure> precureList) { Console.WriteLine(seriesName); Console.WriteLine("プリキュア名,英語,頭文字"); // イニシャルリスト List<string> precureInitials = new List<string>(); // イニシャル重複チェック bool initialsConflict = false; foreach (var precureKey in precures) { if (!precureList.TryGetValue(precureKey, out Precure precure)) { // 万が一取得できなかった場合はスキップする continue; } if (precureInitials.Contains(precure.Initial)) { initialsConflict = true; } precureInitials.Add(precure.Initial); Console.WriteLine($"{precure.PrecureName},{precure.GirlName},{precure.Initial}"); } Console.WriteLine($"人数:{precureInitials.Count}"); Console.WriteLine($"かぶり:{(initialsConflict ? "あり" : "なし")}"); Console.WriteLine(); } private static string ToPascal(string text) { // 省略 } } }結果は省略します。
結果のまとめ
上記で出力したデータを整形したのが下記の結果となります。
ふたりはプリキュア
プリキュア名 英語 頭文字 キュアブラック Cure Black B キュアホワイト Cure White W 人数:2
かぶり:なしふたりはプリキュア Max Heart
プリキュア名 英語 頭文字 キュアブラック Cure Black B キュアホワイト Cure White W シャイニールミナス Shiny Luminous S 人数:3
かぶり:なしふたりはプリキュア Splash☆Star
プリキュア名 英語 頭文字 キュアブルーム Cure Bloom B キュアイーグレット Cure Egret E 人数:2
かぶり:なしYes! プリキュア5
プリキュア名 英語 頭文字 キュアドリーム Cure Dream D キュアルージュ Cure Rouge R キュアレモネード Cure Lemonade L キュアミント Cure Mint M キュアアクア Cure Aqua A 人数:5
かぶり:なしYes! プリキュア5 Go Go!
プリキュア名 英語 頭文字 キュアドリーム Cure Dream D キュアルージュ Cure Rouge R キュアレモネード Cure Lemonade L キュアミント Cure Mint M キュアアクア Cure Aqua A ミルキィローズ Milky Rose M 人数:6
かぶり:ありフレッシュプリキュア!
プリキュア名 英語 頭文字 キュアピーチ Cure Peach P キュアベリー Cure Berry B キュアパイン Cure Pine P キュアパッション Cure Passion P 人数:4
かぶり:ありハートキャッチプリキュア!
プリキュア名 英語 頭文字 キュアブロッサム Cure Blossom B キュアマリン Cure Marine M キュアサンシャイン Cure Sunshine S キュアムーンライト Cure Moonlight M 人数:4
かぶり:ありスイートプリキュア♪
プリキュア名 英語 頭文字 キュアメロディ Cure Melody M キュアリズム Cure Rhythm R キュアビート Cure Beat B キュアミューズ Cure Muse M 人数:4
かぶり:ありスマイルプリキュア!
プリキュア名 英語 頭文字 キュアハッピー Cure Happy H キュアサニー Cure Sunny S キュアピース Cure Peace P キュアマーチ Cure March M キュアビューティ Cure Beauty B 人数:5
かぶり:なしドキドキ!プリキュア
プリキュア名 英語 頭文字 キュアハート Cure Heart H キュアダイヤモンド Cure Diamond D キュアロゼッタ Cure Rosetta R キュアソード Cure Sword S キュアエース Cure Ace A 人数:5
かぶり:なしハピネスチャージプリキュア!
プリキュア名 英語 頭文字 キュアラブリー Cure Lovely L キュアプリンセス Cure Princess P キュアハニー Cure Honey H キュアフォーチュン Cure Fortune F 人数:4
かぶり:なしGo!プリンセスプリキュア
プリキュア名 英語 頭文字 キュアフローラ Cure Flora F キュアマーメイド Cure Mermaid M キュアトゥインクル Cure Twinkle T キュアスカーレット Cure Scarlet S 人数:4
かぶり:なし魔法つかいプリキュア!
プリキュア名 英語 頭文字 キュアミラクル Cure Miracle M キュアマジカル Cure Magical M キュアフェリーチェ Cure Felice F 人数:3
かぶり:ありキラキラ☆プリキュアアラモード
プリキュア名 英語 頭文字 キュアホイップ Cure Whip W キュアカスタード Cure Custard C キュアジェラート Cure Gelato G キュアマカロン Cure Macaron M キュアショコラ Cure Chocolat C キュアパルフェ Cure Parfait P 人数:6
かぶり:ありHUGっと!プリキュア
プリキュア名 英語 頭文字 キュアエール Cure Yell Y キュアアンジュ Cure Ange A キュアエトワール Cure Etoile E キュアマシェリ Cure Macherie M キュアアムール Cure Amour A 人数:5
かぶり:ありスター☆トゥインクルプリキュア
プリキュア名 英語 頭文字 キュアスター Cure Star S キュアミルキー Cure Milky M キュアソレイユ Cure Soleil S キュアセレーネ Cure Selene S キュアコスモ Cure Cosmo C 人数:5
かぶり:ありヒーリングっど♥プリキュア
プリキュア名 英語 頭文字 キュアグレース Cure Grace G キュアフォンテーヌ Cure Fontaine F キュアスパークル Cure Sparkle S 人数:3
かぶり:なし考察
まずはシリーズ別の状況を表にまとめてみます。
シリーズ名 人数 かぶり ふたりはプリキュア 2 なし ふたりはプリキュア Max Heart 3 なし ふたりはプリキュア Splash☆Star 2 なし Yes! プリキュア5 5 なし Yes! プリキュア5 Go Go! 6 あり フレッシュプリキュア! 4 あり ハートキャッチプリキュア! 4 あり スイートプリキュア♪ 4 あり スマイルプリキュア! 5 なし ドキドキ!プリキュア 5 なし ハピネスチャージプリキュア! 4 なし Go!プリンセスプリキュア 4 なし 魔法つかいプリキュア! 3 あり キラキラ☆プリキュアアラモード 6 あり HUGっと!プリキュア 5 あり スター☆トゥインクルプリキュア 5 あり ヒーリングっど♥プリキュア 3 なし かぶりありシリーズ数:8
かぶりなしシリーズ数:9このことから、一概に「プリキュアを頭文字だけで区別することはできる」とも「プリキュアを頭文字だけで区別することはできない」ともいえない状況です。
人数とかぶりの関係
「人数が多いとかぶりは多くなるのか?少なくなるのか?」というのを調べてみます。
人数 かぶりありシリーズ数 かぶりなしシリーズ数 合計シリーズ数 かぶりあり率 2 0 2 2 0.00% 3 1 2 3 33.33% 4 3 2 5 60.00% 5 2 3 5 40.00% 6 2 0 2 100.00% N=17なので一概には言いにくいのですが、4人ないし6人の場合はかぶりがあることが多いようです。一方で2人や3人だとかぶりは少ないようです。
一番使われているイニシャルは?
プリキュア63人のイニシャルで最も多く使われているイニシャルはどれでしょうか?
※複数シリーズに登場しているプリキュアは重複カウントしていません。またここには「キュアエコー」を含みません。
順位 イニシャル 使用回数 プリキュア名 1 M 13 キュアミント, ミルキィローズ, キュアマリン, キュアムーンライト, キュアメロディ, キュアミューズ, キュアマーチ, キュアマーメイド, キュアミラクル, キュアマジカル, キュアマカロン, キュアマシェリ, キュアミルキー 2 S 9 シャイニールミナス, キュアサンシャイン, キュアサニー, キュアソード, キュアスカーレット, キュアスター, キュアソレイユ, キュアセレーネ, キュアスパークル 3 B 6 キュアブラック, キュアブルーム, キュアベリー, キュアブロッサム, キュアビート, キュアビューティ 3 P 6 キュアピーチ, キュアパイン, キュアパッション, キュアピース, キュアプリンセス, キュアパルフェ 5 A 4 キュアアクア, キュアエース, キュアアンジュ, キュアアムール 5 F 4 キュアフォーチュン, キュアフローラ, キュアフェリーチェ, キュアフォンテーヌ 7 R 3 キュアルージュ, キュアリズム, キュアロゼッタ 7 C 3 キュアカスタード, キュアショコラ, キュアコスモ 7 H 3 キュアハッピー, キュアハート, キュアハニー 10 L 2 キュアレモネード, キュアラブリー 10 D 2 キュアドリーム, キュアダイヤモンド 10 E 2 キュアイーグレット, キュアエトワール 10 G 2 キュアジェラート, キュアグレース 10 W 2 キュアホワイト, キュアホイップ 15 T 1 キュアトゥインクル 15 Y 1 キュアエール 17 I 0 17 J 0 17 K 0 17 N 0 17 O 0 17 Q 0 17 U 0 17 V 0 17 X 0 17 Z 0 直近5作品では?
直近の5作品「魔法つかいプリキュア!」「キラキラ☆プリキュアアラモード」「HUGっと!プリキュア」「スター☆トゥインクルプリキュア」「ヒーリングっど♥プリキュア」に絞ると、こんな感じになります。
※使用実績のないイニシャルは下表では省略しています。
順位 イニシャル 使用回数 プリキュア名 全体順位 全体回数 比率 1 M 5 キュアミラクル, キュアマジカル, キュアマカロン, キュアマシェリ, キュアミルキー 1 13 38.46% 2 S 4 キュアスター, キュアソレイユ, キュアセレーネ, キュアスパークル 2 9 44.44% 3 C 3 キュアカスタード, キュアショコラ, キュアコスモ 7 3 100.00% 4 A 2 キュアアンジュ, キュアアムール 5 4 50.00% 4 F 2 キュアフェリーチェ, キュアフォンテーヌ 5 4 50.00% 4 G 2 キュアジェラート, キュアグレース 10 2 100.00% 7 P 1 キュアパルフェ 3 6 16.67% 7 E 1 キュアエトワール 10 2 50.00% 7 W 1 キュアホイップ 10 2 50.00% 7 Y 1 キュアエール 15 1 100.00% 11 B 0 3 6 0.00% 11 H 0 7 3 0.00% 11 R 0 7 3 0.00% 11 D 0 10 2 0.00% 11 L 0 10 2 0.00% 11 T 0 15 1 0.00% この5作品で初めて使われたのは、「C」「G」「Y」でした。
まとめ
ここまで「プリキュアのイニシャル」を使っていろいろとデータ処理を行ってきました。
果たして64人目のプリキュアは、どんなイニシャルを持つのでしょうか…?
それ以前にコロナが落ち着いて本編が再開するのはいつになるのでしょうか…?1
新型コロナウイルス感染症に対する安全対策への最大限の配慮をすることを目的に、第13話以降の本放送(当初予定:4月26日)を延期し、「おさらいセレクション」と題して通常の放送枠(テレ朝系24局:日曜日午前8時30分、BSS山陰放送:土曜日午前11時15分(遅れネット))で再放送を実施しています。 ↩
英語表記については、英語版Wikipediaとrubicureのデータを参照しています。 ↩
英語表記については、英語版Wikipediaとrubicureのデータを参照しています。 ↩
文字コードの関係からか、「ヒーリングっど♥プリキュア」の「♥」が「?」で出力されています。以下同様。 ↩
出力に「キュアエコー」が出てきてしまっていますが、これは movie.yml も読み込んでいるためです。プリキュアリストを作成する際に、どのテレビシリーズにも紐付いていないため最終的な出力には登場しません。 ↩
- 投稿日:2020-05-23T13:03:52+09:00
【.NET】進捗ダイアログ画面で動的DLL(差し替え可能)の進捗状況を表示する(.NET Core版)
はじめに
.NET Core 3.0以降から、Windows Formがプレビュー版で対応しましたので、下記2つを.NET Core版に移植しました。
① 【.NET】進捗ダイアログ画面で動的DLLの進捗状況を表示する
② 【.NET】進捗ダイアログ画面で動的DLL(差し替え可能)の進捗状況を表示する①については、変更箇所が少なかったため(Assembly.Load→AssemblyLoadContextに変更)、記事に追加する形で修正しました。
②については、変更箇所がそれなりにあったので今回記事にしました。.NET Coreにてメイン画面(EXE)を閉じないでもクラスライブラリー(動的DLL)を差し替え可能なことを実現します。
画像は前回の記事の使いまわしです。.NET Framework版と.NET Core版で実現することは変わらないため、ただ日付だけ古いですがご了承ください。
環境
- Windows 10 Home
- Visual Studo 16.7.0 Preview 1.0
- .NET Core 3.1
AppDimainから変更
.NET Framework版では、新規のAppDimainという単一プロセス内で分離された複数の実行領域(domain)を使用して実現させていました。
.NET Core ではAppDimainが1プロセスで1つしかサポートされていないため、AppDimainが使えません。
その代わりAssemblyLoadContextという新しいクラスが用意されているので、これを使用して実現します。
System.Runtime.Loader.AssemblyLoadContext についてAssemblyLoadContextクラスを使用しても、考え方としてはAppDimainの時と変わりません。
進捗ダイアログ画面から取込バッチ処理のクラスライブラリー(DLL)を動的にロードし、処理終了後にアンロードした上で取込バッチ処理(動的DLL)を差し替えようとすると下記のエラーが発生します。
これは、.NETのメタデータ(≒ 型情報)などのアセンブリが進捗ダイアログ画面側に残った状態になってしまうからです。対応方法
AppDimainの時と同様に進捗ダイアログ画面をクラスライブラリー(DLL)に分離し、メイン画面からAssemblyLoadContextインスタンスを作成して進捗ダイアログ画面(DLL)をロードして実行します。
また、取込バッチ処理(動的DLL)も同じAssemblyLoadContextインスタンスにロードされて実行されます。
進捗ダイアログ画面を閉じると、AssemblyLoadContextインスタンスがアンロードされます。ソースコード
進捗ダイアログ画面
バックグラウンド処理にはBackgroundWorkerコンポーネントを使用しています。
別のスレッドからForm上のコントロールにアクセスしたことにより「有効ではないスレッド間の操作」の例外エラーとなったため、コントロールに対してはInvoke((Action)delegate (){}) で囲んでいます。前回との違いとして、プロパティにAssemblyLoadContext用のコンテキストを追加しています。
frmDoWorkusing System; using System.ComponentModel; using System.Windows.Forms; using System.Reflection; using System.IO; namespace DoWork { public partial class frmDoWork : Form { // 取込処理名 public string ExecuteName { get; set; } // 取込処理の引数 public string Arguments { get; set; } // コンテキスト public AssemblyLoadContext Context { get; set; } private Type _myType = null; private Object _instance = null; // コンストラクタ public frmDoWork() { InitializeComponent(); } // 画面初回表示時 private void frmDoWork_Shown(object sender, EventArgs e) { // アセンブリ名を使ってクラス ライブラリーを動的に読み込み string baseName = Path.GetFileNameWithoutExtension(ExecuteName); var myDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); var assemblyPath = Path.Combine(myDirectory, ExecuteName); Assembly assembly = Context.LoadFromAssemblyPath(assemblyPath); _myType = assembly.GetType(baseName + ".Program"); _instance = Activator.CreateInstance(_myType); // アセンブリ内のクラスの Update イベントの EventInfo を取得 EventInfo eventInfo = _myType.GetEvent("Update"); var methodInfo = this.GetType().GetMethod("OnUpdate"); Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, methodInfo); // EventInfo に対してイベント ハンドラーを追加 eventInfo.AddEventHandler(_instance, handler); // 閉じるボタンを無効にする btnClose.Enabled = false; // ProgressChangedイベントが発生するようにする bgWorker.WorkerReportsProgress = true; // 処理を開始する bgWorker.RunWorkerAsync(); } // 画面を閉じる private void btnClose_Click(object sender, EventArgs e) { Close(); } // 取込処理 private void bgWorker_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker bgWorker = (BackgroundWorker)sender; // 処理を開始する int result = (int)_myType.InvokeMember("Main", BindingFlags.InvokeMethod, null, _instance, new object[] { Arguments.Split(',') }); // 結果を設定する e.Result = result; } // 途中経過イベント処理 private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { // ProgressBarの値を変更する Invoke((Action)delegate () { prbDowork.Value = e.ProgressPercentage; // タイトルのテキストを変更する lblTitle.Text = (e.ProgressPercentage).ToString() + " %"; }); } // 取込処理が終わったときに呼び出される private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { Invoke((Action)delegate () { if (e.Error != null) { // エラーが発生したとき lblTitle.Text = "エラー:" + e.Error.Message; } else { // ProgressBarの結果を取得する int result = (int)e.Result; if (result == -1) { // エラーで中断したとき lblTitle.Text = "処理を中断しました。"; } else { // 正常に終了したとき prbDowork.Value = prbDowork.Maximum; lblTitle.Text = "完了しました。"; } } // 閉じるボタンを有効に戻す btnClose.Enabled = true; }); } // 進捗値の更新 public void OnUpdate(object sender, ProgressChangedEventArgs e) { // ProgressChangedイベントハンドラを呼び出し bgWorker.ReportProgress(e.ProgressPercentage); } // 実行処理 public void Execute(string executeName, string args) { this.ExecuteName = executeName; this.Arguments = args; this.ShowDialog(); } } }メイン画面
実行ボタンで進捗ダイアログ画面を表示する。
.NET Core でアセンブリのアンローダビリティを使用およびデバッグする方法
アセンブリをロードするときに、そのファイルが見つからない場合、FileNotFoundException例外(System.IO名前空間)が発生してしまう。
この問題を避けるために、JITコンパイラが動くのを、アセンブリがダウンロードされた後まで遅延させなければならない。そのためにJIT 最適化によるインライン展開を抑止する方法として「[MethodImpl(MethodImplOptions.NoInlining)]」をメソッドの上に付加している。frmMain.csprivate void btnExecute_Click(object sender, EventArgs e) { // メッセージボックスを表示する DialogResult result = MessageBox.Show("実行します。よろしいですか ? ", "処理実行", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2); if (result == DialogResult.Yes) { // 処理実行 string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string assemblyPath = Path.Combine(path, "DoWork.dll"); ExecuteAndUnload(assemblyPath, out WeakReference alcWeakRef); // アンロードされるまで待つ int counter = 0; for (counter = 0; alcWeakRef.IsAlive && (counter < 10); counter++) { GC.Collect(); GC.WaitForPendingFinalizers(); } if (counter < 10) { System.Diagnostics.Debug.WriteLine("アンロード成功"); } else { System.Diagnostics.Debug.WriteLine("アンロード失敗"); } } [MethodImpl(MethodImplOptions.NoInlining)] static void ExecuteAndUnload(string assemblyPath, out WeakReference alcWeakRef) { // アセンブリをロードするAssemblyLoadContextを作成 var alc = new MyAssemblyLoadContext(); // アセンブリをロード Assembly a = alc.LoadFromAssemblyPath(assemblyPath); // 外からアンロードを検知するために弱参照を設定 alcWeakRef = new WeakReference(alc, trackResurrection: true); // リフレクションで関数コール var type = a.GetType("DoWork.frmDoWork"); var instance = Activator.CreateInstance(type); var method = type.GetMethod("Execute"); method.Invoke(instance, new object[] { "SUB00001", "", alc}); // アンロード実施 alc.Unload(); } }AssemblyLoadContext のカスタム
frmMain.cs内に追加しています。
単純にアセンブリをロードしたいだけなら、AssemblyLoadContext.Default を使用すればいいのだが、下記の理由で Default を使うとアンロードができなくなるためカスタムする必要がある。
AssemblyLoadContextのUnloadメソッドはコールされた時点では開始されるだけで完了はしません。、
アンロードが完了されるのは、以下の条件を満たしたときとなります。
- コール スタックに、AssemblyLoadContext にロードされたアセンブリ内のメソッドを含むスレッドがなくなった。
- AssemblyLoadContext にロードされたアセンブリ内の型、それらの型のインスタンス、およびアセンブリ自体が参照されなくなった。
参照:.NET Core でアセンブリのアンローダビリティを使用およびデバッグする方法
AssemblyLoadContextのコンストラクタ引数にある'isCollectible'は'true'にする必要があります。これはパフォーマンスの観点からデフォルトでは'false'になっています。
また、'false'のまま'Unload'をコールすると例外が発生してしまうためです。MyAssemblyLoadContextpublic class MyAssemblyLoadContext : AssemblyLoadContext { public MyAssemblyLoadContext() : base(isCollectible: true) { } }取込バッチ処理
クラスライブラリー(DLL)で、進捗状況のイベントを発生させ進捗値を渡します。
処理内容はサンプルなので適当です。Program.csusing System; namespace SUB00001 { public class Program : MarshalByRefObject { // 更新されると起きるイベント public event ProgressChangedEventHandler Update; public int Main(string[] args) { if (!SubProccess10()) return -1; if (!SubProccess30()) return -1; if (!SubProccess50()) return -1; if (!SubProccess70()) return -1; if (!SubProccess90()) return -1; // 完了 SetProgress(100); return 0; } // 処理10% private bool SubProccess10() { System.Threading.Thread.Sleep(200); SetProgress(10); return true; } // 処理30% private bool SubProccess30() { System.Threading.Thread.Sleep(200); SetProgress(30); return true; } // 処理50% private bool SubProccess50() { System.Threading.Thread.Sleep(200); SetProgress(50); return true; } // 処理70% private bool SubProccess70() { System.Threading.Thread.Sleep(200); SetProgress(70); return true; } // 処理90% private bool SubProccess90() { System.Threading.Thread.Sleep(200); SetProgress(90); return true; } // 進捗状況を標準出力に出力する private void SetProgress(int value) { // 更新イベントを起こす ProgressChangedEventArgs e = new ProgressChangedEventArgs(value, null); Update?.Invoke(this, e); } } }DLL差し替え結果
- デスクトップに取込バッチ処理クラスライブラリー(DLL)の成功版をコピーする。
- メイン画面を起動
- 取込バッチ処理クラスライブラリー(DLL)の失敗版を実行する(進捗画面は閉じる)。
- メイン画面を起動したまま、DLLを成功版に差し替える。
- 取込バッチ処理クラスライブラリー(DLL)の成功版を実行する。
失敗
SubProccess70()の戻り値を true -> false に変更。
DLL差し替え
成功
最後に
.NET Core版の移植に時間がかかるかと思ったんですが、案外あっさりとできました。
とはいっても先人たちが、記事を書いてくれたお陰様です。感謝!今の時代にあんまり用途がない気がしますが、何かの役には立つことがあるかと思います。
- 投稿日:2020-05-23T11:09:57+09:00
Blazorコンポーネント向けのテストフレームワークのbUnitを使ってみる
概要
つい最近正式版がリリースされた、Blazor WebAssemblyですが、
そのBlazorのコンポーネント向けのテストフレームワークのbUnitを試した際のメモです。前提
- Windows 10 (64bit)
- Visual Studio 2019 16.6
- .NET Core SDK (3.1.300)
※今回はBlazor WebAssemblyを使用しています。
(Server-sideは未検証ですが恐らく動くと思います。)手順
Blazorプロジェクトの作成
Visual Studioもしくはdotnetコマンドから、Blazor WebAssemblyの新規プロジェクトを作成してください。
bUnit用のテンプレートをインストール
下記のコマンドでテンプレートをインストールします。
バージョンは2020/5/22時点で1.0.0-beta-7ですが、下記を参照して適宜最新バージョン等をインストールしてください。
https://www.nuget.org/packages/bunit.template/dotnet new --install bunit.template::1.0.0-beta-7なお、上記のテンプレートを使用せずに、既存のNUnitやMSTest用のプロジェクトにも導入可能なようです。
詳細は公式HPの下記を参照ください。
https://bunit.egilhansen.com/docs/create-test-project.html?tabs=xunit#create-a-test-project-with-bunit-templateプロジェクトの生成
現時点ではVisual StudioからGUIでプロジェクトテンプレートとして選択ができないようなので、
最初に作成したBlazorのプロジェクトにカレントディレクトリを移動して、下記のコマンドを実行します。dotnet new bunit -o <テストPJ名>テストPJ名は任意ですが、よく見かけるUnitTestの例ですと、
<テスト対象のPJ名.Test>
とする事が多いかと思います。プロジェクトの参照
BlazorのプロジェクトをVisual Studioから立ち上げて、
ソリューションエクスプローラから 追加 → 既存のプロジェクト で作成したテストPJを追加します。
追加すれば、下記のようにBlazorのPJとUnitTest用のPJが表示されているはずです。追加したテストPJ側の依存関係にプロジェクトの参照追加で、テスト対象となるBlazorのプロジェクトを追加します。
不要ファイルの削除
テンプレートで作成したテストPJには、単体でも動くようにテスト用のコンポーネントが同じPJ内に含まれています。(Counter.razor)
同じコンポーネントがBlazorアプリ側の初期状態にも含まれていますので、そちらを参照するように変更します。
※下記の操作はすべて、テストPJ側の操作です。
- Counter.razorの削除
- _Imports.razorに参照の追加
@using BlazorアプリのPJ名.Pages;
- CounterCSharpTest.csに参照の追加
@using BlazorアプリのPJ名.Pages;テストの実行
まずは、この状態でテストが動くか確認してみましょう。
ツールバーから テスト → 全てのテストを実行 を選択します。
下記のようにテストが実行されてパスすれば成功です。以上で、セットアップは完了です。
次からはテストの内容を確認していきます。テストの内容確認
bUnitのテンプレートではCounterコンポーネントのテストがサンプルとして実装されています。
Counterコンポーネントは下記のような、ボタンを押下すると数値がインクリメントされる機能が実装されているコンポーネントです。C#コードベースのテスト
早速ですが、テストコードを見ていきます。
CounterCSharpTest.cspublic class CounterCSharpTests : TestContext { [Fact] public void CounterStartsAtZero() { // Arrange var cut = RenderComponent<Counter>(); // Assert that content of the paragraph shows counter at zero cut.Find("p").MarkupMatches("<p>Current count: 0</p>"); } [Fact] public void ClickingButtonIncrementsCounter() { // Arrange var cut = RenderComponent<Counter>(); // Act - click button to increment counter cut.Find("button").Click(); // Assert that the counter was incremented cut.Find("p").MarkupMatches("<p>Current count: 1</p>"); } }見ればすぐわかる内容ですが、RenderComponentメソッドで対象のコンポーネントを描画し、
その後、UIを操作や表示内容の確認を実施しています。
サンプルでは下記の2パターンのテストが実装されています。
- 初期状態は、pタグに"Current count: 0"といった要素が記述されていること
- ボタンクリック後に、pタグに"Current count: 1"といった要素が記述されていること
2.にテストケースを追加してみます。
再度ボタンを押下した場合に、カウンタの数が2になっていることを確認することとします。[Fact] public void ClickingButtonIncrementsCounter() { // Arrange var cut = RenderComponent<Counter>(); // Act - click button to increment counter cut.Find("button").Click(); // Assert that the counter was incremented cut.Find("p").MarkupMatches("<p>Current count: 1</p>"); // ここからテストケース追加 // Act - click button to increment counter again cut.Find("button").Click(); // Assert that the counter was incremented cut.Find("p").MarkupMatches("<p>Current count: 2</p>"); }その後、テストを再度実行してパスする事を確認しましょう。
razorベースのテスト
先ほどはC#コードベースのテストを確認しましたが、次はrazorコードベースのテスト手法を確認します。
(CounterRazorTest.razor)
このファイルには2種類のテストが実装されいるので順に説明します。スナップショットテスト
SnapshotTestタグで囲まれた要素が1つのテストケースになります。
TestInputタグ内に、テスト対象のコンポーネントを定義し、ExpectedOutputタグ内に実際に出力されるhtmlタグを記載してアサーションするといった形になります。
Setup,SetupAsyncでラムダ呼び出し可能なようなので、でDIのモックも可能なようですが、ボタン操作などはできなさそうに見えるのでどちらかというと、コンポーネントに渡すパラメータを色々と変えた場合のテスト用途として使えそうです。CounterRazorTest.razor<SnapshotTest Description="Counter starts at zero"> <TestInput> <Counter /> </TestInput> <ExpectedOutput> <h1>Counter</h1> <p>Current count: 0</p> <button class="btn btn-primary">Click me</button> </ExpectedOutput> </SnapshotTest> // 略Razorコンポーネントテスト
こちらは最初に紹介したC#コードベースのテストとスナップショットテストを合わせたようなテストです。
Fixtureタグが1つのテストケースとなり、ComponentUnderTestにテスト対象のタグを定義し、
Fragmentに期待されるHTMLの部分要素を定義します。C#コードとしてはTest属性でメソッドを紐づけることでrazorタグ内の要素を操作することができます。
やっていること自体は,C#コードのサンプルと同様にボタンを1度クリック後にpタグの文言がインクリメントされているかをチェックしているだけです。<Fixture Description="Clicking button increments counter" Test="Test"> <ComponentUnderTest> <Counter></Counter> </ComponentUnderTest> <Fragment> <p>Current count: 1</p> </Fragment> </Fixture> @code { public void Test(Fixture fixture) { // Arrange var cut = fixture.GetComponentUnderTest<Counter>(); // Act - click button to increment counter cut.Find("button").Click(); // Assert that the counter was incremented var expected = fixture.GetFragment(); cut.Find("p").MarkupMatches(expected); } }Fragmentタグにはidを付与することで何個も定義してケースごとに使い分けることができます。
下記は2つのFragmentを定義して、ボタンを1度押したケースとその後、再度押したケースで結果がインクリメントされていることを検証しています。<Fixture Description="Clicking button increments counter" Test="Test"> <ComponentUnderTest> <Counter></Counter> </ComponentUnderTest> <Fragment id="first"> <p>Current count: 1</p> </Fragment> <Fragment id="second"> <p>Current count: 2</p> </Fragment> </Fixture> @code { public void Test(Fixture fixture) { // Arrange var cut = fixture.GetComponentUnderTest<Counter>(); // Act - click button to increment counter cut.Find("button").Click(); // Assert that the counter was incremented var expected = fixture.GetFragment("first"); cut.Find("p").MarkupMatches(expected); // ここから追加(再度ボタンを押下) // Act - click button to increment counter again cut.Find("button").Click(); // Assert that the counter was incremented expected = fixture.GetFragment("second"); cut.Find("p").MarkupMatches(expected); } }ちなみに敢えてテストケースを失敗させた場合には、下図のように表示されます。
実際の値と期待値のタグが表示されるのでわかりやすいかと思います。コードの追加及びテストケースの追加
これまではCounterコンポーネントのテストケースを確認しましたが、次はより実践的なAPI等によるデータ取得が絡むコンポーネントのケースを考えてみます。
初期状態で作成されているFetch dataの天気読み込み部分を書き換えたうえでテストケースを追加してみます。
処理のサービス化
元のコードはコンポーネント内でHTTPクライアントを使ってサーバ側に設置されたjosnを読み込む形となっていますが、
下記のようなインターフェス及びダミーデータを返す形にサービス化します。WeatherService.cspublic interface IWeatherService { Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync(); } public class WeatherService : IWeatherService { public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync() { // ダミーデータを作成して返す await Task.Delay(1500); var forecasts = new List<WeatherForecast>(); forecasts.Add(new WeatherForecast() { Date = new DateTime(2020, 5, 1), TemperatureC = 20, Summary = "Sunny" }); forecasts.Add(new WeatherForecast() { Date = new DateTime(2020, 5, 2), TemperatureC = 10, Summary = "Rainy" }); forecasts.Add(new WeatherForecast() { Date = new DateTime(2020, 5, 3), TemperatureC = 14, Summary = "Cloudy" }); return forecasts; } }作成したサービスをDIできるように登録します。
Program.cspublic class Program { public static async Task Main(string[] args) { // 略 builder.Services.AddSingleton<IWeatherService, WeatherService>(); // 略 } }コンポーネント内でサービスを呼び出してデータを取得するように変更します。
FeatchData.razor@page "/fetchdata" // サービスをInject @inject IWeatherService WeatherService <h1>Weather forecast</h1> // 略 @code { private WeatherForecast[] forecasts; protected override async Task OnInitializedAsync() { forecasts = (await WeatherService.GetWeatherForecastAsync()).ToArray(); } }以上で実装は終了です。
実際に動かしてデータ取得ができていることを確認しましょう。テストの作成
次に実装した機能に対するテストを作成していきます。
モックの作成
テスト時には実際にAPI等にアクセスせずに、任意の応答が取得できるように下記のようなモッククラスを作成します。
これを使用することで、各テストケースで任意のテストデータを設定ができます。internal class MockWeatherService : IWeatherService { public TaskCompletionSource<IEnumerable<WeatherForecast>> Task { get; } = new TaskCompletionSource<IEnumerable<WeatherForecast>>(); public Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync() { return Task.Task; } }モックを使用して、取得データが0件の場合と3件の場合のケースを実装します。
各テストケースでモックにデータを準備して設定しています。データの取得処理は非同期になるので、取得が完了するまで待機しないと、結果をアサーションできません。
そのための仕組みとして、WaitForStateメソッドが用意されています。
下記の例では、データの取得が完了してtableタグが描画されてるまで待っています。public class FeatchDataCSharpTest : TestContext { [Fact] public void ZeroDataCase() { // set empty record mock var forecasts = new List<WeatherForecast>(); var mockService = new MockWeatherService(); mockService.Task.SetResult(forecasts); Services.AddSingleton<IWeatherService>(mockService); var fetchData = RenderComponent<FetchData>(); // wait until table rendered fetchData.WaitForState(() => fetchData.FindAll(".table").Count > 0); var expectedHtml = @"<table class=""table""> <thead> <tr> <th>Date</th> <th>Temp. (C) </th> <th>Temp. (F) </th> <th>Summary </th> </tr> </thead> <tbody> </tbody>"; fetchData.Find("table").MarkupMatches(expectedHtml); } [Fact] public void ExistsDataCase() { // set dummy record mock var forecasts = new List<WeatherForecast>(); forecasts.Add(new WeatherForecast() { Date = new DateTime(2020, 5, 1), TemperatureC = 20, Summary = "Sunny" }); forecasts.Add(new WeatherForecast() { Date = new DateTime(2020, 5, 2), TemperatureC = 10, Summary = "Rainy" }); forecasts.Add(new WeatherForecast() { Date = new DateTime(2020, 5, 3), TemperatureC = 14, Summary = "Cloudy" }); var mockService = new MockWeatherService(); mockService.Task.SetResult(forecasts); Services.AddSingleton<IWeatherService>(mockService); var fetchData = RenderComponent<FetchData>(); // wait until table rendered fetchData.WaitForState(() => fetchData.FindAll(".table").Count > 0); var expectedHtml = @"<table class=""table""> <thead> <tr> <th>Date</th> <th>Temp. (C) </th> <th>Temp. (F) </th> <th>Summary </th> </tr> </thead> <tbody> <tr> <td>2020/05/01</td> <td>20</td> <td>67</td> <td>Sunny</td> </tr> <tr> <td>2020/05/02</td> <td>10</td> <td>49</td> <td>Rainy</td> </tr> <tr> <td>2020/05/03</td> <td>14</td> <td>57</td> <td>Cloudy</td> </tr> </tbody>"; fetchData.Find("table").MarkupMatches(expectedHtml); } }まとめ
簡単なサンプルパターンだけにはなりますが、Blazorのコンポーネント向けテストフレームワークのbUnitの紹介をしました。
紹介したもの以外にもJS側のモック化や様々なアサーション機能など、色々と機能が提供されいますので、
興味のある方は公式ページを参照してみください。bUnit自体はまだbetaということで、破壊的な変更が加わる可能性等がありますが、従来のC#ロジックとは別にGUIに近いrazorコンポーネントのテスト自動化が可能なことが確認できました。
HTMLの出力内容が隠蔽されるようなUIライブラリを使う場合には使いづらいですが、自分で制御できる場合には、有用かもしれません。
- 投稿日:2020-05-23T01:18:49+09:00