大文字と小文字を区別しないstring.Replaceの代替はありますか?


306

私は、文字列を検索し、のすべての出現交換する必要がある%FirstName%%PolicyAmount%データベースから引き出された値とを。問題は、FirstNameの大文字と小文字の違いです。そのため、このString.Replace()メソッドを使用できません。この件に関するウェブページを見たことがあります

Regex.Replace(strInput, strToken, strReplaceWith, RegexOptions.IgnoreCase);

ただし、何らかの理由でで置き換えようと%PolicyAmount%する$0と、置き換えが行われません。ドル記号が正規表現の予約文字であることと関係があると思います。

正規表現の特殊文字を処理するために入力をサニタイズすることを含まない、使用できる別の方法はありますか?


1
「$ 0」が入る変数である場合、それは正規表現にまったく影響を与えません。
cfeduke、2008年

回答:


132

MSDN
$ 0 から -「グループ番号番号(10進数)に一致する最後の部分文字列を置き換えます。」

.NET正規表現では、グループ0が常に完全一致です。リテラル$の場合、次のことを行う必要があります。

string value = Regex.Replace("%PolicyAmount%", "%PolicyAmount%", @"$$0", RegexOptions.IgnoreCase);

16
この特定のケースではこれで問題ありませんが、文字列が外部から入力される場合、正規表現で特別な何かを意味する文字が含まれていないことを確信できません
Allanrbo

23
次のような特殊文字をエスケープする必要があります:string value = Regex.Replace( "%PolicyAmount%"、Regex.Escape( "%PolicyAmount%")、Regex.Escape( "$ 0")、RegexOptions.IgnoreCase);
Helge Klein

8
Regex.ReplaceでRegex.Escapeを使用するときは注意してください。渡された3つの文字列をすべてエスケープし、結果に対してRegex.Unescapeを呼び出す必要があります。
Holger Adam

