CODE Polyglot / よく使う書き方 / C#

C# のよく使う書き方(7本)

開発でよく書くコードの型と小さなツールの、C# の版をまとめました。標準の機能だけで書いていて、毎朝、実際に動かして答えを確かめています。

01. スネークケースとキャメルケースを変換する

user_id と userId を行き来させます。データベースの列名と JSON のキー、定数と変数のように、名前の書き方をそろえるときに使います。HTTPServer のように大文字が続く名前で、よく見かける書き方はほとんどの言語で崩れます。 ほかの言語・正解の例・解説 →

case_convert.cs10.0.402 で 14 / 14 件の出力が正解と一致

// スネークケース(user_id)とキャメルケース(userId)を、指定した向きに変換する
using System.Text;
using System.Text.RegularExpressions;

string mode = args[0], s = args[1];
// C# の \w は「なまえ」のようなひらがなにも合うので、[A-Za-z0-9_] と書く。
// $ は末尾の改行の手前にも合うので、文字列の本当の終わりの \z を使う
if (!Regex.IsMatch(s, @"\A[A-Za-z0-9_]*\z"))
{
    Console.WriteLine("NG: 半角の英数字と _ だけにしてください");
}
else if (mode == "snake")
{
    // 切れ目は2種類。小文字か数字の次の大文字(userId)と、大文字の続きの最後で次が小文字のもの(HTTP|Server)
    var t = Regex.Replace(s, "(?<=[a-z0-9])(?=[A-Z])", "_");
    t = Regex.Replace(t, "(?<=[A-Z])(?=[A-Z][a-z])", "_");
    // ToLower() は動かす場所の言語の設定を使う(トルコ語では I が「ı」)。ToLowerInvariant を使う
    Console.WriteLine(t.ToLowerInvariant());
}
else
{
    // RemoveEmptyEntries で「__」のところの空の部分を捨てる。
    // (TextInfo.ToTitleCase は全部大文字の単語を略語とみなして MAX のまま残すので使わない)
    var sb = new StringBuilder();
    foreach (var p in s.Split('_', StringSplitOptions.RemoveEmptyEntries))
    {
        sb.Append(sb.Length == 0
            ? p.ToLowerInvariant()
            : char.ToUpperInvariant(p[0]) + p.Substring(1).ToLowerInvariant());
    }
    Console.WriteLine(sb.ToString());
}

02. HTML の特殊文字をエスケープする

利用者が入力した文字を HTML に埋め込む前に、< > & " ' を &lt; などに置き換えます。標準の関数は言語ごとに ' の書き方が違い、この決まりの形とそのまま一致したのは Ruby だけでした。 ほかの言語・正解の例・解説 →

escape.cs10.0.402 で 8 / 8 件の出力が正解と一致

// 受け取った文字列の & < > " ' を、HTML の書き方(&amp; &lt; &gt; &quot; &#39;)に変える
string s = args[0];

// WebUtility.HtmlEncode は絵文字や é なども &#127843; のような数字の形に変えてしまう(この回は5文字だけ変える)ので、自分で置き換える。
// Replace はすべて置き換える。& を最初に変えないと、あとで作った &lt; の & まで &amp; になる
string escaped = s
    .Replace("&", "&amp;")
    .Replace("<", "&lt;")
    .Replace(">", "&gt;")
    .Replace("\"", "&quot;")
    .Replace("'", "&#39;");
Console.WriteLine(escaped);

03. 順番を保って重複を消す

同じ値の2回目以降を消し、初めて出た順に残します。タグの一覧や、選んだ項目の一覧でよく使います。集合(Set)に入れて戻す書き方では、言語によって順番が変わったり、実行するたびに変わったりします。 ほかの言語・正解の例・解説 →

unique.cs10.0.402 で 9 / 9 件の出力が正解と一致

// 受け取った文字列から、2回目以降に出たものを消し、初めて出た順に出す
// Distinct は初めて出た順に返し、既定では文字の並び(ordinal)で比べる。
// string.Compare(a, b) == 0 で比べると、カルチャの規則で「が」1文字と「か」+濁点を同じとみなすことがある
var kept = args.Distinct().ToList();
foreach (string s in kept)
{
    Console.WriteLine(s == "" ? "(空)" : s);
}
Console.WriteLine($"{kept.Count}件({args.Length - kept.Count}件を消した)");

04. 日付と時刻を ISO 8601 で読み書きする

API やログでよく使う 2026-10-30T09:00:00+09:00 の形を読み、UTC と日本時間で書き直します。標準のパーサーにそのまま渡すと、2月30日を3月2日として読んだり、タイムゾーンの無い日時を動かす場所の時間帯で読んだりします。 ほかの言語・正解の例・解説 →

iso8601.cs10.0.402 で 15 / 15 件の出力が正解と一致

// ISO 8601 の日時を受け取って、UTC と日本時間で書き出す
using System.Globalization;
using System.Text.RegularExpressions;

// DateTimeOffset.Parse は「2026/10/30」や空白区切りも読み、秒の小数は7桁(100ナノ秒)に四捨五入する(.999999999 は次の秒になる)。
// 形は正規表現で決めて、小数は切り捨てるので外してから読む。\d は全角やアラビア数字にも当たるので [0-9] と書く
var m = Regex.Match(args[0], @"\A([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})(\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})?\z");
Console.WriteLine(Convert(m));

