C#で半角カタカナを全角カタカナに変換する

# はじめに

半角カタカナは何かと使い勝手が悪い文字ですので、全角カタカナに置き換えたいと思います。
但し、他の半角文字（数字やアルファベット）は変換したくありません。半角なら半角、全角なら全角のままにしたいので、StrConvを使いませんでした。
いろいろなやり方があると思いますが、文字コードで範囲を指定するなどのテクニカルな方法は避け、べたな方法で対処しました。

# 定数宣言

変換元の**kanakeys**と変換後の**kanavalues**をstaticに用意します。
濁音、半濁音の扱いが面倒です。
また、存在チェックのためだけに、**static List<char> kanakeyList**を用意しました。
なお、**ヰヱ**の半角文字は無いみたいです。

```c#
private static readonly string[] kanakeys = { "ｳﾞ", "ｶﾞ", "ｷﾞ", "ｸﾞ", "ｹﾞ", "ｺﾞ", "ｻﾞ", "ｼﾞ", "ｽﾞ", "ｾﾞ", "ｿﾞ", "ﾀﾞ", "ﾁﾞ", "ﾂﾞ", "ﾃﾞ", "ﾄﾞ", "ﾊﾞ", "ﾋﾞ", "ﾌﾞ", "ﾍﾞ", "ﾎﾞ", "ﾊﾟ", "ﾋﾟ", "ﾌﾟ", "ﾍﾟ", "ﾎﾟ", "ｱ", "ｲ", "ｳ", "ｴ", "ｵ", "ｶ", "ｷ", "ｸ", "ｹ", "ｺ", "ｻ", "ｼ", "ｽ", "ｾ", "ｿ", "ﾀ", "ﾁ", "ﾂ", "ﾃ", "ﾄ", "ﾅ", "ﾆ", "ﾇ", "ﾈ", "ﾉ", "ﾊ", "ﾋ", "ﾌ", "ﾍ", "ﾎ", "ﾏ", "ﾐ", "ﾑ", "ﾒ", "ﾓ", "ﾔ", "ﾕ", "ﾖ", "ﾗ", "ﾘ", "ﾙ", "ﾚ", "ﾛ", "ﾜ", "ｦ", "ﾝ", "ｬ", "ｭ", "ｮ", "ｧ", "ｨ", "ｪ", "ｫ", "ﾞ", "ﾟ", "､", "｡", "ｰ" };
private static readonly string[] kanavalues = { "ヴ", "ガ", "ギ", "グ", "ゲ", "ゴ", "ザ", "ジ", "ズ", "ゼ", "ゾ", "ダ", "ヂ", "ヅ", "デ", "ド", "バ", "ビ", "ブ", "ベ", "ボ", "パ", "ピ", "プ", "ペ", "ポ", "ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ", "タ", "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "ノ", "ハ", "ヒ", "フ", "ヘ", "ホ", "マ", "ミ", "ム", "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ", "ヲ", "ン", "ャ", "ュ", "ョ", "ァ", "ィ", "ェ", "ォ", "゛", "゜", "、", "。", "ー" };
private static readonly List<char> kanakeyList = new List<char> { 'ｱ', 'ｲ', 'ｳ', 'ｴ', 'ｵ', 'ｶ', 'ｷ', 'ｸ', 'ｹ', 'ｺ', 'ｻ', 'ｼ', 'ｽ', 'ｾ', 'ｿ', 'ﾀ', 'ﾁ', 'ﾂ', 'ﾃ', 'ﾄ', 'ﾅ', 'ﾆ', 'ﾇ', 'ﾈ', 'ﾉ', 'ﾊ', 'ﾋ', 'ﾌ', 'ﾍ', 'ﾎ', 'ﾏ', 'ﾐ', 'ﾑ', 'ﾒ', 'ﾓ', 'ﾔ', 'ﾕ', 'ﾖ', 'ﾗ', 'ﾘ', 'ﾙ', 'ﾚ', 'ﾛ', 'ﾜ', 'ｦ', 'ﾝ', 'ｬ', 'ｭ', 'ｮ', 'ｧ', 'ｨ', 'ｪ', 'ｫ', 'ﾞ', 'ﾟ', '､', '｡', 'ｰ' };
```

