C# Tips | 文字列処理:全角→半角

C# C#
スポンサーリンク

はじめに なぜ「全角→半角」が業務でこんなに大事なのか

日本語の業務システムでは、「見た目は同じなのに、文字コードが違うせいで一致しない」という事故が本当に多いです。
「A」と「A」、「1」と「1」、「@」と「@」――人間の目には同じに見えても、コンピュータ的には別物です。

マスタのコードは半角、ユーザー入力は全角。
CSV は半角、画面入力は全角。
外部システムは全角、こちらの検索キーは半角。

このズレを放置すると、「検索にヒットしない」「重複チェックをすり抜ける」「比較が合わない」といったバグの温床になります。
だからこそ、「全角を半角にそろえる」ユーティリティを最初に用意しておくと、後のコードが一気に楽になります。

ここでは、C# 初心者向けに、全角→半角の基本的な考え方から、実務で使えるユーティリティの作り方まで、例題付きで丁寧に解説していきます。


全角と半角の違いをざっくり理解する

見た目は同じでも「別の文字」

まず押さえておきたいのは、「全角」と「半角」は、単に「幅が違う」だけではなく、Unicode 上では「別の文字」として定義されているということです。

例えば、「A」と「A」は別のコードポイントです。

全角A:U+FF21
半角A:U+0041

同じ「A」に見えても、コンピュータ的には「違う文字列」として扱われます。
そのため、次のような比較は false になります。

string zenkaku = "A";
string hankaku = "A";

Console.WriteLine(zenkaku == hankaku); // false
C#

この差を吸収するために、「全角を半角に変換してから比較する」というステップを挟むのが、実務では定番になります。


String.Normalize を使った「簡易」全角→半角

Unicode 正規化 FormKC / FormKD という武器

.NET には、Unicode の「正規化」を行う string.Normalize というメソッドがあります。
このメソッドに NormalizationForm.FormKCFormKD を指定すると、「互換文字」を標準的な形に変換してくれます。

この「互換文字」には、全角英数字や一部の記号が含まれているため、
「全角英数字を半角に寄せる」という用途にある程度使えます。

実際に試してみる

using System;
using System.Text;

class Program
{
    static void Main()
    {
        string zenkaku = "ABC123@!";
        string normalized = zenkaku.Normalize(NormalizationForm.FormKC);

        Console.WriteLine(zenkaku);     // ABC123@!
        Console.WriteLine(normalized);  // ABC123@!
    }
}
C#

NormalizationForm.FormKC を指定することで、
全角英字・数字・一部記号が半角に変換されているのが分かります。

ここでの重要ポイントは、「Normalize は“全角→半角専用”ではないが、互換文字を標準形に寄せる過程で結果的に半角になるものがある」ということです。


Normalize だけでは足りないところと、その限界

すべての全角が半角になるわけではない

注意しなければいけないのは、「Normalize(FormKC) を呼べば、すべての全角がきれいに半角になるわけではない」という点です。

特に日本語の「全角カナ」「半角カナ」の変換は、Normalize だけでは思った通りにならないことがあります。

例えば、ある文字は半角に寄るが、別の文字はそのまま、
あるいは「半角カナを渡したら逆に全角に戻ってしまう」といった挙動もありえます。

つまり、

英数字や一部記号だけを対象にするなら Normalize でかなり助かる
日本語カナまで完璧にやりたいなら、別途マッピングが必要

というイメージを持っておくとよいです。

「どこまで変換したいか」を最初に決める

実務で設計するときは、次のような方針を決めておくと楽になります。

英数字と記号だけ全角→半角にしたいのか。
カナ(カタカナ)も半角にしたいのか。
記号の一部は全角のまま残したいのか。

ここでは、まず「英数字+よく使う記号」を対象にした、シンプルで実務向きなユーティリティから作っていきます。


実務で使える「全角英数字→半角」ユーティリティ

マッピングテーブル方式の基本

全角→半角変換を確実に行いたい場合、
「全角文字と半角文字の対応表(マッピング)を自分で持つ」というやり方が一番シンプルで確実です。 Stack Overflow

考え方はこうです。

