C#(CSharp)の改行コード完全ガイド|Environment.NewLine・\r\n・\nの違いと使い方

改行コードを適切に選ばないと、Windowsでは正しく表示されるのにLinuxでは余分な文字が入る、CSVファイルを開くとレコードが崩れる、文字列を行ごとに分割できない、といった問題が発生します。

この記事では、C#の改行コードについて、Environment.NewLine\r\n\nの違いから、文字列、コンソール、ファイル、CSV、HTMLでの実践的な使い方まで詳しく解説します。

1. C#の改行コードとは

1-1. 改行コードが文字列やテキストファイルで果たす役割

改行コードとは、文字列の中で「ここで現在の行を終了し、次の行へ移動する」ことを表す制御文字です。

たとえば、次の文字列は見た目上は2行ですが、内部では「1行目」と「2行目」の間に改行コードが入っています。

1行目2行目

C#では、次のように改行コードを含む文字列を作成できます。

string text = "1行目\n2行目";

改行コードは、主に次の場面で使用されます。

  • コンソールに複数行のメッセージを表示する

  • テキストファイルを行単位で保存する

  • ログメッセージを読みやすく整形する

  • CSVやTSVのレコードを区切る

  • メール本文や帳票データを複数行にする

  • 文字列を行ごとに分割する

画面上の表示だけでなく、データをどこで区切るかを示す重要な役割を持っています。

1-2. CR・LF・CRLFの意味と違い

代表的な改行コードは、CR、LF、CRLFの3種類です。

名称C#での表記Unicode意味
CR\rU+000DCarriage Return
LF\nU+000ALine Feed
CRLF\r\nU+000D+U+000ACRとLFの組み合わせ

CRは「カーソルを行の先頭へ戻す」、LFは「次の行へ送る」という意味を持ちます。

現在の一般的なテキスト処理では、次のように使用されます。

string cr = "\r";string lf = "\n";string crlf = "\r\n";

\rだけをコンソールへ出力すると、改行せずにカーソルが行頭へ戻ることがあります。この動作を利用すると、進捗表示などを同じ行で更新できます。

for (int i = 0; i <= 100; i++){Console.Write($"\r進捗: {i}%");Thread.Sleep(20);}

ただし、\rの表示結果は使用するターミナルや出力先によって異なる場合があります。

1-3. Windows・Linux・macOSで使われる改行コード

現在、主要なOSでは次の改行コードが一般的に使われます。

OS一般的な改行コードC#での表記
WindowsCRLF\r\n
LinuxLF\n
macOSLF\n
旧Mac OSCR\r

現在のmacOSはUnix系OSであるため、Linuxと同じLFが基本です。旧Mac OSではCRが使われていましたが、現在のアプリケーションで新しくCR形式を採用するケースはほとんどありません。

Windowsで作成したテキストファイルをLinuxで処理する場合や、Linux上のC#アプリケーションからWindows向けファイルを出力する場合には、改行コードの違いを意識する必要があります。

1-4. 改行コードと文字コードの違い

改行コードと文字コードは、名前が似ていますが役割が異なります。

改行コードは、文字列内で行の終了を表す文字または文字列です。一方、文字コードは、文字をどのようなバイト列として保存するかを定める仕組みです。

代表的な文字コードには、次のものがあります。

  • UTF-8

  • UTF-16

  • Shift_JIS

  • EUC-JP

たとえば、UTF-8のテキストファイルでも、改行コードはCRLFにもLFにもできます。

File.WriteAllText("sample.txt","1行目\r\n2行目",Encoding.UTF8);

この例では、文字コードがUTF-8、改行コードがCRLFです。

ファイルの文字化けは文字コードの不一致、行末に不要な記号が表示される問題は改行コードの不一致が原因であることが多いため、両者を分けて考えることが重要です。

2. C#で文字列を改行する基本的な方法

2-1. Environment.NewLineを使って改行する

実行環境に合わせて改行したい場合は、Environment.NewLineを使用します。

string text = "1行目" + Environment.NewLine + "2行目";

Console.WriteLine(text);

実行結果は次のとおりです。

1行目2行目

Environment.NewLineは、実行中のプラットフォームに適した改行文字列を返します。

  • Windowsでは\r\n

  • LinuxやmacOSなどのUnix系環境では\n

