C# Tips | 文字列処理:スネークケース変換

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

はじめに 「スネークケース変換」は“別世界の命名ルールをつなぐアダプタ”

C# の世界では UserNameOrderId のような PascalCase/camelCase が主流ですが、
業務で触るのは C# の世界だけじゃないですよね。

データベースのカラム名は user_name
外部の REST API は order_id
設定ファイル(YAML, JSON)は access_token

こういう「snake_case(スネークケース)」と C# の命名ルールの間を、
きれいに変換してくれる小さなユーティリティを持っておくと、
コードが一気に読みやすく、バグも減ります。

ここでは、初心者向けに、

PascalCase/camelCase → snake_case の変換ルール
実務で使える変換ユーティリティの実装
略語(ID, URL など)をどう扱うか
null や変な入力への安全な対応

を、例題付きでかみ砕いて説明していきます。


スネークケースのルールを言葉で整理する

まずは「snake_case とは何か」を言葉で定義しておきます。

すべて小文字で書く。
単語の区切りはアンダースコア _ でつなぐ。

例としては、

user_name
order_id
created_at

などです。

C# の UserNameuserName と違って、「大文字で区切る」のではなく「アンダースコアで区切る」スタイルです。
この「単語の境目をどう見つけるか」が、変換ロジックの肝になります。


PascalCase/camelCase → snake_case の基本アルゴリズム

ざっくりした考え方

UserNameuserNameuser_name にしたいとき、
やりたいことはこうです。

文字列を左から 1 文字ずつ見る。
「小文字 → 大文字」に切り替わるところを「単語の境目」とみなす。
境目の前に _ を挟む。
最後に全部小文字にする。

例えば UserName なら、

U ser N ameuser_name

というイメージです。

シンプル実装(まずは分かりやすさ優先)

using System;
using System.Text;

public static class SnakeCaseConverter
{
    public static string ToSnakeCase(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        var sb = new StringBuilder();
        char[] chars = value.Trim().ToCharArray();

        for (int i = 0; i < chars.Length; i++)
        {
            char c = chars[i];

            bool isUpper = char.IsUpper(c);

            if (i > 0 && isUpper)
            {
                char prev = chars[i - 1];

                bool prevIsLower = char.IsLower(prev);
                bool prevIsDigit = char.IsDigit(prev);

                if (prevIsLower || prevIsDigit)
                {
                    sb.Append('_');
                }
            }

            sb.Append(char.ToLowerInvariant(c));
        }

        return sb.ToString();
    }
}
C#

ここでは、次のようなルールで _ を挿入しています。

先頭文字には _ を付けない。
現在の文字が大文字で、直前が「小文字」または「数字」のときだけ _ を挟む。

これで、UserNameuser_nameOrder1Numberorder1_number のような変換ができます。


動作確認とパターン別の挙動

代表的な例を試してみる

Console.WriteLine(SnakeCaseConverter.ToSnakeCase("UserName"));      // user_name
Console.WriteLine(SnakeCaseConverter.ToSnakeCase("userName"));      // user_name
Console.WriteLine(SnakeCaseConverter.ToSnakeCase("OrderID"));       // order_id
Console.WriteLine(SnakeCaseConverter.ToSnakeCase("CreatedAt"));     // created_at
Console.WriteLine(SnakeCaseConverter.ToSnakeCase("URL"));           // url
Console.WriteLine(SnakeCaseConverter.ToSnakeCase("User1Name"));     // user1_name
Console.WriteLine(SnakeCaseConverter.ToSnakeCase("  UserName  "));  // user_name
Console.WriteLine(SnakeCaseConverter.ToSnakeCase(null));            // 空文字
C#

ここでの重要ポイントは、

null や空白だけの文字列は、例外にせず空文字にそろえている。
前後の空白は Trim() で落としてから処理している。
URL のような「全部大文字」の場合は、そのまま url になる(途中に _ は入らない)。

「全部大文字の略語は 1 単語として扱う」という挙動は、
実務的にもかなり自然で扱いやすいです。


略語(ID, URL, API など)をどう扱うか

「ID」を id にするか i_d にするか問題

略語が混ざると、少し悩ましいケースが出てきます。

例えば UserID をどう変換するか。

今の実装だと、

UserIDuser_id

になります。

これは、「UserID の間に境目がある」とみなして _ を挟んでいるからです。
多くのシステムでは、この挙動で十分自然です。

一方で、もし UId のような変な名前が来た場合、

UIdu_id

となりますが、そもそも UId という命名自体が微妙なので、
そこまでケアするより「ちゃんと PascalCase で書こう」とチームで決めたほうが健全です。

「URLPath」などの複合略語

URLPath のような名前は、今の実装だとこうなります。

URLPathurl_path

URL 全体が 1 単語として扱われ、Path との境目に _ が入る形です。
これも、多くのケースでは自然な結果です。

もし、「URLPathurlpath にしたい」といった特殊な要件があるなら、
それは個別の例外ルールとして別途処理する必要がありますが、
汎用ユーティリティとしては、今の挙動で十分実用的です。


もう少し厳密にやりたい場合の改良ポイント

「大文字が連続するブロック」を 1 単語とみなす

より厳密にやるなら、「大文字が連続するブロック」を 1 単語とみなす、という考え方もあります。

例えば、

HTTPRequesthttp_request
XMLHttpRequestxml_http_request

のようにしたい場合です。

その場合は、「現在の文字が大文字で、次の文字が小文字なら、そこで単語が終わる」といったルールを追加していきます。
ただし、ロジックが一気に複雑になるので、初心者向け・実務向けの最初の一歩としては、
先ほどのシンプル版で十分です。

「困るケースが具体的に出てきたら、そのときに改良する」で大丈夫です。


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

どこで snake_case に変換するか

スネークケース変換も、「どの層でやるか」を決めておくとスッキリします。

外部システムが snake_case を要求してくる場合。
内部では PascalCase/camelCase で持っておき、
API や SQL を組み立てる直前で snake_case に変換する。

逆に、外部から snake_case が来る場合。
受け取ったときに camelCase/PascalCase に変換してから内部で扱う(これは前回のキャメルケース変換側の話)。

例えば、Dapper などで SQL を組み立てるときに、
C# のプロパティ名からカラム名を作るユーティリティとして使うイメージです。

string propertyName = "UserName";
string columnName = SnakeCaseConverter.ToSnakeCase(propertyName); // user_name

string sql = $"SELECT {columnName} FROM users";
C#

こうしておくと、「C# 側は C# の命名ルール」「DB 側は DB の命名ルール」を守りつつ、
その橋渡しをユーティリティで吸収できます。


まとめ 「スネークケース変換ユーティリティ」は“命名文化の違いを吸収する通訳”

snake_case 変換は、単なる文字列遊びではなく、

「C# と DB/外部API の命名ルールの違いを吸収する通訳」

だと考えると、役割がはっきり見えてきます。

押さえておきたいポイントはこうです。

PascalCase/camelCase → snake_case は、「大文字の前に _ を挟んで、最後に全部小文字にする」という流れで実装できる。
null や空白だけの入力は、例外にせず空文字にそろえると、呼び出し側が楽になる。
略語(ID, URL など)は、「全部大文字のブロックは 1 単語」とみなすシンプルなルールで、実務的には十分なことが多い。
「どの層で snake_case に変換するか」(DB 用、API 用など)を決めておくと、システム全体の命名がきれいに保てる。

ここまで理解できれば、「手で user_name と書いている」段階から一歩進んで、
「C# の名前から自動で snake_case を生成する」実務的なユーティリティを、自分で設計・実装できるようになっていきます。

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