1 文字ずつ見ていく
その文字が「全角→半角マップ」に載っていれば、対応する半角に置き換える
載っていなければ、そのまま残す

これを C# で書くと、次のようになります。

コード例:全角英数字+一部記号を半角にする

using System;
using System.Collections.Generic;
using System.Text;

public static class ZenkakuConverter
{
    private static readonly Dictionary<char, char> ZenkakuToHankakuMap = new()
    {
        ['0'] = '0', ['1'] = '1', ['2'] = '2', ['3'] = '3', ['4'] = '4',
        ['5'] = '5', ['6'] = '6', ['7'] = '7', ['8'] = '8', ['9'] = '9',

        ['A'] = 'A', ['B'] = 'B', ['C'] = 'C', ['D'] = 'D', ['E'] = 'E',
        ['F'] = 'F', ['G'] = 'G', ['H'] = 'H', ['I'] = 'I', ['J'] = 'J',
        ['K'] = 'K', ['L'] = 'L', ['M'] = 'M', ['N'] = 'N', ['O'] = 'O',
        ['P'] = 'P', ['Q'] = 'Q', ['R'] = 'R', ['S'] = 'S', ['T'] = 'T',
        ['U'] = 'U', ['V'] = 'V', ['W'] = 'W', ['X'] = 'X', ['Y'] = 'Y',
        ['Z'] = 'Z',

        ['a'] = 'a', ['b'] = 'b', ['c'] = 'c', ['d'] = 'd', ['e'] = 'e',
        ['f'] = 'f', ['g'] = 'g', ['h'] = 'h', ['i'] = 'i', ['j'] = 'j',
        ['k'] = 'k', ['l'] = 'l', ['m'] = 'm', ['n'] = 'n', ['o'] = 'o',
        ['p'] = 'p', ['q'] = 'q', ['r'] = 'r', ['s'] = 's', ['t'] = 't',
        ['u'] = 'u', ['v'] = 'v', ['w'] = 'w', ['x'] = 'x', ['y'] = 'y',
        ['z'] = 'z',

        ['!'] = '!', ['@'] = '@', ['#'] = '#', ['$'] = '$', ['%'] = '%',
        ['^'] = '^', ['&'] = '&', ['*'] = '*', ['('] = '(', [')'] = ')',
        ['-'] = '-', ['_'] = '_', ['='] = '=', ['+'] = '+',
        ['['] = '[', [']'] = ']', ['{'] = '{', ['}'] = '}',
        [';'] = ';', [':'] = ':', ['\''] = '\'', ['"'] = '"',
        [','] = ',', ['.'] = '.', ['/'] = '/', ['?'] = '?',
        ['<'] = '<', ['>'] = '>', ['|'] = '|', ['\'] = '\\',
        [' '] = ' ' // 全角スペース→半角スペース
    };

    public static string ToHankakuBasic(string? input)
    {
        if (string.IsNullOrEmpty(input))
        {
            return string.Empty;
        }

        var sb = new StringBuilder(input.Length);

        foreach (char c in input)
        {
            if (ZenkakuToHankakuMap.TryGetValue(c, out char mapped))
            {
                sb.Append(mapped);
            }
            else
            {
                sb.Append(c);
            }
        }

        return sb.ToString();
    }
}
C#

使い方の例

string raw = "ID:ABC123 @テスト";

string normalized = ZenkakuConverter.ToHankakuBasic(raw);

Console.WriteLine(raw);       // ID:ABC123 @テスト
Console.WriteLine(normalized); // ID:ABC123 @テスト
C#

ここでの重要ポイントを整理します。

1 文字ずつ Dictionary<char, char> で変換しているので、「どの文字がどう変わるか」が明確で予測しやすいです。
全角スペースを半角スペースに変えているので、その後のトリムや比較がやりやすくなります。
日本語部分(「テスト」など)はマップに載せていないので、そのまま残ります。

「英数字と記号だけ半角に寄せたい」という業務では、このくらいのユーティリティが一番扱いやすいです。


Normalize とマッピングを組み合わせる“現実的な落としどころ”

まず Normalize でざっくり寄せてから、足りないところをマップで補う

もう少し柔らかい設計として、

