Dize için bir alternatif var mı?


306

Bir dize aramak ve tüm oluşumları %FirstName%ve %PolicyAmount%bir veritabanından çekilen bir değer ile değiştirmek gerekiyor . Sorun FirstName büyük harf değişiyor olmasıdır. Bu String.Replace()yöntemi kullanmamı engelliyor . Konuyla ilgili şu web sayfalarını gördüm:

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

Ancak ben denemek ve değiştirme nedense %PolicyAmount%ile $0, yedek gerçekleşir asla. Dolar işareti regex ayrılmış bir karakter olması ile ilgili bir şey olduğunu varsayalım.

Regex özel karakterlerle başa çıkmak için girdiyi sterilize etmeyi içermeyen başka bir yöntem var mı?


1
Eğer girilen değişken "$ 0" ise normal ifadeyi hiç etkilemez.
cfeduke

Yanıtlar:


132

MSDN
$ 0 - "Son alt dizeyi grup numarası numarasıyla (ondalık) eşleştirir."

.NET Normal ifadelerinde grup 0 her zaman tüm eşleşmedir. Gerçek bir $ için yapmanız gerekenler

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

16
bu özel durumda bu iyi, ama dizeleri dışarıdan girdi olduğu durumlarda, kimse normal ifadelerde hangi ortalama şey özel karakterler içermeyen emin olamaz
Allanrbo

23
Bunun gibi özel karakterlerden kaçmalısınız: string value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), Regex.Escape ("$ 0"), RegexOptions.IgnoreCase);
Helge Klein

8
Regex'te Regex.Escape kullanırken lütfen dikkat edin. Geçilen üç dizeden de kaçmanız ve sonuçta Regex.Unescape'i çağırmanız gerekecek!
Holger Adam

4
Msdn'ye göre: "Karakter kaçışları düzenli ifade örüntülerinde tanınır, ancak değiştirme örüntülerinde tanınır." ( msdn.microsoft.com/en-us/library/4edbef7e.aspx )
Bronek

1
Kullanmak en iyisidir: string value = Regex.Replace ("% PolicyAmount%", Regex.Escape ("% PolicyAmount%"), "$ 0" .Replace ("$", "$$"), RegexOptions.IgnoreCase); yerine sadece dolar işaretleri tanır.
Skorek

295

Tartışmayı gerektiren bir aşırı yüklenme string.Replace olmalı gibi görünüyor StringComparison. Olmadığı için böyle bir şey deneyebilirsiniz:

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
Güzel. Ben değiştirecek ReplaceStringkadar Replace.
AMissico

41
Yukarıdaki yorumlara katılıyorum. Bu, aynı yöntem adına sahip bir genişletme yöntemine dönüştürülebilir. Sadece yöntem imzası ile statik bir sınıfta pop: public static string Replace (bu String str, string oldValue, string newValue, StringComparison karşılaştırması)
Mark Robinson 16

8
@Helge, genel olarak, bu iyi olabilir, ama ben kullanıcıdan rastgele dizeleri almak zorunda ve giriş regex için anlamlı olma riski olamaz. Tabii ki, sanırım bir döngü yazabilir ve her karakterin önüne ters eğik çizgi koyabilirim ... Bu noktada, yukarıdakileri de yapabilirim (IMHO).
Jim

9
Üniteyi test ederken asla geri dönmeyeceği bir durumla karşılaştım oldValue == newValue == "".
Ishmael

10
Bu adamcağız; ReplaceString("œ", "oe", "", StringComparison.InvariantCulture)atar ArgumentOutOfRangeException.
Michael Liu

45

Kısmen kafa karıştırıcı bir cevap grubu, çünkü sorunun başlığı aslında sorulan sorudan çok daha büyük. Okuduktan sonra, herhangi bir cevabın, tüm iyi şeyleri özümsemekten birkaç düzenleme uzak olduğundan emin değilim, bu yüzden toplamayı deneyeceğim.

Burada bahsettiğim tuzaklardan kaçınan ve en geniş kapsamlı çözümü sunan bir uzantı yöntemi.

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);
}

Yani...

  • Bu @MarkRobinson bir uzantı yöntemidir
  • Bu , Regex @Helge'i atlamaya çalışmaz (Regex'in dışında böyle koklamayı dizmek istiyorsanız gerçekten byte by-byte yapmanız gerekir)
  • @MichaelLiu bireyin Geçirdi mükemmel test durumda , "œ".ReplaceCaseInsensitiveFind("oe", "")o aklında biraz daha farklı bir davranış olmuş olabilir gerçi.

Ne yazık ki, @HA'nın üçüne de sahip olduğunuz yorumu Escapedoğru değil . Başlangıç ​​değeri ve newValueolması gerekmez.

