DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

文字列を置換する

ここでは、文字列内の指定した文字列を別の文字列に置換する方法(例えば、「昨日は雨でした。」の「雨」を「晴れ」に置換して、「昨日は晴れでした。」という文字列を作成する方法)を幾つか紹介します。

String.Replaceメソッド

最も一般的な方法は、String.Replaceメソッドを使う方法です。String.Replaceメソッドは、1番目のパラメータに検索する文字列を、2番目のパラメータに置換後の文字列を指定します。するとこのメソッドは、置換された文字列を返します。

見つかった文字列はすべて置換されます。はじめに見つかった文字列だけを置換するといったことはできません。

文字列を検索する方法は、序数による検索のみです。よって、全く同じ文字列しか検索できません。大文字小文字を区別しない検索や、カルチャに依存した検索はできません。

String.Replaceメソッドは置換された結果の文字列を返しますが、基のStringの内容は変更されないことに注意してください。

String.Replaceメソッドには、パラメータがChar型のオーバーロードもあります。この方が高速ですので、1文字だけ置換する場合はパラメータにChar型を指定すべきです。

以下にString.Replaceメソッドを使用した例を幾つか示します。

VB.NET
コードを隠すコードを選択
Dim s As String = "にわにはにわにわとりがいる。"

'文字列を置換する(「にわ」を「庭」に置換する)
Dim r1 As String = s.Replace("にわ", "庭")
'庭には庭庭とりがいる。

'文字を置換する(「に」を「2」に置換する)
Dim r2 As String = s.Replace("に"c, "2"c)
'2わ2は2わ2わとりがいる。
C#
コードを隠すコードを選択
string s = "にわにはにわにわとりがいる。";

//文字列を置換する(「にわ」を「庭」に置換する)
string r1 = s.Replace("にわ", "庭");
//庭には庭庭とりがいる。

//文字を置換する(「に」を「2」に置換する)
string r2 = s.Replace('に', '2');
//2わ2は2わ2わとりがいる。

StringBuilder.Replaceメソッド

StringBuilderクラスの場合は、StringBuilder.Replaceメソッドで文字列を置換することができます。使い方や動作はString.Replaceメソッドとほぼ同様で、序数による検索で、見つかったすべての文字列を置換します。また、Char型パラメータのオーバーロードも用意されています。

String.Replaceと違うのは、StringBuilder.ReplaceメソッドはStringBuilderオブジェクトの内容そのものが変更されることです。また、StringBuilder.Replaceメソッドでは、文字列を検索する範囲を指定することもできます。

なおString.ReplaceとStringBuilder.Replaceメソッドのパフォーマンスの比較については、後で詳しく説明します。

以下にStringBuilder.Replaceメソッドを使用した例を示します。

VB.NET
コードを隠すコードを選択
Dim s As String = "にわにはにわにわとりがいる。"

'StringBuilderを作成する
Dim sb As New System.Text.StringBuilder(s)

'文字列を置換する(「にわとり」を「鶏」に置換する)
sb.Replace("にわとり", "鶏")
'先頭から2文字の範囲で、文字列を置換する(先頭の「にわ」を「庭」に置換する)
sb.Replace("にわ", "庭", 0, 2)

'Stringに変換する
Dim r1 As String = sb.ToString()
'庭にはにわ鶏がいる。
C#
コードを隠すコードを選択
string s = "にわにはにわにわとりがいる。";

//StringBuilderを作成する
System.Text.StringBuilder sb = new System.Text.StringBuilder(s);

//文字列を置換する(「にわとり」を「鶏」に置換する)
sb.Replace("にわとり", "鶏");
//先頭から2文字の範囲で、文字列を置換する(先頭の「にわ」を「庭」に置換する)
sb.Replace("にわ", "庭", 0, 2);

//Stringに変換する
string r1 = sb.ToString();
//庭にはにわ鶏がいる。

VB.NETのReplace関数

VB.NETのReplace関数を使えば、置換する回数を指定することができます。また、テキストモードでの検索もできます。テキストモードでは、大文字と小文字、ひらがなとカタカナ、半角と全角を区別しないで検索し、置換します。

