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

構造体,クラスの参照渡しの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++をやると混乱します。

某漫画のように
「最初の入り口がオブジェクト指向なんだよね最近の子って」
「そうそう そんなんじゃ使いものにならねえっての」
となります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

その構造体、クラスの仮引数は値か参照か[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++をやると混乱します。

某漫画のように
「最初の入り口がオブジェクト指向なんだよね最近の子って」
「そうそう そんなんじゃ使いものにならねえっての」
となります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

その仮引数(構造体,クラス)は値か参照か[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++をやると混乱します。

某漫画のように
「最初の入り口がオブジェクト指向なんだよね最近の子って」
「そうそう そんなんじゃ使いものにならねえっての」
となります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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」が多数を占めてしまうため)。

使用するもの

対象とするプリキュア

原則的に、テレビシリーズで登場したプリキュアに限定します。
なので、映画のみに登場している「キュアエコー」は対象外となります。

プログラムを書く

プログラム置き場

プログラムはここに置いておきます。
Hokkaidosm/precure_initial

シリーズデータを取得する

https://github.com/sue445/rubicure/tree/master/config/series.yml からシリーズデータを取得します。
シリーズデータには、「シリーズ名」と「登場するプリキュアのキー」を含みます。

長いので折りたたむルン
Program.cs
using 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.cs
using 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.cs
using 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


  1. 新型コロナウイルス感染症に対する安全対策への最大限の配慮をすることを目的に、第13話以降の本放送(当初予定:4月26日)を延期し、「おさらいセレクション」と題して通常の放送枠(テレ朝系24局:日曜日午前8時30分、BSS山陰放送:土曜日午前11時15分(遅れネット))で再放送を実施しています。 

  2. 英語表記については、英語版Wikipediarubicureのデータを参照しています。 

  3. 英語表記については、英語版Wikipediarubicureのデータを参照しています。 

  4. 文字コードの関係からか、「ヒーリングっど♥プリキュア」の「♥」が「?」で出力されています。以下同様。 

  5. 出力に「キュアエコー」が出てきてしまっていますが、これは movie.yml も読み込んでいるためです。プリキュアリストを作成する際に、どのテレビシリーズにも紐付いていないため最終的な出力には登場しません。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【.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)を差し替えようとすると下記のエラーが発生します。
使用中のフォルダー.png
これは、.NETのメタデータ(≒ 型情報)などのアセンブリが進捗ダイアログ画面側に残った状態になってしまうからです。

対応方法

AppDimainの時と同様に進捗ダイアログ画面をクラスライブラリー(DLL)に分離し、メイン画面からAssemblyLoadContextインスタンスを作成して進捗ダイアログ画面(DLL)をロードして実行します。
また、取込バッチ処理(動的DLL)も同じAssemblyLoadContextインスタンスにロードされて実行されます。
進捗ダイアログ画面を閉じると、AssemblyLoadContextインスタンスがアンロードされます。

ソースコード

進捗ダイアログ画面

frmDoWork.png

バックグラウンド処理にはBackgroundWorkerコンポーネントを使用しています。
別のスレッドからForm上のコントロールにアクセスしたことにより「有効ではないスレッド間の操作」の例外エラーとなったため、コントロールに対してはInvoke((Action)delegate (){}) で囲んでいます。

前回との違いとして、プロパティにAssemblyLoadContext用のコンテキストを追加しています。

frmDoWork
using 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.cs
private 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でアセンブリをアンロードする

参照:.NET Core でアセンブリのアンローダビリティを使用およびデバッグする方法

AssemblyLoadContextのコンストラクタ引数にある'isCollectible'は'true'にする必要があります。これはパフォーマンスの観点からデフォルトでは'false'になっています。
また、'false'のまま'Unload'をコールすると例外が発生してしまうためです。

MyAssemblyLoadContext
public class MyAssemblyLoadContext : AssemblyLoadContext
{
    public MyAssemblyLoadContext() : base(isCollectible: true)
    {
    }
}

取込バッチ処理

クラスライブラリー(DLL)で、進捗状況のイベントを発生させ進捗値を渡します。
処理内容はサンプルなので適当です。

Program.cs
using 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差し替え結果

  1. デスクトップに取込バッチ処理クラスライブラリー(DLL)の成功版をコピーする。
  2. メイン画面を起動
  3. 取込バッチ処理クラスライブラリー(DLL)の失敗版を実行する(進捗画面は閉じる)。
  4. メイン画面を起動したまま、DLLを成功版に差し替える。
  5. 取込バッチ処理クラスライブラリー(DLL)の成功版を実行する。

失敗

SubProccess70()の戻り値を true -> false に変更。
import_progress2.gif

DLL差し替え