OSに依存しないC#アプリケーションを作る場合に適しています。

2-2. エスケープシーケンス「\n」を使って改行する

\nはLFを表すエスケープシーケンスです。

string text = "名前: 山田太郎\n年齢: 30歳";

Console.WriteLine(text);

\nは短く記述でき、Windowsのコンソールや多くのテキストエディターでも改行として表示されます。

ただし、文字列の内部に格納されるのは常にLFです。Windows標準形式のCRLFが必要なファイルや外部システムに渡すデータでは、仕様を確認してから使用してください。

2-3. 「\r\n」を指定してWindows形式で改行する

Windows形式の改行コードを明示したい場合は、\r\nを使用します。

string text = "商品A\r\n商品B\r\n商品C";

Linux上で実行していても、結果にはCRLFが入ります。そのため、Windows向けのテキストファイルや、CRLFを要求するデータ形式を生成する場合に利用できます。

string windowsText = string.Join("\r\n", new[]{"1行目","2行目","3行目"});

File.WriteAllText("windows-text.txt", windowsText);

2-4. Console.WriteLineで出力時に改行する

Console.WriteLineは、指定した内容を出力した後に改行します。

Console.WriteLine("1行目");Console.WriteLine("2行目");

一方、Console.Writeは自動的に改行しません。

Console.Write("C#");Console.Write("入門");

実行結果は次のようになります。

C#入門

出力時に行を分けたいだけであれば、文字列へ改行コードを埋め込むより、Console.WriteLineを複数回呼び出す方が読みやすいこともあります。

2-5. 文字列補間で変数と改行コードを組み合わせる

文字列補間を使うと、変数と改行コードを読みやすく組み合わせられます。

string name = "山田太郎";int age = 30;

string profile =$"名前: {name}{Environment.NewLine}" +$"年齢: {age}歳";

Console.WriteLine(profile);

\nを使用する場合は、さらに短く記述できます。

string profile = $"名前: {name}\n年齢: {age}歳";

出力先のOSに合わせるならEnvironment.NewLine、LFで統一する仕様なら\nを選びます。

2-6. 改行を2回入れて空行を作る

改行コードを2回続けると、行と行の間に空行を作れます。

string text ="見出し" +Environment.NewLine +Environment.NewLine +"本文";

Console.WriteLine(text);

\nを使用する場合は次のように記述します。

string text = "見出し\n\n本文";

StringBuilderでは、引数なしのAppendLineを使うと空行を追加できます。

var builder = new StringBuilder();

builder.AppendLine("見出し");builder.AppendLine();builder.AppendLine("本文");

Console.Write(builder.ToString());

3. Environment.NewLine・\r\n・\nの違い

3-1. Environment.NewLineが返す値

Environment.NewLineが返す値は、実行環境によって異なります。

Console.WriteLine(Environment.NewLine == "\r\n"? "CRLF": "LFまたはその他");

一般的には、Windowsで\r\n、Unix系環境で\nになります。

重要なのは、開発に使用したパソコンではなく、アプリケーションを実際に実行している環境によって値が決まる点です。

たとえば、Windowsで開発したC#アプリケーションをLinuxのDockerコンテナで実行すると、Environment.NewLineは通常\nになります。

3-2. 「\r」「\n」「\r\n」の動作を比較

各改行文字の違いは、次のコードで確認できます。

string[] values ={"\r","\n","\r\n"};

foreach (string value in values){Console.WriteLine(string.Join(" ",value.Select(c => $"U+{(int)c:X4}")));}

実行結果の例は次のとおりです。

U+000DU+000AU+000D U+000A

それぞれの性質を整理すると、次のようになります。

表記文字数主な用途
\r1文字行頭へ戻る、旧Mac形式
\n1文字Linux・macOS・Web系データ
\r\n2文字Windows形式
Environment.NewLine環境による実行環境に合わせた改行

C#のstring.Lengthで確認すると、CRLFは2文字として数えられます。

Console.WriteLine("\r".Length);   // 1Console.WriteLine("\n".Length);   // 1Console.WriteLine("\r\n".Length); // 2

3-3. WindowsとLinuxで実行結果が変わる理由

Environment.NewLineWriteLine系の処理は、実行環境の標準的な行末形式を使用します。

