C# Tips | 文字列操作:連続する空白を1つにする

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

改行を統一するとは?

プログラムで文字列を扱っていると、ファイルや外部システムによって改行コードが異なることがあります。例えば、Windowsで作成したテキストファイルと、LinuxやmacOSで作成したテキストファイルでは、改行の表現方法が異なる場合があります。

人間が読む分には、どちらも同じように改行されて見えます。しかし、C#のプログラムでは、改行コードの違いが文字列の比較、データの加工、ファイル出力、ログ解析などに影響することがあります。

このような問題を防ぐために役立つのが、改行コードを統一する処理です。

例えば、さまざまな改行コードが混在した文字列を、すべてLF(\n)に統一すれば、後続の処理で改行コードの違いを意識する必要が少なくなります。

業務システムでは、次のような場面で改行の統一が役立ちます。

  • テキストファイルを読み込み、形式を整えてから保存する
  • Web APIから取得した文章を加工する
  • CSVやログなどのテキストデータを解析する
  • 複数のシステムから受け取った文字列を比較する
  • メール本文や帳票のテキストを整形する
  • テストデータに含まれる改行コードの違いを吸収する

この記事では、C#の基本的な文字列操作から始め、初心者でも実務で使いやすい改行統一の方法を順番に解説します。

改行コードの基本を理解する

Windows・Linux・macOSで使われる改行コード

改行コードとは、文字列の中で「ここで次の行に進む」ということを表す制御文字です。代表的な改行コードには、CR、LF、CRLFの3種類があります。

改行コード表記意味
CR\rキャリッジリターン
LF\nラインフィード
CRLF\r\nCRとLFを組み合わせた改行

WindowsではCRLF(\r\n)、LinuxではLF(\n)が一般的です。macOSも現在はLFが一般的に使われています。なお、古いMac OSではCR(\r)が使われていました。

例えば、次の2つの文字列は、見た目は同じ2行の文章でも、内部の文字列は異なります。

// CRLFを使った文字列
string windowsText = "1行目\r\n2行目";

// LFを使った文字列
string linuxText = "1行目\n2行目";
C#

この2つをそのまま比較すると、結果はfalseになります。

// 改行コードが異なるため、falseになる
bool isSame = windowsText == linuxText;

Console.WriteLine(isSame); // False
C#

人間には同じ文章に見えても、プログラムは文字列に含まれる文字を比較するため、改行コードが異なれば別の文字列として扱います。

改行コードが混在することもある

1つの文字列の中に、CRLF、LF、CRが混在することもあります。

例えば、複数のファイルを結合した場合や、異なるシステムから受け取った文章を連結した場合です。

// 異なる改行コードが混在した文字列
string text = "1行目\r\n2行目\n3行目\r4行目";

Console.WriteLine(text);
C#

このような文字列を扱う場合、単純にCRLFだけをLFに置き換えても、CRやLFがすべて統一されるわけではありません。

そのため、改行を統一する処理では、CRLFを1つの改行として正しく認識したうえで、CRとLFの両方も処理することが重要です。

最も簡単な改行統一の方法

Replaceを使ってLFに統一する

C#で改行コードを統一する基本的な方法は、string.Replace()を使って置換することです。

例えば、CRLFとCRをLFに変換する場合は、次のように書けます。

using System;

class Program
{
    static void Main()
    {
        // 改行コードが混在した文字列
        string text = "1行目\r\n2行目\r3行目\n4行目";

        // CRLFをLFに統一する
        text = text.Replace("\r\n", "\n");

        // 残ったCRもLFに統一する
        text = text.Replace("\r", "\n");

        // 統一後の文字列を表示する
        Console.WriteLine(text);
    }
}
C#

このコードでは、最初にCRLFをLFへ置き換え、次に残ったCRをLFへ置き換えています。

最初にCRLFを処理することが重要です。CRLFはCRとLFの2文字で構成されているため、先にCRだけを置き換えると、CRLFが意図しない形に変化してしまいます。

この例では、最終的にすべての改行がLFになります。

なぜCRLFを先に置き換えるのか

例えば、次の文字列を考えます。

// CRLFを含む文字列
string text = "A\r\nB";
C#

ここで、先にCRをLFへ置き換えると、次のようになります。

// CRを先に置換した場合
string text = "A\n\nB";
C#

CRLFがLF・LFの2つになってしまいました。これは、CRLFのCR部分を先に置換したためです。