4
msdnによると:「文字エスケープは正規表現パターンでは認識されますが、置換パターンでは認識されません。」(msdn.microsoft.com/en-us/library/4edbef7e.aspx
Bronek

1
文字列値= Regex.Replace( "%PolicyAmount%"、Regex.Escape( "%PolicyAmount%")、 "$ 0" .Replace( "$"、 "$$")、RegexOptions.IgnoreCase);を使用するのが最善です。置換はdolar標識のみを認識するためです。
Skorek 2017年

295

引数をとるオーバーロードがstring.Replace 必要なようStringComparisonです。そうではないので、次のようなことを試すことができます:

public static string ReplaceString(string str, string oldValue, string newValue, StringComparison comparison)
{
    StringBuilder sb = new StringBuilder();

    int previousIndex = 0;
    int index = str.IndexOf(oldValue, comparison);
    while (index != -1)
    {
        sb.Append(str.Substring(previousIndex, index - previousIndex));
        sb.Append(newValue);
        index += oldValue.Length;

        previousIndex = index;
        index = str.IndexOf(oldValue, index, comparison);
    }
    sb.Append(str.Substring(previousIndex));

    return sb.ToString();
}

9
いいね。に変更ReplaceStringReplaceます。
AMissico 2010

41
上記のコメントに同意してください。これは、同じメソッド名を持つ拡張メソッドにすることができます。ただ、メソッドのシグネチャを持つ静的クラスでそれをポップ:公共の静的な文字列は、交換してください(この文字列str、文字列OLDVALUE、文字列newValueに、StringComparison比較)
マーク・ロビンソン

8
@Helge、一般的には問題ないかもしれませんが、ユーザーから任意の文字列を取得する必要があり、正規表現にとって意味のある入力のリスクを冒すことはできません。もちろん、ループを書いて、すべての文字の前にバックスラッシュを置くことができると思います...その時点で、私は上記のことも行うかもしれません(IMHO)。
ジム

9
これを単体テストしているときに、いつでも戻らないケースに遭遇しましたoldValue == newValue == ""
イシュマエル2013年

10
これはバギーです。ReplaceString("œ", "oe", "", StringComparison.InvariantCulture)投げるArgumentOutOfRangeException
Michael Liu

45

質問のタイトルが実際は質問されている特定の質問よりもはるかに大きいために、解答の混乱を招くグループのようなものがあります。読んだ後、ここですべての良いものを同化することから少し編集するだけで答えが得られるかどうかはわかりません。

ここで私がここで言及した落とし穴を回避し、最も広く適用可能なソリューションを提供すると思う拡張メソッドがあります。

public static string ReplaceCaseInsensitiveFind(this string str, string findMe,
    string newValue)
{
    return Regex.Replace(str,
        Regex.Escape(findMe),
        Regex.Replace(newValue, "\\$[0-9]+", @"$$$0"),
        RegexOptions.IgnoreCase);
}

そう...

残念ながら、3つすべてに対する@HAのコメントEscapeは正しくありません。初期値であるnewValue必要はありません。

注:ただし、「キャプチャされた値」マーカーのように見えるものの一部で$ある場合は、挿入する新しい値でs をエスケープする必要があります。したがって、Regex.Replace [sic]内のRegex.Replaceの3つのドル記号。それがなければ、このようなものが壊れます...

"This is HIS fork, hIs spoon, hissssssss knife.".ReplaceCaseInsensitiveFind("his", @"he$0r")

ここにエラーがあります:

An unhandled exception of type 'System.ArgumentException' occurred in System.dll

Additional information: parsing "The\hisr\ is\ he\HISr\ fork,\ he\hIsr\ spoon,\ he\hisrsssssss\ knife\." - Unrecognized escape sequence \h.

正規表現に慣れているユーザーは、エラーを回避できるように感じますが、バイトスニッフィング文字列にはまだ部分的です(ただし、エンコーディングでSpolskyを読んだ後のみ)。重要なユースケースを対象としています。「安全でない正規表現」についてのクロックフォードを少し思い出させます。(幸運であれば)必要なものを許可する正規表現を頻繁に記述し$10ますが、十分に配慮していなかったため、意図せずに(たとえば、上記のnewValue正規表現で実際に有効な「キャプチャ値」文字列は許可されますか?) 。どちらの方法にも価値があり、さまざまな種類の意図しないエラーを助長します。多くの場合、複雑さを過小評価することは簡単です。

その奇妙な$エスケープ(そして、置換された値で期待していたRegex.Escapeようなキャプチャされた値のパターンをエスケープしなかった$0)は、しばらくの間私を怒らせました。プログラミングは難しい(c)1842


32

これが拡張メソッドです。どこにあるかわかりません。

public static class StringExtensions
{
    public static string Replace(this string originalString, string oldValue, string newValue, StringComparison comparisonType)
    {
        int startIndex = 0;
        while (true)
        {
            startIndex = originalString.IndexOf(oldValue, startIndex, comparisonType);
            if (startIndex == -1)
                break;

            originalString = originalString.Substring(0, startIndex) + newValue + originalString.Substring(startIndex + oldValue.Length);

            startIndex += newValue.Length;
        }

        return originalString;
    }

}

空/ null文字列のケースを処理する必要がある場合があります。
Vad

2
このソリューションの複数のエラー:1. originalString、oldValue、およびnewValueのnullを確認します。2. orginalStringを返さない(機能しない、単純型は参照で渡されない)が、最初にorginalValueの値を新しい文字列に割り当て、それを変更して返す。
RWC

31

最も簡単な方法は、.Netに付属していて.Net 1.0以降で使用されているReplaceメソッドを使用することです。

string res = Microsoft.VisualBasic.Strings.Replace(res, 
                                   "%PolicyAmount%", 
                                   "$0", 
                                   Compare: Microsoft.VisualBasic.CompareMethod.Text);

このメソッドを使用するには、Microsoft.VisualBasicアセンブリに参照を追加する必要があります。このアセンブリは.Netランタイムの標準部分であり、追加のダウンロードではなく、廃止としてマークされていません。


4
できます。Microsoft.VisualBasicアセンブリへの参照を追加する必要があります。
CleverPatrick 2013

奇妙なことに、この方法を使用すると問題が発生しました(行頭の文字が欠落しました)。ここで最も人気のある答えC. Dragon 76は期待どおりに機能しました。
ジェレミー・トンプソン、

1
これの問題は、置換が行われなくても新しい文字列を返すことです。string.replace()は同じ文字列へのポインタを返します。差し込み印刷のようなものを実行している場合、非効率になる可能性があります。
Brain2000 2015

4
Brain2000、あなたは間違っています。.NETのすべての文字列は不変です。
Der_Meister 2017

Der_Meister、あなたの言うことは正しいですが、Brain2000が言ったことを間違っているわけではありません。
Simon Hewitt

11
    /// <summary>
    /// A case insenstive replace function.
    /// </summary>
    /// <param name="originalString">The string to examine.(HayStack)</param>
    /// <param name="oldValue">The value to replace.(Needle)</param>
    /// <param name="newValue">The new value to be inserted</param>
    /// <returns>A string</returns>
    public static string CaseInsenstiveReplace(string originalString, string oldValue, string newValue)
    {
        Regex regEx = new Regex(oldValue,
           RegexOptions.IgnoreCase | RegexOptions.Multiline);
        return regEx.Replace(originalString, newValue);
    }

どちらが良い方法ですか?何stackoverflow.com/a/244933/206730?よりよい性能?
Kiquenet 2013年

8

cfedukeの回答に触発されて、私はこの関数を作成しました。この関数は、IndexOfを使用して文字列内の古い値を検索し、それを新しい値に置き換えます。数百万行を処理するSSISスクリプトでこれを使用しましたが、正規表現メソッドはこれよりもはるかに低速でした。

public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue)
{
    int prevPos = 0;
    string retval = str;
    // find the first occurence of oldValue
    int pos = retval.IndexOf(oldValue, StringComparison.InvariantCultureIgnoreCase);

    while (pos > -1)
    {
        // remove oldValue from the string
        retval = retval.Remove(pos, oldValue.Length);

        // insert newValue in it's place
        retval = retval.Insert(pos, newValue);

        // check if oldValue is found further down
        prevPos = pos + newValue.Length;
        pos = retval.IndexOf(oldValue, prevPos, StringComparison.InvariantCultureIgnoreCase);
    }

    return retval;
}

