はじめに 「改行コード統一」は“見えない差異でバグらないための下ごしらえ”
業務でテキストを扱っていると、
「見た目は同じなのに、なぜか比較で不一致になる」「CSVがうまく分割できない」
みたいな、地味だけどイヤなトラブルが起きます。
原因のひとつが 改行コードの違い です。
Windows、Linux、古いMac、外部システム、ブラウザ…
それぞれが違う改行コードを使っているせいで、
同じ「改行」に見えても、実際には別の文字列になってしまいます。
だからこそ、「まず改行コードを統一してから処理する」ユーティリティを持っておくと、
後続の処理がとても安定します。
ここでは、初心者向けに、
改行コードの種類と違い
なぜ統一が必要になるのか
C#での「改行コード統一ユーティリティ」の作り方
実務での使いどころと注意点
を、例題付きでかみ砕いて説明していきます。
改行コードの種類をざっくり理解する
代表的な3種類の改行コード
改行コードには、主に次の3種類があります。
CRLF(キャリッジリターン+ラインフィード)CR(キャリッジリターン)LF(ラインフィード)
文字として書くとこうなります。
CRLF:\r\nCR:\rLF:\n
それぞれ、歴史的な事情で使われている環境が違います。
Windows:CRLF(\r\n)
Unix / Linux / macOS(現行):LF(\n)
古いMac(OS 9以前など):CR(\r)
今はほとんど CRLF と LF の2種類を意識しておけば十分ですが、
外部システムや古いデータを扱うときには CR 単体も紛れ込むことがあります。
「見た目は同じ1行」でも中身は違う
例えば、次の2つの文字列は、画面上は同じように見えます。
"ABC\r\nDEF""ABC\nDEF"
でも、バイト列としては違うし、Length も違います。
"ABC\r\nDEF" の長さは 7(A B C \r \n D E F)"ABC\nDEF" の長さは 6(A B C \n D E F)
この違いが、比較・分割・置換などで地味に効いてきます。
なぜ改行コードを統一する必要があるのか
比較・検索・置換が安定しなくなる
例えば、外部システムから受け取ったテキストと、
自分のシステム内で生成したテキストを比較するとします。
片方が CRLF、もう片方が LF だと、
見た目は同じでも == 比較では不一致になります。
また、「\n で Split して行ごとに処理しよう」と思っても、
中に \r\n が混ざっていると、行末に \r が残ってしまったりします。
string text = "A\r\nB\r\n";
string[] lines = text.Split('\n');
// lines[0] == "A\r"
// lines[1] == "B\r"
// 最後に空行が入ることもある
C#この「行末に謎の \r が残る」状態は、
トリムし忘れると後続処理でバグの原因になります。
ファイルフォーマットや外部連携の仕様を守るため
CSVや固定長ファイル、外部APIの仕様で、
「改行コードはLFで」とか「Windows形式(CRLF)で」と決まっていることがあります。
このとき、内部ではどんな改行コードでも受け入れつつ、
出力時に指定の改行コードに統一する、という設計にしておくと安全です。
C#での基本方針:「まずLFに統一」→「必要なら出力時に変換」
内部表現をLF(\n)に統一するのがおすすめ
アプリケーション内部では、
改行コードを LF(\n)に統一して扱う のがおすすめです。
理由はシンプルで、
Environment.NewLine が \r\n の環境でも、\n だけで扱ったほうがシンプル
Unix系(Linux / macOS)はもともと \n
多くのライブラリやツールが「LF前提」で動いている
からです。
方針としては、
入力時:CRLF や CR を全部 LF に変換
内部処理:LF 前提で Split / Replace などを行う
出力時:必要に応じて LF → CRLF などに変換
という3段階に分けると、頭がスッキリします。
改行コード統一ユーティリティ(入力側:LFにそろえる)
すべての改行を \n に統一するメソッド
まずは、「どんな改行が来ても \n に統一する」ユーティリティを作ります。
public static class NewLineNormalizer
{
public static string ToLf(string? text)
{
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
string normalized = text.Replace("\r\n", "\n");
normalized = normalized.Replace("\r", "\n");
return normalized;
}
}
C#ポイントは順番です。
先に \r\n を \n に変換
そのあとで残った単独の \r を \n に変換
こうしないと、"\r\n" が "\n\n" になってしまいます。
動作例
string s1 = "A\r\nB\r\nC"; // Windows形式
string s2 = "A\nB\nC"; // Unix形式
string s3 = "A\rB\rC"; // 古いMac形式
Console.WriteLine(NewLineNormalizer.ToLf(s1)); // "A\nB\nC"
Console.WriteLine(NewLineNormalizer.ToLf(s2)); // "A\nB\nC"
Console.WriteLine(NewLineNormalizer.ToLf(s3)); // "A\nB\nC"
C#これで、「どこから来たテキストでも、とりあえずLFにそろえる」ことができます。
改行コード統一ユーティリティ(出力側:任意の形式に変換)
LFからCRLF(Windows形式)に変換する
内部でLFに統一しておけば、
出力時に「環境や仕様に合わせた改行コード」に変換するのは簡単です。
例えば、「LF → CRLF」に変換するメソッドはこう書けます。
public static class NewLineFormatter
{
public static string ToCrLf(string? text)
{
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
string normalized = NewLineNormalizer.ToLf(text);
return normalized.Replace("\n", "\r\n");
}
public static string ToEnvironmentNewLine(string? text)
{
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
string normalized = NewLineNormalizer.ToLf(text);
return normalized.Replace("\n", Environment.NewLine);
}
}
C#ToCrLf は「Windows形式に固定したいとき」、ToEnvironmentNewLine は「実行環境の標準改行に合わせたいとき」に使えます。
動作例
string internalText = "A\nB\nC"; // 内部ではLFで管理
Console.WriteLine(NewLineFormatter.ToCrLf(internalText));
// "A\r\nB\r\nC"
Console.WriteLine(NewLineFormatter.ToEnvironmentNewLine(internalText));
// Windowsなら "A\r\nB\r\nC"
// Linuxなら "A\nB\nC"
C#これで、「内部はLF、外に出すときだけ変換」というスタイルが実現できます。
実務での使いどころ
外部から受け取ったテキストの前処理として
外部API、ファイルアップロード、メール本文、
どこから来たか分からないテキストを扱うときは、
まず最初に NewLineNormalizer.ToLf を通しておくと安心です。
string raw = ReadFromExternalSource();
string normalized = NewLineNormalizer.ToLf(raw);
// 以降は normalized を前提に処理
string[] lines = normalized.Split('\n');
C#こうしておけば、「行末に謎の \r が残る」問題を避けられます。
ファイル出力・レポート出力の最後に
CSVやレポートをファイルとして出力するとき、
仕様書に「改行コード:CRLF」と書かれていることがあります。
その場合は、最後に ToCrLf を通してから書き出します。
string contentLf = BuildReportText(); // 内部はLF前提で組み立て
string contentCrLf = NewLineFormatter.ToCrLf(contentLf);
File.WriteAllText(path, contentCrLf, Encoding.UTF8);
C#こうしておくと、
「開発環境はLinuxだけど、納品物はWindows形式で」というケースにも柔軟に対応できます。
ちょっとした注意点
文字列長やインデックスが変わることを意識する
改行コードを変換すると、文字列の長さが変わります。
"\r\n"(2文字) → "\n"(1文字)"\n"(1文字) → "\r\n"(2文字)
もし、「何文字目にカーソルを置く」といった位置情報を扱っている場合は、
改行コード変換の前後でインデックスがずれることに注意が必要です。
多くの場合は、「位置情報は変換後の文字列で計算する」ようにすれば問題ありません。
すでにLF統一されている前提のライブラリもある
一部のライブラリやツールは、
「改行はLF前提」として動いていることがあります。
その場合は、入力前に ToLf を通しておくと、
ライブラリ側の想定とズレずに済みます。
まとめ 「改行コード統一ユーティリティ」は“見えない差異を消して、テキスト処理を安定させる下地作り”
改行コードの違いは、目に見えないのにバグの原因になる、
ちょっと厄介な存在です。
だからこそ、
入力時に CRLF / CR / LF をすべて LF に統一する
内部処理は LF 前提で行う
出力時にだけ、仕様や環境に合わせて LF → CRLF や Environment.NewLine に変換する
この流れをユーティリティとして固定しておく
という設計にしておくと、
「改行コードのせいでハマる」ことがぐっと減ります。
ここまで理解できれば、「なんとなく \r\n と \n が混ざっている」状態から一歩進んで、
“改行コードを意識的にコントロールできるC#の文字列ユーティリティ”を、自分の手で設計・実装できるようになっていきます。
