Existe uma alternativa para string.Replace que é insensível a maiúsculas e minúsculas?

Eu preciso pesquisar uma seqüência de caracteres e replace todas as ocorrências de %FirstName% e %PolicyAmount% com um valor extraído de um database. O problema é que a capitalização do FirstName varia. Isso me impede de usar o método String.Replace() . Eu vi páginas da web sobre o assunto que sugerem

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

No entanto, por algum motivo, quando tento replace %PolicyAmount% por $0 , a substituição nunca ocorre. Eu suponho que tem algo a ver com o cifrão sendo um personagem reservado no regex.

Existe outro método que eu possa usar que não envolva sanear a input para lidar com caracteres especiais de regex?

Do MSDN
$ 0 – “Substitui a última substring correspondida pelo número do grupo (decimal).”

No .NET, o grupo de expressões regulares 0 é sempre a correspondência inteira. Por um literal $ você precisa

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

Parece que string.Replace deve ter uma sobrecarga que leva um argumento StringComparison . Como isso não acontece, você pode tentar algo assim:

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

Um tipo de grupo confuso de respostas, em parte porque o título da pergunta é realmente muito maior do que a pergunta específica que está sendo feita. Depois de ler, eu não tenho certeza se alguma resposta está a algumas edições de assimilar todas as coisas boas aqui, então eu imaginei tentar resumir.

Aqui está um método de extensão que eu acho que evita as armadilhas mencionadas aqui e fornece a solução mais amplamente aplicável.

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

Assim…

  • Este é um método de extensão @MarkRobinson
  • Isso não tenta pular Regex @Helge (você realmente tem que fazer byte-by-byte se você quiser string sniff assim fora do Regex)
  • Passa o excelente caso de teste de "œ".ReplaceCaseInsensitiveFind("oe", "") , "œ".ReplaceCaseInsensitiveFind("oe", "") , embora ele possa ter tido um comportamento ligeiramente diferente em mente.

Infelizmente, o comentário da @HA de que você deve Escape todos os três não está correto . O valor inicial e o newValue não precisam ser.

Nota: Você precisa, no entanto, escaping do $ s no novo valor que está inserindo se fizer parte do que parece ser um marcador de “valor capturado” . Assim, os três cifrões no Regex. Substituir dentro do Regex.Replace [sic]. Sem isso, algo assim quebra …

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

Aqui está o erro:

 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. 

Eu sei que as pessoas que estão confortáveis ​​com o Regex sentem que o seu uso evita erros, mas eu ainda sou parcial para byte sniffing strings (mas só depois de ter lido o Spolsky nas codificações ) para ter certeza absoluta de que você está recebendo o que você destinados a casos de uso importantes. Lembra-me de Crockford sobre ” expressões regulares inseguras ” um pouco. Demasiadas vezes escrevemos expressões regulares que permitem o que queremos (se tivermos sorte), mas involuntariamente permitimos mais (por exemplo, é $10 realmente uma string de “valor de captura” válida no meu newExalue regexp, acima?) Porque não fomos atenciosos suficiente. Ambos os methods têm valor e ambos incentivam diferentes tipos de erros não intencionais. Muitas vezes é fácil subestimar a complexidade.

Aquele $ estranho escapando (e que o Regex.Escape não escapou de padrões de valor capturados como $0 como eu esperaria em valores de reposição) me deixou louco por um tempo. A programação é difícil (c) 1842

Aqui está um método de extensão. Não tenho certeza onde eu encontrei.

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

Parece que o método mais fácil é simplesmente usar o método Replace que vem com .Net e existe desde o .Net 1.0:

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

Para usar esse método, você deve adicionar uma referência ao Microsoft.VisualBasic assemblly. Esse assembly é uma parte padrão do tempo de execução do .Net, não é um download extra ou está marcado como obsoleto.

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

Inspirado pela resposta do cfeduke, eu fiz essa function que usa IndexOf para encontrar o valor antigo na string e depois substituí-lo pelo novo valor. Eu usei isso em um script SSIS processando milhões de linhas, e o método regex foi muito mais lento do que isso.

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

Expandindo a resposta popular de C. Dragon 76 , transformando seu código em uma extensão que sobrecarrega o método Replace padrão.

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

Baseado na resposta de Jeff Reddy, com algumas otimizações e validações:

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

uma versão semelhante à do C. Dragon, mas se você precisar apenas de uma única substituição:

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

Aqui está outra opção para executar substituições Regex, já que muitas pessoas não parecem notar que as correspondências contêm o local dentro da string:

  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(); } 
 Regex.Replace(strInput, strToken.Replace("$", "[$]"), strReplaceWith, RegexOptions.IgnoreCase); 

O método de expressão regular deve funcionar. No entanto, o que você também pode fazer é minúscula a string do database, em minúsculas as% variables% que você tem e, em seguida, localize as posições e comprimentos na seqüência de maiúsculas inferior do database. Lembre-se, as posições em uma string não mudam apenas porque ela é menor.

Em seguida, usando um loop que vai no sentido inverso (é mais fácil, se você não tiver que manter uma contagem de onde os pontos posteriores se movem) remover da string não-inferior do database as variables% por sua posição e comprimento e insira os valores de substituição.

(Desde que todo mundo está tomando um tiro nisso). Aqui está a minha versão (com cheques nulos e input correta e substituição de escape) ** Inspirado em torno da internet e outras versões:

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

Uso:

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

Deixe-me fazer o meu caso e então você pode me despedaçar se quiser.

Regex não é a resposta para este problema – muito lento e com fome de memory, relativamente falando.

O StringBuilder é muito melhor que o processamento de strings.

Como este será um método de extensão para complementar a string.Replace . string.Replace , acredito que é importante combinar como isso funciona – portanto, lançar exceções para os mesmos problemas de argumento é importante, pois está retornando a string original se uma substituição não foi feita.

Eu acredito que ter um parâmetro StringComparison não é uma boa ideia. Eu tentei, mas o caso de teste originalmente mencionado por Michael-liu mostrou um problema:

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

Enquanto IndexOf irá corresponder, há uma incompatibilidade entre o comprimento da correspondência na string de origem (1) e oldValue.Length (2). Isso se manifestou causando IndexOutOfRange em algumas outras soluções quando oldValue.Length foi adicionado à posição de correspondência atual e não consegui encontrar uma maneira de contornar isso. Regex não combina com o case, então eu peguei a solução pragmática de usar StringComparison.OrdinalIgnoreCase apenas para minha solução.

Meu código é semelhante a outras respostas, mas minha diferença é que procuro uma correspondência antes de me dar ao trabalho de criar um StringBuilder . Se nenhum for encontrado, uma alocação potencialmente grande será evitada. O código então torna-se um do{...}while vez de um while{...}

Eu fiz alguns testes extensivos contra outras respostas e isso saiu um pouco mais rápido e usou um pouco menos de memory.

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