不要なときに正規表現を使用しない場合の+1。もちろん、数行のコードを使用しますが、$機能が必要でない限り、正規表現ベースの置換よりもはるかに効率的です。
ChrisG 2016年

6

拡大C.ドラゴン76オーバーロードデフォルトその拡張子に彼のコードを作成することでの人気答えReplace方法。

public static class StringExtensions
{
    public static string Replace(this string str, string oldValue, string newValue, StringComparison comparison)
    {
        StringBuilder sb = new StringBuilder();

        int previousIndex = 0;
        int index = str.IndexOf(oldValue, comparison);
        while (index != -1)
        {
            sb.Append(str.Substring(previousIndex, index - previousIndex));
            sb.Append(newValue);
            index += oldValue.Length;

            previousIndex = index;
            index = str.IndexOf(oldValue, index, comparison);
        }
        sb.Append(str.Substring(previousIndex));
        return sb.ToString();
     }
}

3

Jeff Reddyの回答に基づいて、いくつかの最適化と検証を行います。

public static string Replace(string str, string oldValue, string newValue, StringComparison comparison)
{
    if (oldValue == null)
        throw new ArgumentNullException("oldValue");
    if (oldValue.Length == 0)
        throw new ArgumentException("String cannot be of zero length.", "oldValue");

    StringBuilder sb = null;

    int startIndex = 0;
    int foundIndex = str.IndexOf(oldValue, comparison);
    while (foundIndex != -1)
    {
        if (sb == null)
            sb = new StringBuilder(str.Length + (newValue != null ? Math.Max(0, 5 * (newValue.Length - oldValue.Length)) : 0));
        sb.Append(str, startIndex, foundIndex - startIndex);
        sb.Append(newValue);

        startIndex = foundIndex + oldValue.Length;
        foundIndex = str.IndexOf(oldValue, startIndex, comparison);
    }

    if (startIndex == 0)
        return str;
    sb.Append(str, startIndex, str.Length - startIndex);
    return sb.ToString();
}

2

C.ドラゴンのものに似たバージョンですが、1回の交換だけが必要な場合のために:

int n = myText.IndexOf(oldValue, System.StringComparison.InvariantCultureIgnoreCase);
if (n >= 0)
{
    myText = myText.Substring(0, n)
        + newValue
        + myText.Substring(n + oldValue.Length);
}

1

一致が文字列内の場所を含んでいることに多くの人が気づいていないので、Regex置換を実行する別のオプションを次に示します。

    public static string ReplaceCaseInsensative( this string s, string oldValue, string newValue ) {
        var sb = new StringBuilder(s);
        int offset = oldValue.Length - newValue.Length;
        int matchNo = 0;
        foreach (Match match in Regex.Matches(s, Regex.Escape(oldValue), RegexOptions.IgnoreCase))
        {
            sb.Remove(match.Index - (offset * matchNo), match.Length).Insert(match.Index - (offset * matchNo), newValue);
            matchNo++;
        }
        return sb.ToString();
    }

MatchNoを掛ける理由を説明していただけますか?
Aheho 2014

oldValueとnewValueの長さが異なる場合、値を置き換えると文字列は長くなったり短くなったりします。match.Indexは文字列内の元の位置を参照しているため、置換による位置の移動に合わせて調整する必要があります。別のアプローチは、右から左に削除/挿入を実行することです。
ブランドン