以下にVB.NETのReplace関数を使った例を幾つか示します。

VB.NET
コードを隠すコードを選択
'Imports Microsoft.VisualBasic

Dim s As String = "にわにはにわにわとりがいる。"

'2回だけ置換する
Dim r1 As String = Replace(s, "にわ", "庭", 1, 2, CompareMethod.Binary)
'庭には庭にわとりがいる。

'ひらがなとカタカナを区別しないで、すべての"ニワ"を"庭"に置換する
Dim r2 As String = Replace(s, "ニワ", "庭", 1, -1, CompareMethod.Text)
'庭には庭庭とりがいる。

'3文字目以降の部分文字列を、1回だけ置換する
Dim r3 As String = Replace(s, "にわ", "2羽", 3, 1, CompareMethod.Binary)
'には2羽にわとりがいる。
C#
コードを隠すコードを選択
//参照設定に「Microsoft.VisualBasic」を追加する必要がある

string s = "にわにはにわにわとりがいる。";

//2回だけ置換する
string r1 = Microsoft.VisualBasic.Strings.Replace(s, "にわ", "庭", 1, 2,
    Microsoft.VisualBasic.CompareMethod.Binary);
//庭には庭にわとりがいる。

//ひらがなとカタカナを区別しないで、すべての"ニワ"を"庭"に置換する
string r2 = Microsoft.VisualBasic.Strings.Replace(s, "ニワ", "庭", 1, -1,
    Microsoft.VisualBasic.CompareMethod.Text);
//庭には庭庭とりがいる。

//3文字目以降の部分文字列を、1回だけ置換する
string r3 = Microsoft.VisualBasic.Strings.Replace(s, "にわ", "2羽", 3, 1,
    Microsoft.VisualBasic.CompareMethod.Binary);
//には2羽にわとりがいる。

Regex.Replaceメソッド

Regex.Replaceメソッドを使うと、正規表現を使って文字列の置換を行うことができます。正規表現を使って置換を行うと、String.ReplaceやStringBuilder.Replaceメソッドではできない、複雑な置換を行うことができます。ただし、正規表現についての知識が必要ですし、パフォーマンスは、通常、String.ReplaceやStringBuilder.Replaceメソッドと比べて大きく劣ります。

正規表現の置換について詳しくは、「正規表現を使って文字列を置換する」をご覧ください。

大文字小文字を区別しないで、回数を指定して置換する

大文字と小文字を区別しない置換や、置換する回数の指定は、VB.NETのReplaceやRegex.Replaceを使えばできます。しかしこれらを使わなくても、自分でそのようなメソッドを作成すれば、意外と簡単にできます。

以下に、置換する回数の指定と、大文字と小文字を区別しない置換が可能なメソッドの例を紹介します。

VB.NET
コードを隠すコードを選択
''' <summary>
''' 指定した文字列内の指定した文字列を別の文字列に置換する。
''' </summary>
''' <param name="input">置換する文字列のある文字列。</param>
''' <param name="oldValue">検索文字列。</param>
''' <param name="newValue">置換文字列。</param>
''' <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param>
''' <param name="compInfo">文字列の検索に使用するCompareInfo。</param>
''' <param name="compOptions">文字列の検索に使用するCompareOptions。</param>
''' <returns>置換された結果の文字列。</returns>
Public Shared Function StringReplace(ByVal input As String, ByVal oldValue As String, _
    ByVal newValue As String, ByVal count As Integer, _
    ByVal compInfo As System.Globalization.CompareInfo, _
    ByVal compOptions As System.Globalization.CompareOptions) As String

    If input Is Nothing OrElse input.Length = 0 OrElse _
        oldValue Is Nothing OrElse oldValue.Length = 0 OrElse _
        count = 0 Then

        Return input
    End If

    If compInfo Is Nothing Then
        compInfo = System.Globalization.CultureInfo.InvariantCulture.CompareInfo
        compOptions = System.Globalization.CompareOptions.Ordinal
    End If

    Dim inputLen As Integer = input.Length
    Dim oldValueLen As Integer = oldValue.Length
    Dim buf As New System.Text.StringBuilder(inputLen)

    Dim currentPoint As Integer = 0
    Dim foundPoint As Integer = -1
    Dim currentCount As Integer = 0

    Do
        '文字列を検索する
        foundPoint = compInfo.IndexOf(input, oldValue, currentPoint, compOptions)
        If foundPoint < 0 Then
            buf.Append(input.Substring(currentPoint))
            Exit Do
        End If

        '見つかった文字列を新しい文字列に換える
        buf.Append(input.Substring(currentPoint, foundPoint - currentPoint))
        buf.Append(newValue)

        '次の検索開始位置を取得
        currentPoint = foundPoint + oldValueLen

        '指定回数置換したか調べる
        currentCount += 1
        If currentCount = count Then
            buf.Append(input.Substring(currentPoint))
            Exit Do
        End If
    Loop While currentPoint < inputLen

    Return buf.ToString()