一方、CRLFを先にLFへ置き換えれば、次のようになります。

// CRLFを先に置換した場合
string text = "A\nB";
C#

このように、CRLFを先に処理することで、1回の改行が2回の改行に増えてしまう問題を防げます。

ただし、改行統一の処理を複数の場所で繰り返し書くと、置換順序を間違えたり、修正漏れが発生したりする可能性があります。実務では、共通のメソッドにまとめておくと便利です。

実務で使える改行統一メソッドを作成する

改行をLFに統一するメソッド

ここからは、改行コードを統一する処理を、何度でも使えるメソッドとして実装します。

C#では、改行を表す文字列として"\r\n"や"\n"を使用できます。次の例では、CRLFとCRをLFへ置き換える処理を1つのメソッドにまとめます。

using System;

class Program
{
    // すべての改行コードをLF(\n)に統一する
    static string NormalizeNewLines(string text)
    {
        // nullの場合はnullをそのまま返す
        if (text == null)
        {
            return null;
        }

        // 最初にCRLFをLFへ置き換える
        text = text.Replace("\r\n", "\n");

        // 残ったCRをLFへ置き換える
        text = text.Replace("\r", "\n");

        // 統一後の文字列を返す
        return text;
    }

    static void Main()
    {
        // 異なる改行コードが混在した文字列
        string text = "1行目\r\n2行目\r3行目\n4行目";

        // 改行コードを統一する
        string normalizedText = NormalizeNewLines(text);

        // 結果を表示する
        Console.WriteLine(normalizedText);
    }
}
C#

NormalizeNewLines()は、受け取った文字列の改行コードをLFに統一して返します。

このメソッドでは、最初にnullかどうかを確認しています。nullは文字列が存在しない状態を表すため、そのままReplace()を呼び出すとNullReferenceExceptionが発生します。

そのため、nullの場合は置換処理を実行せず、nullを返しています。

ただし、この設計は呼び出し元がnullを受け取る可能性を許容している場合に適しています。必ず文字列が必要な業務処理では、nullをエラーとして扱う設計のほうが適切なこともあります。

空文字列とnullの違いに注意する

C#では、nullと空文字列""は異なります。

  • null:文字列オブジェクトが存在しない状態
  • "":文字数が0の文字列

先ほどのメソッドは、空文字列を渡した場合には空文字列を返します。

// 空文字列を渡す
string emptyText = NormalizeNewLines("");

// 結果は空文字列
Console.WriteLine(emptyText.Length); // 0

// nullを渡す
string nullText = NormalizeNewLines(null);

// 結果はnull
Console.WriteLine(nullText == null); // True
C#

このように、改行の統一だけを目的とする場合は、nullや空文字列を勝手に別の値へ変換しないことが重要です。

例えば、nullを空文字列に変換してしまうと、入力値が存在しなかったのか、意図的に空の文字列が渡されたのかを区別できなくなる場合があります。

業務システムでは、データの意味を維持することを意識して実装しましょう。

改行コードをEnvironment.NewLineに統一する

Environment.NewLineとは

C#には、実行環境で一般的に使用される改行文字列を取得できるEnvironment.NewLineがあります。

using System;

class Program
{
    static void Main()
    {
        // 実行環境の標準的な改行コードを取得する
        string newLine = Environment.NewLine;

        // 改行コードを表示する
        Console.WriteLine($"改行コードの長さ: {newLine.Length}");
    }
}
C#

Windowsでは通常CRLF、LinuxやmacOSでは通常LFが返されます。

ただし、Environment.NewLineは、あくまで実行環境に対応した改行文字列です。読み込んだデータに含まれる改行コードを自動的に統一してくれるわけではありません。

そのため、入力データの改行を統一する処理と、出力時に使用する改行コードの選択は、分けて考える必要があります。

実行環境の改行コードに統一する

先ほどのメソッドを応用し、改行コードをEnvironment.NewLineへ統一することもできます。

using System;

class Program
{
    // 改行コードを実行環境の標準的な改行に統一する
    static string NormalizeNewLines(string text)
    {
        // nullの場合はnullを返す
        if (text == null)
        {
            return null;
        }

        // まずLFに統一する
        text = text.Replace("\r\n", "\n");
        text = text.Replace("\r", "\n");

        // LFを実行環境の改行コードに置き換える
        text = text.Replace("\n", Environment.NewLine);

        return text;
    }