  1. Normalize(NormalizationForm.FormKC) で Unicode 的に互換文字を標準形に寄せる
  2. それでも変換されないものだけ、マッピングテーブルで補う

という二段構えにする方法もあります。

これにより、「Unicode 側で面倒を見てくれる部分」は Normalize に任せつつ、
「業務としてこうしてほしい」という細かいルールだけをマッピングで上書きできます。

コード例:Normalize+マッピング

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Text.Unicode;
using System.Text.Encodings.Web;
using System.Text.Normalization;

public static class ZenkakuConverter2
{
    private static readonly Dictionary<char, char> ExtraMap = new()
    {
        [' '] = ' ' // 全角スペースだけは必ず半角にしたい、など
    };

    public static string ToHankakuWithNormalize(string? input)
    {
        if (string.IsNullOrEmpty(input))
        {
            return string.Empty;
        }

        string normalized = input.Normalize(NormalizationForm.FormKC);

        var sb = new StringBuilder(normalized.Length);

        foreach (char c in normalized)
        {
            if (ExtraMap.TryGetValue(c, out char mapped))
            {
                sb.Append(mapped);
            }
            else
            {
                sb.Append(c);
            }
        }

        return sb.ToString();
    }
}
C#

using は実際には System.TextSystem だけで十分です。上はイメージとして見てください。)

このようにしておくと、

Normalize が面倒を見てくれる範囲(全角英数字など)は自動で半角寄りになる
全角スペースなど、Normalize では変わらないが業務的に変えたいものだけをマップで補正する

というバランスの良い実装になります。


実務での使いどころと設計のポイント

「どの層で全角→半角するか」を決める

全角→半角変換は、どこでやるかを決めておくとコードがきれいになります。

典型的には、次のような流れにします。

画面入力や CSV 読み込みなど、「外部から入ってきた瞬間」に全角→半角をかける。
内部ロジックでは「すでに半角にそろっている」前提で扱う。

例えば、ユーザーが入力した「顧客コード」を扱うときに、こうします。

string? rawCode = ReadFromTextBox();          // 全角かもしれない
string code = ZenkakuConverter.ToHankakuBasic(rawCode); // ここで半角にそろえる
code = StringTrimUtil.NormalizeEmpty(code);   // ここでトリム+空白正規化

// 以降、code は「半角・トリム済み・空白なら空文字」という前提で扱える
C#

こうしておくと、ドメインロジック側では「全角かも」「スペース付きかも」といった心配をしなくて済みます。

「どこまで変換するか」を仕様として明文化する

全角→半角は、「やればやるほど良い」というものではありません。
変換しすぎると、「ユーザーが意図した文字列」と「システムが持っている文字列」がズレてしまうこともあります。

例えば、

記号はそのまま残したい
カナは全角のままにしたい
メールアドレスだけは絶対に半角にしたい

といった要件が出てきます。

そのため、

どの項目に対して
どの文字種を
どのタイミングで

全角→半角するのかを、チーム内で決めておくと、ユーティリティの使い方がブレません。


まとめ 「全角→半角ユーティリティ」は“比較と検索を安定させるための下ごしらえ”

全角→半角は、見た目をきれいにするためだけではなく、

「比較・検索・重複チェックを安定させるための前処理」

だと捉えると、設計の筋が通ります。

押さえておきたいポイントは次の通りです。

全角と半角は Unicode 上では別文字なので、そのまま比較すると一致しない。
string.Normalize(NormalizationForm.FormKC) で、全角英数字や一部記号を半角寄りにできるが、万能ではない。
確実に制御したい場合は、「全角→半角」のマッピングテーブルを自前で持ち、1 文字ずつ変換するユーティリティを用意する。
日本語環境では、全角スペースをどう扱うか(半角にするか、そのままか)を最初に決めておくとよい。
外部入力の入口で全角→半角をかけ、内部ロジックでは「半角にそろっている」前提で扱うと、コードがシンプルで安全になる。

ここまでできれば、「全角と半角が混ざっていて比較が合わない」という典型的な日本語システムの沼から抜け出して、
“まず文字をそろえてからロジックを書く”という、プロっぽい C# の書き方に一歩踏み込めます。

タイトルとURLをコピーしました