そのため、同じC#コードでも、WindowsとLinuxで生成されるバイト列が異なる場合があります。

string text = $"A{Environment.NewLine}B";byte[] bytes = Encoding.UTF8.GetBytes(text);

Console.WriteLine(Convert.ToHexString(bytes));

Windowsでは、改行部分に0D0Aが含まれます。

410D0A42

Linuxでは、改行部分が0Aになります。

410A42

人が画面で見ると同じ2行でも、ファイル比較、ハッシュ値、テスト結果、外部システムへの送信データでは違いが現れます。

3-4. Environment.NewLineを使うべきケース

Environment.NewLineが適しているのは、実行環境に自然な形式でテキストを扱いたいケースです。

代表例は次のとおりです。

  • OS上で人が読むテキストファイルを作る

  • コンソール向けのメッセージを組み立てる

  • デスクトップアプリのテキスト表示を整形する

  • OSに依存しないアプリケーションを作る

  • ローカル環境向けのログを生成する

string report = string.Join(Environment.NewLine,"処理結果","成功: 10件","失敗: 0件");

特定形式を要求されていない一般的な文章では、最初の選択肢として使いやすい方法です。

3-5. 「\n」や「\r\n」を明示すべきケース

改行形式が仕様で決まっている場合は、実行環境に任せず明示します。

\nを明示する主なケースは次のとおりです。

  • Linux向けのシェルスクリプトを生成する

  • Gitで管理する設定ファイルをLFに統一する

  • JSONやWeb API向けのデータをLFで生成する

  • OSにかかわらず同一のテスト結果を得る

string linuxScript = "#!/bin/bash\necho "Hello"\n";

\r\nを明示する主なケースは次のとおりです。

  • Windows向けのテキストファイルを生成する

  • CRLFを要求するプロトコルやファイル仕様に対応する

  • Windows用のバッチファイルを生成する

  • 既存システムがCRLFを前提としている

string batchFile = "@echo off\r\necho Hello\r\n";

外部仕様がある場合は、Environment.NewLineより仕様で指定された改行コードを優先します。

3-6. 用途別の改行コード早見表

用途推奨方法
実行環境に合わせた文章Environment.NewLine
コンソールで行末を改行Console.WriteLine
Linux・macOS向けファイル\n
Windows向けファイル\r\n
OSに依存しない固定形式仕様に従って明示
複数行の固定文章Raw文字列リテラル
大量の文字列組み立てStringBuilder.AppendLine
ファイルを1行ずつ出力StreamWriter.WriteLine
文字列配列をファイル化File.WriteAllLines
改行形式が混在する入力正規化してから処理

4. 複数行文字列を作成する方法

4-1. 通常の文字列リテラルに改行コードを入れる

通常の文字列リテラルでは、\n\r\nを使用します。

string message = "処理を開始します。\nしばらくお待ちください。";

Environment.NewLineと連結する方法もあります。

string message ="処理を開始します。" +Environment.NewLine +"しばらくお待ちください。";

短いメッセージなら\n、OSに合わせる必要があるならEnvironment.NewLineが分かりやすいでしょう。

4-2. 逐語的文字列リテラル「@」で複数行を記述する

文字列の先頭に@を付けると、逐語的文字列リテラルになります。ソースコード上で直接改行して、複数行文字列を記述できます。

string message = @"1行目2行目3行目";

逐語的文字列では、\nは改行ではなく、バックスラッシュとnとして扱われます。

string text = @"1行目\n2行目";

Console.WriteLine(text);

実行結果は次のとおりです。

1行目\n2行目

ダブルクォートを含める場合は、""と2つ続けます。

string text = @"彼は""こんにちは""と言いました。";

4-3. Raw文字列リテラルで複数行を記述する

C# 11以降では、3つ以上のダブルクォートで囲むRaw文字列リテラルを使用できます。

string message = """1行目2行目3行目""";

Raw文字列リテラルでは、ダブルクォートやバックスラッシュをエスケープせずに記述できます。

string json = """{"name": "山田太郎","path": "C:\data\sample.txt"}""";

JSON、XML、HTML、SQLなどの複数行テキストをC#コード内へ埋め込む場合に便利です。