static string Convert(Match m)
{
    if (!m.Success) return "NG: 日時の形ではありません";
    var inv = CultureInfo.InvariantCulture;
    // ParseExact は 2月30日・25時を FormatException にする。カルチャを固定しないと、和暦などの設定で読み違える
    if (!DateTime.TryParseExact(m.Groups[1].Value, "yyyy-MM-dd'T'HH:mm:ss", inv, DateTimeStyles.None, out _))
        return "NG: 日時の形ではありません";
    var tz = m.Groups[3].Value;
    if (tz == "") return "NG: タイムゾーン(Z か +09:00 など)を付けてください";
    // zzz は「+09:00」を読む。Z は zzz では読めないので +00:00 に置き換える
    if (!DateTimeOffset.TryParseExact(m.Groups[1].Value + (tz == "Z" ? "+00:00" : tz), "yyyy-MM-dd'T'HH:mm:sszzz", inv, DateTimeStyles.None, out var t))
        return "NG: 日時の形ではありません";
    // ToLocalTime() や TimeZoneInfo.Local は動かす場所の時間帯。時差(TimeSpan)を直接渡して変える
    var utc = t.ToOffset(TimeSpan.Zero);
    var jst = t.ToOffset(TimeSpan.FromHours(9));
    return "UTC " + utc.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", inv) + "\n" +
           "日本時間 " + jst.ToString("yyyy年MM月dd日 HH:mm:ss", inv);
}

05. アクセスログを集計する

Web サーバーのアクセスログを標準入力から読み、ステータスごとの件数と、応答の遅いリクエストの上位3件を出します。空白で区切って何番目かを取る書き方は手軽ですが、壊れた行を数えたり、応答秒を文字のまま並べたりします。 ほかの言語・正解の例・解説 →

access_log.cs10.0.402 で 8 / 8 件の出力が正解と一致

// 標準入力のアクセスログを読んで、件数・ステータスごとの件数・遅い順の上位3件を出す
using System.Globalization;
using System.Text.RegularExpressions;

// 日時やHTTPの版には空白が入るので、空白で区切って何番目と数えず、行の形そのものを正規表現で確かめる。
// .NET の \d は全角やアラビア文字の数字にも合うので [0-9] と書く。$ は最後の改行の手前にも合うので \z
var log = new Regex(@"\A\S+ \S+ \S+ \[[^\]]+\] ""(\S+) (\S+) [^""]+"" ([0-9]{3}) ([0-9]+|-) ([0-9]+\.[0-9]+)\z");

var rows = new List<(string Sec, double SecNum, string Method, string Path, string Status)>();
int bad = 0;
// Console.ReadLine は \n でも \r\n でも行を区切り、行の終わりの記号を取って返す
string? line;
while ((line = Console.ReadLine()) != null)
{
    if (line == "")
    {
        continue;
    }
    Match m = log.Match(line);
    if (!m.Success)
    {
        bad++;
        continue;
    }
    string sec = m.Groups[5].Value;
    // double.Parse は国の設定に従う(ドイツ語の設定では「,」が小数点)。ログの「.」で読むため InvariantCulture
    rows.Add((sec, double.Parse(sec, CultureInfo.InvariantCulture), m.Groups[1].Value, m.Groups[2].Value, m.Groups[3].Value));
}

Console.WriteLine($"件数 {rows.Count}");
if (bad > 0)
{
    Console.WriteLine($"読めない行 {bad}");
}

// SortedDictionary はキーの小さい順に取り出せる。文字の比べ方は国の設定に左右されない Ordinal にする
var counts = new SortedDictionary<string, int>(StringComparer.Ordinal);
foreach (var r in rows)
{
    counts[r.Status] = counts.GetValueOrDefault(r.Status) + 1;
}
Console.WriteLine("ステータス");
foreach (var (status, n) in counts)
{
    Console.WriteLine($"{status} {n}");
}

Console.WriteLine("遅い順");
// List.Sort は安定でない(同じ秒の行の順が入れ替わることがある)。LINQ の OrderByDescending は安定
foreach (var r in rows.OrderByDescending(r => r.SecNum).Take(3))
{
    Console.WriteLine($"{r.Sec} {r.Method} {r.Path}");
}

06. URL 用の文字列(slug)を作る

記事のタイトルから「hello-world」のような URL の一部を作ります。ブログや商品ページのアドレスを、見て意味が分かる形にするときに使います。よく見かける書き方は、日本語のタイトルをそのまま URL に入れたり、空の slug を黙って返したりします。 ほかの言語・正解の例・解説 →

slug.cs10.0.402 で 14 / 14 件の出力が正解と一致

// 文字列を URL に使える形(slug)に変える
using System.Text.RegularExpressions;

const int Max = 32;
// ToLower() は動かす場所の言語の設定を使う(トルコ語では I が「ı」)。A〜Z だけを変える
var s = Regex.Replace(args[0], "[A-Z]", m => m.Value.ToLowerInvariant());
// C# の \w は Unicode を見るので「日本語」にも合う。残す文字を [a-z0-9] と書く
s = Regex.Replace(s, "[^a-z0-9]+", "-").Trim('-');
if (s.Length > Max)
{
    s = s.Substring(0, Max).TrimEnd('-');
}
Console.WriteLine(s == "" ? "NG: URL に使える文字がありません" : s);

07. URL エンコードする

検索語や日本語の名前を、URL のクエリの値(`?q=...`)に入れられる形に変えます。よく見かける書き方は、空白を「+」にしたり、`!` や `~` を変えずに残したりして、受け取る側で別の文字列になります。 ほかの言語・正解の例・解説 →

url_encode.cs10.0.402 で 15 / 15 件の出力が正解と一致

// 文字列を URL のクエリの値に入れられる形(パーセントエンコード)に変える
// Uri.EscapeDataString は .NET 6 から RFC 3986 のとおり(それより前は ! * ' ( ) を残していた)
// WebUtility.UrlEncode は空白を「+」にして、! * ( ) をそのまま残す(フォームの形)
Console.WriteLine(Uri.EscapeDataString(args[0]));

ほかの言語