# メソッド

```c#
        public static string KanaToFull(string target)
        {
            Debug.WriteLine("in: [" + target + "]");
            DateTime dtStart = DateTime.Now;//開始

            if(target.Where(x => kanakeyList.Contains(x)).Count() > 0)//対象かどうかチェック
            {
                for (int idx = 0; idx < kanavalues.Length; idx++)
                {
                    target = target.Replace(kanakeys[idx], kanavalues[idx]);
                }
            }

            DateTime dtEnd = DateTime.Now;//終了
            TimeSpan timeSpan = dtEnd - dtStart;//速度測定
            Debug.WriteLine("out:[" + target + "]");
            Debug.WriteLine(timeSpan.TotalMilliseconds.ToString());
            return target;
        }
```

Debug.WriteLine、DateTime、TimeSpan　は速度測定用なので、実装時は不要です。

そうすると本体は、以下だけになります。

```c#
            if(target.Where(x => kanakeyList.Contains(x)).Count() > 0)//対象かどうかチェック
            {
                for (int idx = 0; idx < kanavalues.Length; idx++)
                {
                    target = target.Replace(kanakeys[idx], kanavalues[idx]);
                }
            }
```

少しでも速度を上げるために、**kanakeyList**に含まれる文字が**target**に存在するかどうかのチェックを行っています。**必ず半角カナがあるのでチェックの必要が無い**、**targetが短くて意識する必要が無い**、などであれば、却って速度を低下させるので、取り除いても良いかと思います。

後は、**kanakeys**の文字列を**kanavalues**の文字列にReplaceしているだけです。
**kanavalues.Length**回Replaceを実行するので速度が気になりますね。

# 速度測定

試みに、吾輩は猫であるの冒頭2600文字程度を、漢字を除いて半角カナに変換して実行したところ、私の環境では

| 1回目 | 2回目 | 3回目 | 4回目 | 5回目 |
| :--: | :--: | :--: | :--: | :--: |
| 約5ms | 約0.3ms | 約0.3ms | 約0.3ms | 約0.2ms |
| 約5ms | 約0.4ms | 約0.3ms | 約0.3ms | 約0.2ms |

でした。一旦終了させて再実験しても1回目は少し遅いので、文書が変数上に残っているのかも知れないと思い、舞姫の冒頭約2000文字程度を同じように半角化して代わりばんこに実行したところ、

| 1回目 | 2回目 | 3回目 | 4回目 |
| :--: | :--: | :--: | :--: |
| 約5ms/猫 | 約0.3ms/舞 | 約0.3ms/猫 | 約0.1ms/舞 |
| 約3ms/舞 | 約0.4ms/猫 | 約0.2ms/舞 | 約0.2ms/猫 |

でした。ひょっとしたら、staticな配列であっても呼び出されるまでメモリ上にはおらず、初回ロードのコストなのかも知れません。検証するのも面倒なのでやりませんけど。

例えば100回このメソッドを呼ぶことがあったとして、初回ロード時のコストが5ms、2回目以降のコストが0.3msとすれば、5+0.3*99≒35ms、0.035秒なので、特に速度を意識する必要はなさそうに思います。

# 補足

指摘頂いているStringBuilderの件、てっきりAdd系しか速度向上に寄与しないと思っていて、そもそもReplace出来るとは思っていませんでした。ありがとうございます。
ListよりHashSetの方が早いですね。ちょっと雑でした。CountとAnyもそうですね。
switchの方が確かに速いのですが、ソースコードの見やすさとの兼ね合いで、Replaceを回してみました。
ご指摘ありがとうございます。