Not: Ancak, "yakalanan değer" işaretinin bir parçasıysa,$ eklediğiniz yeni değerden kaçmak zorundasınız . Böylece üç dolar işareti Regex.Regex.Replace [sic] içine yerleştirin. Bu olmadan, böyle bir şey kırılır ...

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

İşte hata:

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.

Ne söyleyeyim, Regex ile rahat olan insanların kullanımlarının hatalardan kaçındığını hissediyorum, ancak genellikle koklama dizelerini bayt etmek için hala kısmi oluyorum (ancak yalnızca kodlamalarda Spolsky'yi okuduktan sonra ) önemli kullanım durumları için tasarlanmıştır. Bana biraz güvensiz düzenli ifadeler Crockford hatırlatıyor . Çok sık istediğimiz şeye izin veren (şanslıysak) $10regexps yazıyoruz , ancak kasıtlı olarak daha fazlasına izin veriyoruz (örn., NewValue regexp'imde gerçekten geçerli bir "yakalama değeri" dizesi mi?), Çünkü yeterince düşünmedik . Her iki yöntemin de değeri vardır ve her ikisi de farklı türdeki istenmeyen hataları teşvik eder. Karmaşıklığı hafife almak genellikle kolaydır.

Bu tuhaf $kaçış (ve bu , yerine koyma değerlerinde beklediğim Regex.Escapegibi yakalanan değer kalıplarından kaçmadı $0) beni bir süre deli etti. Programlama Zor (c) 1842


32

İşte bir uzantı yöntemi. Nerede bulduğumdan emin değilim.

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;
    }

}

Boş / null dize durumlarını işlemeniz gerekebilir.
Vad

2
Bu çözümde çoklu hatalar: 1. originalString, oldValue ve newValue değerlerini null için kontrol edin. 2. orginalString'i geri vermeyin (çalışmaz, basit türler referans olarak iletilmez), ancak önce yeni bir dizeye orginalValue değerini atayın ve değiştirin ve geri verin.
RWC

31

En kolay yöntem basitçe .Net ile gelen ve .Net 1.0'dan beri var olan Değiştir yöntemini kullanmak gibi görünüyor:

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

Bu yöntemi kullanmak için, Microsoft.VisualBasic derlemesine bir başvuru eklemeniz gerekir. Bu derleme .Net çalışma zamanının standart bir parçasıdır, fazladan bir indirme değildir veya kullanılmaz olarak işaretlenmiştir.


4
İşe yarıyor. Microsoft.VisualBasic derlemesine bir başvuru eklemeniz gerekir.
CleverPatrick

Kullandığımda bu yöntemin bazı sorunları olduğunu garipleştirdi (satırın başındaki karakterler kayboldu). Burada en popüler cevap C. Dragon 76beklendiği gibi çalıştı.
Jeremy Thompson

1
Bununla ilgili sorun, string.replace () öğesinin aynı dizeye bir işaretçi döndürdüğü bir değiştirme yapılmasa bile YENİ bir dize döndürmesidir. Form mektup birleştirme gibi bir şey yapıyorsanız verimsiz olabilir.
Brain2000

4
Brain2000, yanılıyorsun. .NET'teki tüm dizeler değiştirilemez.
Der_Meister

Der_Meister, söyledikleriniz doğru olsa da, Brain2000'in söylediklerini yanlış yapmaz.
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);
    }

Hangisi daha iyi bir yol? stackoverflow.com/a/244933/206730 hakkında ne var ? daha iyi performans?
Kiquenet

8

Cfeduke'nin cevabından esinlenerek, dizedeki eski değeri bulmak için IndexOf'u kullanan ve daha sonra yeni değerle değiştiren bu işlevi yaptım. Milyonlarca satır işleme bir SSIS komut dosyasında kullandım ve regex yöntemi bundan çok daha yavaştı.

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;
}

Gerekli olmadığında regex kullanmadığınız için +1. Elbette, birkaç satır daha kullanıyorsunuz, ancak $ işlevselliğine ihtiyacınız olmadıkça normal ifade tabanlı değiştirmeden çok daha verimli.
ChrisG

6

Üzerine Genişleyen C. Dragon'un 76 bir uzantısı aşırı varsayılan içine onun kodunu yaparak 'nin sevilen cevap Replaceyöntemi.

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'in cevabına dayanarak, bazı optimizasyonlar ve onaylamalar ile:

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. Dragon's'a benzer bir sürüm, ancak yalnızca tek bir yedeklemeye ihtiyacınız varsa:

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

1