コピー完了.png

成功

import_progress.gif

最後に

.NET Core版の移植に時間がかかるかと思ったんですが、案外あっさりとできました。
とはいっても先人たちが、記事を書いてくれたお陰様です。感謝!

今の時代にあんまり用途がない気がしますが、何かの役には立つことがあるかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Blazorコンポーネント向けのテストフレームワークのbUnitを使ってみる

概要

つい最近正式版がリリースされた、Blazor WebAssemblyですが、
そのBlazorのコンポーネント向けのテストフレームワークのbUnitを試した際のメモです。

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が表示されているはずです。

tpj.PNG

追加したテストPJ側の依存関係にプロジェクトの参照追加で、テスト対象となるBlazorのプロジェクトを追加します。

tp2j.PNG

不要ファイルの削除

テンプレートで作成したテストPJには、単体でも動くようにテスト用のコンポーネントが同じPJ内に含まれています。(Counter.razor)
同じコンポーネントがBlazorアプリ側の初期状態にも含まれていますので、そちらを参照するように変更します。
※下記の操作はすべて、テストPJ側の操作です。

  • Counter.razorの削除
  • _Imports.razorに参照の追加
  @using BlazorアプリのPJ名.Pages;
  • CounterCSharpTest.csに参照の追加
  @using BlazorアプリのPJ.Pages;

テストの実行

まずは、この状態でテストが動くか確認してみましょう。
ツールバーから テスト → 全てのテストを実行 を選択します。
下記のようにテストが実行されてパスすれば成功です。

test_res.PNG

以上で、セットアップは完了です。
次からはテストの内容を確認していきます。

テストの内容確認

bUnitのテンプレートではCounterコンポーネントのテストがサンプルとして実装されています。
Counterコンポーネントは下記のような、ボタンを押下すると数値がインクリメントされる機能が実装されているコンポーネントです。

co.gif

C#コードベースのテスト

早速ですが、テストコードを見ていきます。

CounterCSharpTest.cs
    public 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パターンのテストが実装されています。

  1. 初期状態は、pタグに"Current count: 0"といった要素が記述されていること
  2. ボタンクリック後に、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);
    }
}

ちなみに敢えてテストケースを失敗させた場合には、下図のように表示されます。
実際の値と期待値のタグが表示されるのでわかりやすいかと思います。

res.PNG

コードの追加及びテストケースの追加

これまではCounterコンポーネントのテストケースを確認しましたが、次はより実践的なAPI等によるデータ取得が絡むコンポーネントのケースを考えてみます。

初期状態で作成されているFetch dataの天気読み込み部分を書き換えたうえでテストケースを追加してみます。

wea.gif

処理のサービス化

元のコードはコンポーネント内でHTTPクライアントを使ってサーバ側に設置されたjosnを読み込む形となっていますが、
下記のようなインターフェス及びダミーデータを返す形にサービス化します。

WeatherService.cs
    public 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.cs
    public 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);
        }
    }

テストを流してパスする事を確認します。
ast2.PNG

まとめ

簡単なサンプルパターンだけにはなりますが、Blazorのコンポーネント向けテストフレームワークのbUnitの紹介をしました。
紹介したもの以外にもJS側のモック化や様々なアサーション機能など、色々と機能が提供されいますので、
興味のある方は公式ページを参照してみください。

bUnit自体はまだbetaということで、破壊的な変更が加わる可能性等がありますが、従来のC#ロジックとは別にGUIに近いrazorコンポーネントのテスト自動化が可能なことが確認できました。
HTMLの出力内容が隠蔽されるようなUIライブラリを使う場合には使いづらいですが、自分で制御できる場合には、有用かもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フィールドとプロパティ その2

背景

これから鬼教官より自分の面倒を見ていただけることになった。
課題を今後出されるので、そこで習ったことを記す。

課題

消費税クラスを作成し、継承。
消費税の変数はstaticにし、消費税込みの値段を出力せよ。

プログラム

前回からの派生版。
消費税クラスを作成・継承し、消費税込みの値段を出力するかというプログラム。
無題.png
余裕をかました結果。。。。

学び

staticを使うときは、クラス名.フィールド名で参照することができる
無題.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

staticの使い方

背景

これから鬼教官より自分の面倒を見ていただけることになった。
課題を今後出されるので、そこで習ったことを記す。

課題

消費税クラスを作成し、継承。
消費税の変数はstaticにし、消費税込みの値段を出力せよ。

プログラム

前回からの派生版。
消費税クラスを作成・継承し、消費税込みの値段を出力するかというプログラム。
無題.png
余裕をかました結果。。。。

学び

staticを使うときは、クラス名.フィールド名で参照することができる
無題.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む