わかった。これが「オフセット」変数の目的です。私が理解していないのは、なぜあなたがmatchNoを掛けているのかということです。私の直感は、文字列内の一致の場所は、以前の出現の実際の数とは関係がないことを教えてくれます。
Aheho 2014

気にしないで、私は今それを手に入れました。オフセットは、発生数に基づいてスケーリングする必要があります。置換を行う必要があるたびに2文字を失う場合は、removeメソッドのパラメーターを計算するときにそれを考慮する必要があります
Aheho

0
Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase);

3
これは機能しません。$はトークンにありません。strReplace With文字列にあります。
Aheho 2008年

9
そして、あなたはそれをそれに適応させることができないのですか?
Joel Coehoorn、2008年

18
このサイトは正解のリポジトリとなるはずです。ほぼ正しい答えではありません。
Aheho 2008年

0

正規表現メソッドが機能するはずです。ただし、データベースからの文字列を小文字にし、%variables%を小文字にして、データベースからの小文字の文字列の位置と長さを特定することもできます。文字列内の位置は、小文字であっても変化しないことに注意してください。

次に、逆のループを使用します(そうしないと、後のポイントの移動先の実行カウントを維持する必要があります)、小文字の文字列からデータベースの%variables%を位置によって削除し、長さと置換値を挿入します。


逆に言うと、データベースからの文字列を逆にたどらずに、見つかった場所を最も遠いものから最も短いものへと逆に処理します。
cfeduke、2008年

できるか、または正規表現を使用するだけです:)
Ray

0

(誰もがこれを狙っているので)。これが私のバージョンです(nullチェック、正しい入力と置換のエスケープ付き)**インターネットや他のバージョンからインスピレーションを得ています:

using System;
using System.Text.RegularExpressions;

public static class MyExtensions {
    public static string ReplaceIgnoreCase(this string search, string find, string replace) {
        return Regex.Replace(search ?? "", Regex.Escape(find ?? ""), (replace ?? "").Replace("$", "$$"), RegexOptions.IgnoreCase);          
    }
}

使用法:

var result = "This is a test".ReplaceIgnoreCase("IS", "was");

0

私のケースを作ってみましょう。よろしければ、私を千切りにすることができます。

Regexはこの問題の答えではありません-比較的言えば、遅すぎてメモリが不足しています。

StringBuilderは文字列マングリングよりもはるかに優れています。

これはを補足する拡張メソッドになるため、string.Replaceその動作を一致させることが重要だと思います。したがって、同じ引数の問題に対して例外をスローすることは、置換が行われなかった場合に元の文字列を返すことと同様に重要です。

StringComparisonパラメータを持つことは良い考えではないと思います。私はそれを試してみましたが、マイケル・リューによって最初に言及されたテストケースは問題を示しました:-

[TestCase("œ", "oe", "", StringComparison.InvariantCultureIgnoreCase, Result = "")]

IndexOfは一致しますが、ソース文字列の一致の長さ(1)とoldValue.Length(2)の間に不一致があります。これは、oldValue.Lengthが現在の一致位置に追加されたときに他のいくつかのソリューションでIndexOutOfRangeが発生し、これを回避する方法が見つからなかったことによって明らかになりました。とにかく正規表現はケースに一致しないので、私はStringComparison.OrdinalIgnoreCase自分のソリューションにのみ使用するという実用的なソリューションを採用しました。

私のコードは他の回答と似ていますが、私の作成した問題は、を作成する前に一致を探すことStringBuilderです。何も見つからない場合は、潜在的に大きな割り当てが回避されます。その後、コードdo{...}whilewhile{...}

私は他のアンサーに対していくつかの広範なテストを行いましたが、これは部分的に速く出て、わずかに少ないメモリを使用しました。

    public static string ReplaceCaseInsensitive(this string str, string oldValue, string newValue)
    {
        if (str == null) throw new ArgumentNullException(nameof(str));
        if (oldValue == null) throw new ArgumentNullException(nameof(oldValue));
        if (oldValue.Length == 0) throw new ArgumentException("String cannot be of zero length.", nameof(oldValue));

        var position = str.IndexOf(oldValue, 0, StringComparison.OrdinalIgnoreCase);
        if (position == -1) return str;

        var sb = new StringBuilder(str.Length);

        var lastPosition = 0;

        do
        {
            sb.Append(str, lastPosition, position - lastPosition);

            sb.Append(newValue);

        } while ((position = str.IndexOf(oldValue, lastPosition = position + oldValue.Length, StringComparison.OrdinalIgnoreCase)) != -1);

        sb.Append(str, lastPosition, str.Length - lastPosition);

        return sb.ToString();
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.