End Function

''' <summary>
''' 指定した文字列内の指定した文字列を別の文字列に置換する。
''' </summary>
''' <param name="input">置換する文字列のある文字列。</param>
''' <param name="oldValue">検索文字列。</param>
''' <param name="newValue">置換文字列。</param>
''' <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param>
''' <param name="ignoreCase">大文字と小文字を区別しない時はTrue。</param>
''' <returns>置換された結果の文字列。</returns>
Public Shared Function StringReplace(ByVal input As String, ByVal oldValue As String, _
    ByVal newValue As String, ByVal count As Integer, _
    ByVal ignoreCase As Boolean) As String

    If ignoreCase Then
        Return StringReplace(input, oldValue, newValue, count, _
            System.Globalization.CultureInfo.InvariantCulture.CompareInfo, _
            System.Globalization.CompareOptions.OrdinalIgnoreCase)
    Else
        Return StringReplace(input, oldValue, newValue, count, _
            System.Globalization.CultureInfo.InvariantCulture.CompareInfo, _
            System.Globalization.CompareOptions.Ordinal)
    End If
End Function

''' <summary>
''' 指定した文字列内の指定した文字列を別の文字列に置換する。
''' </summary>
''' <param name="input">置換する文字列のある文字列。</param>
''' <param name="oldValue">検索文字列。</param>
''' <param name="newValue">置換文字列。</param>
''' <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param>
''' <returns>置換された結果の文字列。</returns>
Public Shared Function StringReplace(ByVal input As String, ByVal oldValue As String, _
    ByVal newValue As String, ByVal count As Integer) As String

    Return StringReplace(input, oldValue, newValue, count, _
        System.Globalization.CultureInfo.InvariantCulture.CompareInfo, _
        System.Globalization.CompareOptions.Ordinal)
End Function
C#
コードを隠すコードを選択
/// <summary>
/// 指定した文字列内の指定した文字列を別の文字列に置換する。
/// </summary>
/// <param name="input">置換する文字列のある文字列。</param>
/// <param name="oldValue">検索文字列。</param>
/// <param name="newValue">置換文字列。</param>
/// <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param>
/// <param name="compInfo">文字列の検索に使用するCompareInfo。</param>
/// <param name="compOptions">文字列の検索に使用するCompareOptions。</param>
/// <returns>置換された結果の文字列。</returns>
public static string StringReplace(
    string input, string oldValue, string newValue, int count,
    System.Globalization.CompareInfo compInfo,
    System.Globalization.CompareOptions compOptions)
{
    if (input == null || input.Length == 0 ||
        oldValue == null || oldValue.Length == 0 ||
        count == 0)
    {
        return input;
    }

    if (compInfo == null)
    {
        compInfo = System.Globalization.CultureInfo.InvariantCulture.CompareInfo;
        compOptions = System.Globalization.CompareOptions.Ordinal;
    }

    int inputLen = input.Length;
    int oldValueLen = oldValue.Length;
    System.Text.StringBuilder buf = new System.Text.StringBuilder(inputLen);

    int currentPoint = 0;
    int foundPoint = -1;
    int currentCount = 0;

    do
    {
        //文字列を検索する
        foundPoint = compInfo.IndexOf(input, oldValue, currentPoint, compOptions);
        if (foundPoint < 0)
        {
            buf.Append(input.Substring(currentPoint));
            break;
        }

        //見つかった文字列を新しい文字列に換える
        buf.Append(input.Substring(currentPoint, foundPoint - currentPoint));
        buf.Append(newValue);

        //次の検索開始位置を取得
        currentPoint = foundPoint + oldValueLen;

        //指定回数置換したか調べる
        currentCount++;
        if (currentCount == count)
        {
            buf.Append(input.Substring(currentPoint));
            break;
        }
    }
    while (currentPoint < inputLen);

    return buf.ToString();
}

/// <summary>
/// 指定した文字列内の指定した文字列を別の文字列に置換する。
/// </summary>
/// <param name="input">置換する文字列のある文字列。</param>
/// <param name="oldValue">検索文字列。</param>
/// <param name="newValue">置換文字列。</param>
/// <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param>
/// <param name="ignoreCase">大文字と小文字を区別しない時はTrue。</param>
/// <returns>置換された結果の文字列。</returns>
public static string StringReplace(
    string input, string oldValue, string newValue, int count, bool ignoreCase)
{
    if (ignoreCase)
    {
        return StringReplace(input, oldValue, newValue, count,
            System.Globalization.CultureInfo.InvariantCulture.CompareInfo,
            System.Globalization.CompareOptions.OrdinalIgnoreCase);
    }
    else
    {
        return StringReplace(input, oldValue, newValue, count,
            System.Globalization.CultureInfo.InvariantCulture.CompareInfo,
            System.Globalization.CompareOptions.Ordinal);
    }
}

/// <summary>
/// 指定した文字列内の指定した文字列を別の文字列に置換する。
/// </summary>
/// <param name="input">置換する文字列のある文字列。</param>
/// <param name="oldValue">検索文字列。</param>
/// <param name="newValue">置換文字列。</param>
/// <param name="count">置換する回数。負の数が指定されたときは、すべて置換する。</param>
/// <returns>置換された結果の文字列。</returns>
public static string StringReplace(
    string input, string oldValue, string newValue, int count)
{
    return StringReplace(input, oldValue, newValue, count,
        System.Globalization.CultureInfo.InvariantCulture.CompareInfo,
        System.Globalization.CompareOptions.Ordinal);
}

このメソッドの使い方は、以下のような感じです。

VB.NET
コードを隠すコードを選択
Dim s As String = "Niwanihaniwaniwatorigairu."

'1回だけ置換する
Dim r1 As String = StringReplace(s, "niwa", "庭", 1)
'Niwaniha庭niwatorigairu.

'大文字と小文字を区別しないで、すべて置換する
Dim r2 As String = StringReplace(s, "niwa", "庭", -1, True)
'庭niha庭庭torigairu.
C#
コードを隠すコードを選択
string s = "Niwanihaniwaniwatorigairu.";

//1回だけ置換する
string r1 = StringReplace(s, "niwa", "庭", 1);
//Niwaniha庭niwatorigairu.

//大文字と小文字を区別しないで、すべて置換する
string r2 = StringReplace(s, "niwa", "庭", -1, true);
//庭niha庭庭torigairu.

String.ReplaceとStringBuilder.Replaceのパフォーマンスの比較

通常の文字列置換は、StringクラスのReplaceメソッドで十分です。それではどのようなケースでStringBuilderクラスを使うのでしょうか?

基本的には、「文字列処理を高速に行う」で説明しているように、同じ文字列に対して何回も置換を行う時は、StringBuilderを使います。

しかし、「StringBuilder vs. String / Fast String Operations with .NET 2.0」や「Comparing RegEx.Replace, String.Replace and StringBuilder.Replace - Which has better performance?」などによると、同じ文字列に対して複数回置換を行う場合であっても、String.Replaceの方がStringBuilder.Replaceよりも若干速いというベンチマークの結果が出ています。

私も以下のようなコードで、文字列の長さを変えたり、置換する回数を変えたりして試してみましたが、ほとんどの場合でString.Replaceの方が若干速いようでした。

VB.NET
コードを隠すコードを選択
Dim builder As New System.Text.StringBuilder()
For i As Integer = 0 To 20000
    builder.Append("01234")
Next
Dim s As String = builder.ToString()

Dim sw1 As New System.Diagnostics.Stopwatch()
sw1.Start()
For i As Integer = 0 To 1000
    s = s.Replace((i Mod 10).ToString(), ((i + 5) Mod 10).ToString())
Next
sw1.Stop()
Console.WriteLine("String.Replace: {0}", sw1.ElapsedTicks)

Dim sw2 As New System.Diagnostics.Stopwatch()
sw2.Start()
Dim sb As New System.Text.StringBuilder(s)
For i As Integer = 0 To 1000
    sb.Replace((i Mod 10).ToString(), ((i + 5) Mod 10).ToString())
Next
s = sb.ToString()
sw2.Stop()
Console.WriteLine("StringBuild.Replace: {0}", sw2.ElapsedTicks)

Dim sw3 As New System.Diagnostics.Stopwatch()
sw3.Start()
For i As Integer = 0 To 1000
    s = Replace(s, (i Mod 10).ToString(), ((i + 5) Mod 10).ToString())
Next
sw3.Stop()
Console.WriteLine("VB.NET Strings.Replace: {0}", sw3.ElapsedTicks)

Dim sw4 As New System.Diagnostics.Stopwatch()
sw4.Start()
For i As Integer = 0 To 1000
    s = System.Text.RegularExpressions.Regex.Replace( _
        s, (i Mod 10).ToString(), ((i + 5) Mod 10).ToString())
Next
sw4.Stop()
Console.WriteLine("Regex.Replace: {0}", sw4.ElapsedTicks)

'String.Replace: 2186982
'StringBuild.Replace: 3461453
'VB.NET Strings.Replace: 6677110
'Regex.Replace: 18180959
C#
コードを隠すコードを選択
System.Text.StringBuilder builder = new System.Text.StringBuilder();
for (int i = 0; i < 20000; i++)
{
    builder.Append("01234");
}
string s = builder.ToString();

System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch();
sw1.Start();
for (int i = 0; i < 1000; i++)
{
    s = s.Replace((i % 10).ToString(), ((i + 5) % 10).ToString());
}
sw1.Stop();
Console.WriteLine("String.Replace: {0}", sw1.ElapsedTicks);

System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
sw2.Start();
System.Text.StringBuilder sb = new System.Text.StringBuilder(s);
for (int i = 0; i < 1000; i++)
{
    sb.Replace((i % 10).ToString(), ((i + 5) % 10).ToString());
}
s = sb.ToString();
sw2.Stop();
Console.WriteLine("StringBuild.Replace: {0}", sw2.ElapsedTicks);

System.Diagnostics.Stopwatch sw3 = new System.Diagnostics.Stopwatch();
sw3.Start();
for (int i = 0; i < 1000; i++)
{
    s = Microsoft.VisualBasic.Strings.Replace(
        s, (i % 10).ToString(), ((i + 5) % 10).ToString());
}
sw3.Stop();
Console.WriteLine("VB.NET Strings.Replace: {0}", sw3.ElapsedTicks);

System.Diagnostics.Stopwatch sw4 = new System.Diagnostics.Stopwatch();
sw4.Start();
for (int i = 0; i < 1000; i++)
{
    s = System.Text.RegularExpressions.Regex.Replace(
        s, (i % 10).ToString(), ((i + 5) % 10).ToString());
}
sw4.Stop();
Console.WriteLine("Regex.Replace: {0}", sw4.ElapsedTicks);

//String.Replace: 2188440
//StringBuild.Replace: 3446613
//VB.NET Strings.Replace: 6839046
//Regex.Replace: 18224702

ちなみにChar型の置換について上と同様の方法でString.ReplaceとStringBuild.Replaceを比較してみたところ、この場合はStringBuildの方が速いという結果になりました。

なおこれらの結果は環境によって変化する可能性があるため一概には言えないことをご了承ください。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • コードの先頭に記述されている「Imports ??? がソースファイルの一番上に書かれているものとする」(C#では、「using ???; がソースファイルの一番上に書かれているものとする」)の意味が分からないという方は、こちらをご覧ください。
  • 「???を参照に追加します」の意味が分からないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。