    static void Main()
    {
        // LFやCRLFが混在した文字列
        string text = "A\r\nB\nC\rD";

        // 実行環境に合わせて改行を統一する
        string result = NormalizeNewLines(text);

        Console.WriteLine(result);
    }
}
C#

この実装では、最初にすべての改行をLFに統一してから、Environment.NewLineに置き換えています。

一度共通の改行形式に統一してから目的の形式へ変換することで、処理の順序がわかりやすくなります。

ただし、実行環境に合わせることが常に正解とは限りません。例えば、Linuxで動作するプログラムがWindows向けのテキストファイルを生成する場合、出力先の仕様によってはCRLFが必要です。

どの改行コードを使うかは、実行環境だけでなく、出力先の仕様に合わせて決めることが重要です。

正規表現を使って改行を統一する

Regex.Replaceを使う方法

C#では、System.Text.RegularExpressionsのRegex.Replace()を使って、複数種類の改行コードをまとめて処理することもできます。

using System;
using System.Text.RegularExpressions;

class Program
{
    // すべての改行コードをLFに統一する
    static string NormalizeNewLines(string text)
    {
        // nullの場合はnullを返す
        if (text == null)
        {
            return null;
        }

        // CRLF、CR、LFをすべてLFに置き換える
        return Regex.Replace(text, @"\r\n|\r|\n", "\n");
    }

    static void Main()
    {
        // 異なる改行コードが混在した文字列
        string text = "A\r\nB\rC\nD";

        // 改行コードを統一する
        string result = NormalizeNewLines(text);

        Console.WriteLine(result);
    }
}
C#

正規表現の@"\r\n|\r|\n"は、次のいずれかに一致します。

  • \r\n:CRLF
  • \r:CR
  • \n:LF

縦棒|は「または」を意味します。

CRLFを先に記述しているため、CRLFを1つの改行として認識できます。置換先には"\n"を指定しているため、すべての改行がLFになります。

正規表現を使うと、条件をまとめて表現できます。一方で、正規表現に慣れていない初心者にとっては、通常のReplace()を2回使う方法のほうが読みやすい場合もあります。

改行コードの種類が限られている単純な処理なら、無理に正規表現を使う必要はありません。

Regex.ReplaceとReplaceのどちらを選ぶべきか

どちらも改行を統一できますが、実装のわかりやすさや処理内容によって使い分けるとよいでしょう。

方法特徴向いている場面
string.Replace()処理が単純で読みやすいCRLFとCRを順番に置換する
Regex.Replace()複数のパターンをまとめて指定できる置換条件が増える場合
共通メソッド化呼び出し側のコードを簡潔にできる複数の機能で同じ処理を使う

どの方法を選んでも、改行コードの変換ルールが明確であることが大切です。

実務向けのテンプレート

LFに統一する汎用テンプレート

実務で繰り返し使う場合は、改行統一の処理を共通メソッドとして定義しておくと便利です。

using System;

public static class StringUtility
{
    /// <summary>
    /// 文字列に含まれる改行コードをLF(\n)に統一します。
    /// </summary>
    /// <param name="text">変換対象の文字列</param>
    /// <returns>改行コードを統一した文字列。入力がnullの場合はnull。</returns>
    public static string NormalizeNewLines(string text)
    {
        // nullはそのまま返す
        if (text == null)
        {
            return null;
        }

        // CRLFをLFに変換する
        text = text.Replace("\r\n", "\n");

        // 単独のCRもLFに変換する
        text = text.Replace("\r", "\n");

        // 統一した文字列を返す
        return text;
    }
}
C#

呼び出す側は、次のように記述できます。

using System;

class Program
{
    static void Main()
    {
        // 外部システムなどから取得した文字列を想定
        string input = "商品A\r\n商品B\r商品C\n商品D";

        // 改行コードをLFに統一する
        string normalized = StringUtility.NormalizeNewLines(input);

        // 結果を表示する
        Console.WriteLine(normalized);
    }
}
C#

共通メソッドにすることで、各処理で同じコードを繰り返し記述する必要がなくなります。また、将来、変換ルールを変更する場合にも、基本的には共通メソッドを修正するだけで対応できます。

出力先の改行コードを指定できるテンプレート

出力先の仕様に応じて改行コードを指定したい場合は、次のように実装できます。

using System;