閉じる側の"""より左にある共通のインデントは、文字列の内容から取り除かれます。そのため、ソースコードをインデントしたまま、出力内容を左寄せできます。

4-4. 複数行文字列に変数を埋め込む

逐語的文字列と文字列補間を組み合わせる場合は、$@"..."または@$"..."を使用します。

string name = "山田太郎";int age = 30;

string profile = $@"名前: {name}年齢: {age}歳";

Raw文字列リテラルに変数を埋め込む場合は、先頭に$を付けます。

string name = "山田太郎";int age = 30;

string profile = $"""名前: {name}年齢: {age}歳""";

波かっこ自体を多く含むJSONなどでは、$$を使用すると、変数を埋め込む部分だけを二重波かっこで指定できます。

string name = "山田太郎";

string json = $$"""{"name": "{{name}}"}""";

4-5. ソースコード自体の改行コードが文字列に与える影響

逐語的文字列リテラルや複数行Raw文字列リテラルでは、ソースコード上の改行が文字列の内容になります。

そのため、ソースファイルの行末形式がCRLFからLFへ変換された場合、生成される文字列の改行形式にも影響する可能性があります。

たとえば、Gitの設定やコードフォーマッターによってソースファイルの改行コードが統一される環境では注意が必要です。

改行形式を必ず固定したい場合は、複数行リテラルへ直接依存せず、次のように明示的に組み立てます。

string text = string.Join("\r\n","1行目","2行目","3行目");

実行環境へ合わせる場合は、Environment.NewLineを区切り文字にします。

string text = string.Join(Environment.NewLine,"1行目","2行目","3行目");

4-6. 複数行文字列を読みやすく記述するポイント

短い文字列では、エスケープシーケンスを使うと簡潔です。

string message = "成功\n処理が完了しました。";

長い固定文章やJSONでは、Raw文字列リテラルが読みやすくなります。

string message = """処理が完了しました。