Regex değişikliklerini yürütmek için başka bir seçenek de var, çünkü pek çok kişi eşleşmelerin dize içindeki konumu içerdiğini fark ediyor gibi görünmüyor:

    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();
    }

Neden MatchNo ile çarptığınızı açıklayabilir misiniz?
Aheho

OldValue ve newValue arasında uzunluk farkı varsa, değerleri değiştirdikçe dize uzar veya kısalır. İndeks, dize içindeki orijinal konumu ifade eder, değiştirmemiz nedeniyle bu konumların hareketini ayarlamamız gerekir. Başka bir yaklaşım, Kaldır / Ekle'yi sağdan sola yürütmek olacaktır.
Brandon

Anladım. "Ofset" değişkeninin amacı budur. Anlamadığım şey neden matchNo ile çarptığınız. Sezgim, bir dizenin içindeki bir eşleşmenin konumunun, önceki olayların gerçek sayısı ile hiçbir ilişkisi olmadığını söyler.
Aheho

Boş ver, şimdi anladım. Uzaklığın, meydana gelen # sayıya göre ölçeklendirilmesi gerekir. Her değiştirme yaptığınızda 2 karakter kaybediyorsanız, kaldırma yöntemine parametreleri hesaplarken bunu dikkate almanız gerekir
Aheho

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

3
Bu işe yaramıyor. $ Belirtecinde değil. Bu string ile strReplace.
Aheho

9
Ve bunun için uyarlayamazsın?
Joel Coehoorn

18
Bu sitenin doğru cevaplar için bir depo olması gerekiyor. Neredeyse doğru cevaplar değil.
Aheho

0

Normal ifade yöntemi çalışmalıdır. Bununla birlikte, veritabanından gelen dizeyi küçük harf, sahip olduğunuz% değişkenlerin% yüzdesini küçük harfle yapmak ve sonra veritabanından alt kasalı dizgideki konumları ve uzunlukları bulmaktır. Unutmayın, bir dizgideki konumlar sadece alt kasası nedeniyle değişmez.

Daha sonra ters giden bir döngü kullanarak (eğer daha kolay, eğer daha sonraki noktaların nereye taşınacağı konusunda çalışan bir sayı tutmak zorunda kalacaksınız) alt kasa olmayan dizenizden% değişkenleri% konumlarından veritabanından kaldırın ve uzunluk ve değiştirme değerleri girin.


Tersine, bulduğum yerleri dizeden en arkadan en kısaa doğru tersine işlemek, veritabanından dizeyi tersine çevirmek değil.
cfeduke

Regex'i kullanabilirsiniz ya da sadece kullanabilirsiniz :)
Ray

0

(Çünkü herkes buna ateş ediyor). İşte benim sürümüm (boş denetimler, doğru giriş ve değiştirme çıkışıyla) ** İnternet ve diğer sürümlerden esinlenildi:

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);          
    }
}

Kullanımı:

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

0

Davamı yapmama izin verin ve sonra beni dilerseniz parçalara ayırabilirsiniz.

Regex bu sorunun cevabı değildir - nispeten yavaş ve çok aç ve hafıza aç.

StringBuilder, string mangling'den çok daha iyidir.

Bu, ek bir uzantı yöntemi olacağından string.Replace, bunun nasıl çalıştığını eşleştirmenin önemli olduğuna inanıyorum - bu nedenle, aynı argüman sorunları için istisnalar atmak, bir değiştirme yapılmadıysa orijinal dizeyi döndürmek kadar önemlidir.

Bir StringComparison parametresi olması iyi bir fikir olmadığına inanıyorum. Ben denedim ama michael-liu tarafından belirtilen test durumda bir sorun gösterdi: -

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

IndexOf eşleşirken, kaynak dizgideki (1) eşleşmenin uzunluğu ile oldValue.Length (2) arasında bir uyumsuzluk vardır. Bu, oldValue.Length geçerli eşleme konumuna eklendiğinde bazı diğer çözümlerde IndexOutOfRange'a neden olarak kendini gösterdi ve bunun etrafında bir yol bulamadım. Regex yine de davaya uymuyor, bu yüzden sadece çözümüm için kullanmanın pragmatik çözümünü StringComparison.OrdinalIgnoreCasealdım.

Kodum diğer cevaplara benzer, ancak benim büküm bir oluşturma sorunu gitmeden önce bir maç aramak olduğunu StringBuilder. Hiçbiri bulunmazsa, potansiyel olarak büyük bir ayırmadan kaçınılır. Kod daha sonra do{...}whilebirwhile{...}

Diğer Yanıtlara karşı bazı kapsamlı testler yaptım ve bu fraksiyonel olarak daha hızlı çıktı ve biraz daha az bellek kullandı.

    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();
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.