public static class StringUtility
{
    /// <summary>
    /// 改行コードを指定された文字列に統一します。
    /// </summary>
    /// <param name="text">変換対象の文字列</param>
    /// <param name="newLine">統一後の改行コード</param>
    /// <returns>改行コードを統一した文字列</returns>
    public static string NormalizeNewLines(string text, string newLine)
    {
        // nullの入力はnullのまま返す
        if (text == null)
        {
            return null;
        }

        // 改行コードとしてnullを指定させない
        if (newLine == null)
        {
            throw new ArgumentNullException(nameof(newLine));
        }

        // 空文字列は改行コードとして使用できないため拒否する
        if (newLine.Length == 0)
        {
            throw new ArgumentException(
                "改行コードには空文字列を指定できません。",
                nameof(newLine));
        }

        // 先にすべての改行をLFに統一する
        text = text.Replace("\r\n", "\n");
        text = text.Replace("\r", "\n");

        // 統一したLFを指定された改行コードに置き換える
        return text.Replace("\n", newLine);
    }
}

class Program
{
    static void Main()
    {
        // 異なる改行コードが混在した文字列
        string text = "A\r\nB\rC\nD";

        // LFに統一する
        string lfText = StringUtility.NormalizeNewLines(text, "\n");

        // CRLFに統一する
        string crlfText = StringUtility.NormalizeNewLines(text, "\r\n");

        Console.WriteLine("LF形式:");
        Console.WriteLine(lfText);

        Console.WriteLine("CRLF形式:");
        Console.WriteLine(crlfText);
    }
}
C#

このテンプレートでは、改行コードを呼び出し側から指定できます。LFが必要な場合は"\n"、CRLFが必要な場合は"\r\n"を渡します。

また、newLineがnullまたは空文字列の場合は例外を発生させています。空文字列を指定すると、改行が削除されてしまうためです。

なお、このメソッドは、指定された改行文字列がCR、LF、CRLFなどの適切な値であるかまでは検証していません。業務要件によっては、許可する改行コードを限定するバリデーションを追加してください。

改行を統一するときの重要な注意点

改行コードの統一と空行の削除は別の処理

改行コードを統一することと、連続する改行を1つにまとめることは異なります。

例えば、次の文字列を考えます。

// 空行を含む文字列
string text = "1行目\n\n3行目";
C#

この文字列には、1行目と3行目の間に空行があります。

改行コードをLFに統一する処理を実行しても、空行はそのまま残ります。

// 改行コードをLFに統一する
string normalized = text.Replace("\r\n", "\n")
                        .Replace("\r", "\n");

// 空行は削除されずに残る
Console.WriteLine(normalized);
C#

空行を残すことは、文章やデータの構造を維持するうえで重要です。例えば、段落の区切り、帳票のレイアウト、ログの区切りなどでは、空行に意味がある場合があります。

一方、空行を削除したい場合や、連続する改行を1つにまとめたい場合は、別の処理として実装しましょう。

文字列全体をTrimしない

改行コードを統一するために、Trim()を併用したくなることがあるかもしれません。

しかし、Trim()は文字列の先頭と末尾にある空白文字などを削除するメソッドです。改行の統一だけを目的とする場合、必ずしも必要ではありません。

// 先頭と末尾の改行を含む文字列
string text = "\r\n1行目\r\n2行目\r\n";

// 改行コードだけをLFに統一する
string normalized = text.Replace("\r\n", "\n")
                        .Replace("\r", "\n");

// 先頭と末尾の改行は残る
Console.WriteLine(normalized);
C#

この場合、先頭と末尾の改行はそのまま保持されます。Trim()を実行すると、意図せず先頭や末尾の改行が失われる可能性があります。

入力データの意味や出力先の仕様を確認し、改行の統一と不要な空白の削除は別々に判断してください。

改行を統一しても文字列の意味が変わる場合がある

改行コードを統一すると、文字列の内部表現が変化します。そのため、変換前と変換後の文字列は、必ずしも同じ文字列ではありません。

例えば、改行コードを含む文章をそのままハッシュ計算や電子署名の対象にしている場合、改行コードを変換すると計算結果が変わる可能性があります。

また、ファイルのバイト列を厳密に比較する処理や、特定の形式に従うデータ交換では、改行コードそのものが仕様の一部になっていることがあります。

そのため、改行統一は、単に見た目を整える処理ではなく、データの表現を変更する処理として扱う必要があります。