詳細については、ログファイルを確認してください。""";</span></code></pre><p><span>複数の値を繰り返し追加する場合は、</span><code dir="ltr"><span>StringBuilder</span></code><span>を使います。</span></p><pre dir="ltr"><code dir="ltr"><span>var builder = new StringBuilder();

foreach (string item in items){builder.AppendLine(item);}

string result = builder.ToString();

目的に応じて、短い文字列、固定文章、動的な文字列を使い分けることが大切です。

5. C#の主要な処理で改行する実践例

5-1. Console.WriteLineでコンソールに複数行を出力する

コンソールへ複数行を出力する最も基本的な方法は、Console.WriteLineを複数回呼び出すことです。

Console.WriteLine("処理開始");Console.WriteLine("データを読み込んでいます");Console.WriteLine("処理完了");

1つの文字列にまとめてから出力することもできます。

string message = string.Join(Environment.NewLine,"処理開始","データを読み込んでいます","処理完了");

Console.WriteLine(message);

Console.WriteLineの最後にも改行が追加されるため、文字列の末尾に改行があると、意図せず空行ができることがあります。

string message = "処理完了" + Environment.NewLine;Console.WriteLine(message); // 改行が重なり、空行ができる

末尾改行が不要な文字列をWriteLineで出力するか、末尾改行を含む文字列はConsole.Writeで出力すると整理できます。

5-2. StringBuilder.AppendLineで文字列を組み立てる

複数の文字列を繰り返し追加する場合は、StringBuilder.AppendLineが便利です。

using System.Text;

var builder = new StringBuilder();

builder.AppendLine("商品一覧");builder.AppendLine("商品A: 1,000円");builder.AppendLine("商品B: 2,000円");

string result = builder.ToString();

Console.Write(result);

AppendLineは、文字列を追加した後に既定の改行を追加します。

空行だけを追加したい場合は、引数なしで呼び出します。

builder.AppendLine("見出し");builder.AppendLine();builder.AppendLine("本文");

改行コードをLFに固定したい場合は、Appendで明示します。

builder.Append("1行目").Append('\n');builder.Append("2行目").Append('\n');

5-3. StreamWriter.WriteLineでテキストファイルに書き込む

StreamWriter.WriteLineを使用すると、ファイルへ1行ずつ書き込めます。

using System.Text;

using var writer = new StreamWriter("output.txt",append: false,new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));

writer.WriteLine("1行目");writer.WriteLine("2行目");writer.WriteLine("3行目");

出力する改行コードを指定したい場合は、StreamWriter.NewLineプロパティを設定できます。

LFに固定する例は次のとおりです。

using var writer = new StreamWriter("linux.txt");

writer.NewLine = "\n";writer.WriteLine("1行目");writer.WriteLine("2行目");

CRLFに固定する場合は次のようにします。

using var writer = new StreamWriter("windows.txt");

writer.NewLine = "\r\n";writer.WriteLine("1行目");writer.WriteLine("2行目");

ファイル形式の仕様が決まっている場合に有効です。

5-4. File.WriteAllLinesで複数行のファイルを作成する

文字列の配列やリストを1行ずつファイルへ保存する場合は、File.WriteAllLinesを使用できます。

string[] lines ={"1行目","2行目","3行目"};

File.WriteAllLines("output.txt",lines,new UTF8Encoding(false));

コードが簡潔で、ファイルを開く処理や閉じる処理も自動的に行われます。

改行コードを厳密に指定する必要がある場合は、string.JoinFile.WriteAllTextを組み合わせます。

string text = string.Join("\n", lines);

File.WriteAllText("output-lf.txt",text,new UTF8Encoding(false));

5-5. ログメッセージに改行を挿入する

例外情報や処理結果をログへ出力するとき、複数行にすると読みやすくなります。

try{throw new InvalidOperationException("処理に失敗しました。");}catch (Exception ex){string log =$"日時: {DateTime.Now:yyyy-MM-dd HH:mm:ss}{Environment.NewLine}" +$"種類: {ex.GetType().Name}{Environment.NewLine}" +$"内容: {ex.Message}{Environment.NewLine}" +$"詳細: {ex.StackTrace}";

Console.WriteLine(log);

}

ただし、1レコード1行を前提とするログ収集システムでは、メッセージ内の改行によって別レコードとして解析されることがあります。

その場合は、改行をスペースやエスケープ表現に置き換えます。

string singleLineMessage = message.Replace("\r\n", " ").Replace("\n", " ").Replace("\r", " ");

ログの保存形式や解析ツールの仕様に合わせて判断してください。

5-6. CSV・TSVファイルで改行を扱う

CSVやTSVでは、通常、改行が1レコードの終端として使われます。

string[] records ={"1,山田太郎,東京","2,佐藤花子,大阪"};

File.WriteAllText("data.csv",string.Join("\r\n", records),new UTF8Encoding(false));

CSVのフィールド内に改行を含める場合は、そのフィールドをダブルクォートで囲む必要があります。

string csv ="id,description\r\n" +"1,"1行目\r\n2行目"";

フィールド内のダブルクォートは、2つ重ねてエスケープします。

string value = "彼は"OK"と言った";string escaped = $""{value.Replace(""", """")}"";

CSVにはアプリケーションごとの方言が多いため、複雑なCSVを扱う場合は専用ライブラリの利用も検討してください。

TSVではフィールドをタブで区切り、レコードを改行で区切ります。

string tsv = string.Join("\n","id\tname","1\t山田太郎","2\t佐藤花子");

5-7. HTMLでC#の改行が反映されない場合の対処法

C#の文字列に\nEnvironment.NewLineが入っていても、HTML上ではそのまま改行表示されないことがあります。

string text = "1行目\n2行目";

HTMLでは通常、連続する空白や改行が1つの空白として扱われるためです。

<br>へ変換する方法があります。

string html = text.Replace("\r\n", "<br>").Replace("\n", "<br>").Replace("\r", "<br>");

ただし、ユーザーが入力した文字列をHTMLとして表示するときは、先にHTMLエンコードを行い、スクリプトを埋め込まれないようにする必要があります。

CSSのwhite-spaceを使用する方法もあります。

<div style="white-space: pre-line;">1行目2行目</div>

入力内の改行と連続した空白を維持したい場合は、white-space: pre-wrap;も利用できます。

6. 文字列に含まれる改行コードを判定・分割する方法

6-1. Containsで改行コードの有無を確認する

文字列に改行が含まれているかは、Containsで確認できます。

string text = "1行目\n2行目";

bool hasLf = text.Contains('\n');

Console.WriteLine(hasLf); // True

実行環境の標準改行を調べる場合は、Environment.NewLineを指定します。

bool hasNativeNewLine =text.Contains(Environment.NewLine);

ただし、Windows環境でEnvironment.NewLineを検索すると、LFだけを含む文字列は検出できません。

入力元が不明な場合は、\rまたは\nの有無を確認します。

bool hasAnyNewLine =text.Contains('\r') || text.Contains('\n');

6-2. 「\r\n」「\n」「\r」を区別して判定する

改行コードの種類を判定するときは、最初に\r\nを調べます。

string GetNewLineType(string text){if (text.Contains("\r\n")){return "CRLF";}

if (text.Contains('\n')){return "LF";}if (text.Contains('\r')){return "CR";}return "改行なし";

}

\r\nより先に\r\nを調べると、CRLFに含まれる一部だけを検出してしまいます。

複数の改行コードが混在しているか確認する場合は、それぞれを個別に数えます。

int crlfCount = Regex.Matches(text, "\r\n").Count;

string withoutCrlf = text.Replace("\r\n", "");

int lfCount = withoutCrlf.Count(c => c == '\n');int crCount = withoutCrlf.Count(c => c == '\r');

bool isMixed =new[] { crlfCount, lfCount, crCount }.Count(count => count > 0) >= 2;

6-3. Splitで文字列を行ごとに分割する

改行コードがLFだと分かっている場合は、\nで分割できます。

string text = "A\nB\nC";

string[] lines = text.Split('\n');

Windows形式のCRLF文字列を\nだけで分割すると、各行の末尾に\rが残ることがあります。

string text = "A\r\nB\r\nC";string[] lines = text.Split('\n');

Console.WriteLine(lines[0] == "A\r"); // True

複数形式に対応する場合は、改行文字列をすべて指定します。

string[] lines = text.Split(new[] { "\r\n", "\n", "\r" },StringSplitOptions.None);

空行を除外したい場合は、StringSplitOptions.RemoveEmptyEntriesを使用します。

string[] lines = text.Split(new[] { "\r\n", "\n", "\r" },StringSplitOptions.RemoveEmptyEntries);

ただし、空行に意味がある文章やCSVでは、RemoveEmptyEntriesを使うと情報が失われるため注意してください。

利用している.NETのバージョンでReplaceLineEndingsが使える場合は、改行を統一してから分割する方法もあります。

string normalized = text.ReplaceLineEndings("\n");string[] lines = normalized.Split('\n');

この方法では、CRLF、LF、CRなどの改行をLFへ統一できます。

6-4. StringReader.ReadLineで1行ずつ読み取る

StringReader.ReadLineを使用すると、改行コードを細かく意識せずに文字列を1行ずつ読み取れます。

using var reader = new StringReader(text);

string? line;

while ((line = reader.ReadLine()) is not null){Console.WriteLine($"読み取り: {line}");}

ReadLineが返す文字列には、行末の改行コードは含まれません。

大量の文字列を処理するときや、行ごとに異なる処理を行いたい場合に適しています。

using var reader = new StringReader(text);

int lineNumber = 1;string? line;

while ((line = reader.ReadLine()) is not null){Console.WriteLine($"{lineNumber}: {line}");lineNumber++;}

元の改行コードがCRLFだったのかLFだったのかを保持する必要がある場合は、ReadLineでは判別できません。その場合は、元の文字列を直接調査するか、正規表現などで改行コードを含めて分割します。

まとめ

C#の改行コードには、主に\r\n\r\nEnvironment.NewLineがあります。

一般的なC#アプリケーションでは、実行環境に合わせて改行するならEnvironment.NewLine、LinuxやWeb系の形式へ統一するなら\n、Windows形式へ固定するなら\r\nを使用します。

コンソール出力ではConsole.WriteLine、動的な文字列の組み立てではStringBuilder.AppendLine、ファイルへの行単位出力ではStreamWriter.WriteLineが便利です。

一方、CSV、プロトコル、バッチファイル、シェルスクリプトなど、外部仕様で改行形式が決まっている場合は、OSの標準値ではなく仕様で指定された改行コードを明示してください。

入力文字列の改行形式が不明な場合は、CRLF、LF、CRのすべてを想定し、改行を正規化してから分割すると安定した処理を実装できます。