電子署名、ハッシュ値、ファイルの完全一致判定、外部システムとのデータ交換などでは、仕様を確認せずに改行コードを変換しないようにしてください。

CSVや固定形式のデータでは慎重に扱う

CSVなどのテキスト形式では、改行がレコードの区切りとして使われる場合があります。また、引用符で囲まれたフィールドの中に改行を含められる形式もあります。

例えば、CSVの1つのフィールドに複数行の文章が含まれている場合、改行コードの統一自体は必要でも、単純に改行を削除するとデータの意味が変わってしまいます。

また、CSV全体を改行文字で分割するだけでは、引用符内の改行を正しく処理できない場合があります。

CSVを扱う場合は、単純な文字列操作だけで済ませるのではなく、CSV形式に対応したライブラリを利用することも検討してください。

改行統一のテストを作成する

基本的なテストケース

改行統一の処理を実務で使用する場合は、複数の入力パターンを確認しておくことが重要です。

少なくとも、次のケースをテストするとよいでしょう。

入力期待する結果(LFに統一)
"A\r\nB""A\nB"
"A\rB""A\nB"
"A\nB""A\nB"
"A\r\nB\rC\nD""A\nB\nC\nD"
""""
nullnull
"A\n\nB""A\n\nB"

特に、CRLF、CR、LFが混在するケースと、空行が含まれるケースは確認しておきましょう。

簡単なテストコード

テスト用のライブラリを導入していない場合でも、簡単な確認コードを作成できます。

using System;

class Program
{
    // 改行コードをLFに統一する
    static string NormalizeNewLines(string text)
    {
        // nullの場合はそのまま返す
        if (text == null)
        {
            return null;
        }

        // CRLFをLFに置き換える
        text = text.Replace("\r\n", "\n");

        // 残ったCRをLFに置き換える
        text = text.Replace("\r", "\n");

        return text;
    }

    static void Main()
    {
        // テストケースを用意する
        string[,] tests =
        {
            { "CRLF", "A\r\nB" },
            { "CR",   "A\rB" },
            { "LF",   "A\nB" },
            { "混在", "A\r\nB\rC\nD" },
            { "空行", "A\n\nB" }
        };

        // 各テストケースを確認する
        for (int i = 0; i < tests.GetLength(0); i++)
        {
            string name = tests[i, 0];
            string input = tests[i, 1];

            // 改行コードを統一する
            string result = NormalizeNewLines(input);

            // 改行を可視化して表示する
            string visibleResult = result.Replace("\n", "[LF]");

            Console.WriteLine($"{name}: {visibleResult}");
        }

        // nullの動作も確認する
        string nullResult = NormalizeNewLines(null);

        Console.WriteLine($"null: {nullResult == null}");
    }
}
C#

このテストコードでは、改行文字を[LF]という表示に置き換えています。改行コードは画面上では見分けにくいため、記号に置き換えて確認すると、変換結果を把握しやすくなります。

実際の業務コードでは、期待する結果と実際の結果が一致するかを自動判定するユニットテストを用意すると、修正時の不具合を防ぎやすくなります。

まとめ

C#で改行コードを統一するには、string.Replace()やRegex.Replace()を使う方法が便利です。初心者が最初に覚えるなら、CRLFを先にLFへ置き換え、その後に残ったCRをLFへ置き換える方法がわかりやすいでしょう。

実務で使用する場合は、次のポイントを押さえておくことが大切です。

  • WindowsではCRLF、LinuxではLFが一般的に使用されます。
  • CRLFはCRとLFの2文字で構成されているため、置換順序に注意します。
  • Environment.NewLineは実行環境に対応した改行文字列を取得するために使用します。
  • nullと空文字列は別の状態として扱います。
  • 改行コードの統一と、空行の削除や連続する改行の圧縮は別の処理です。
  • 出力先の仕様によって、LFとCRLFのどちらを使うか判断します。
  • CSV、電子署名、ハッシュ計算など、改行がデータの意味や完全性に影響する処理では慎重に扱います。
  • 共通メソッドにまとめ、CRLF・CR・LFの混在や空行を含むテストケースを用意します。

改行コードの違いは、画面上では気づきにくいものの、文字列比較やファイル処理で問題になることがあります。改行統一のメソッドを用意しておけば、さまざまな入力データを一定の形式に整えやすくなり、C#の文字列操作をより安全で保守しやすいものにできます。

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