c# - how - Caso insensível 'Contém(string)'




csharp contains string (16)

Existe uma maneira de fazer o seguinte retornar verdadeiro?

string title = "ASTRINGTOTEST";
title.Contains("string");

Não parece haver uma sobrecarga que me permita definir a sensibilidade do case. Atualmente, eu COMPREENDO os dois, mas isso é apenas bobagem (pelo qual estou me referindo aos problemas do i18n que vêm com maiúsculas e minúsculas).

ATUALIZAR
Esta questão é antiga e, desde então, percebi que pedi uma resposta simples para um tópico realmente vasto e difícil, se você gostaria de investigá-lo completamente.
Para a maioria dos casos, em bases de código inglesas mono-linguais, this resposta será suficiente. Estou suspeitando porque a maioria das pessoas que vem aqui se enquadram nesta categoria é a resposta mais popular.
This resposta, entretanto, traz à tona o problema inerente de não podermos comparar o texto a maiúsculas e minúsculas até que saibamos que ambos os textos são a mesma cultura e sabemos o que é essa cultura. Esta talvez seja uma resposta menos popular, mas acho que é mais correta e é por isso que a marquei como tal.


.NET Core 2.0+ apenas (a partir de agora)

O .NET Core tem um par de métodos para lidar com isso desde a versão 2.0:

  • String.Contains (Char, StringComparison )
  • String.Contains (String, StringComparison )

Exemplo:

"Test".Contains("test", System.StringComparison.CurrentCultureIgnoreCase);

Com o tempo, eles provavelmente entrarão no .NET Standard e, a partir daí, em todas as outras implementações da Base Class Library.


A classe StringExtension é o caminho a seguir. Combinei algumas das postagens acima para fornecer um exemplo de código completo:

public static class StringExtensions
{
    /// <summary>
    /// Allows case insensitive checks
    /// </summary>
    public static bool Contains(this string source, string toCheck, StringComparison comp)
    {
        return source.IndexOf(toCheck, comp) >= 0;
    }
}

Estas são as soluções mais fáceis.

  1. Por índice de

    string title = "STRING";
    
    if (title.IndexOf("string", 0, StringComparison.CurrentCultureIgnoreCase) != -1)
    {
        // contains 
    }
    
  2. Alterando o caso

    string title = "STRING";
    
    bool contains = title.ToLower().Contains("string")
    
  3. Por Regex

    Regex.IsMatch(title, "string", RegexOptions.IgnoreCase);
    

Eu sei que isso não é o C #, mas no quadro (VB.NET) já existe essa função

Dim str As String = "UPPERlower"
Dim b As Boolean = InStr(str, "UpperLower")

C # variante:

string myString = "Hello World";
bool contains = Microsoft.VisualBasic.Strings.InStr(myString, "world");

Isto é bastante similar ao outro exemplo aqui, mas eu decidi simplificar enum para bool, primário porque outras alternativas normalmente não são necessárias. Aqui está o meu exemplo:

public static class StringExtensions
{
    public static bool Contains(this string source, string toCheck, bool bCaseInsensitive )
    {
        return source.IndexOf(toCheck, bCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) >= 0;
    }
}

E o uso é algo como:

if( "main String substring".Contains("SUBSTRING", true) )
....

O método InStr da montagem VisualBasic é o melhor se você tiver uma preocupação com internacionalização (ou você pode reimplementá-lo). Olhando para ele, dotNeetPeek mostra que não só conta para maiúsculas e minúsculas, mas também para caracteres kana e full-versus half-width (principalmente relevantes para idiomas asiáticos, embora também existam versões de largura total do alfabeto romano ). Estou pulando alguns detalhes, mas confira o método privado InternalInStrText :

private static int InternalInStrText(int lStartPos, string sSrc, string sFind)
{
  int num = sSrc == null ? 0 : sSrc.Length;
  if (lStartPos > num || num == 0)
    return -1;
  if (sFind == null || sFind.Length == 0)
    return lStartPos;
  else
    return Utils.GetCultureInfo().CompareInfo.IndexOf(sSrc, sFind, lStartPos, CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth);
}

Para testar se o paragraph da string contém a word string (obrigado @QuarterMeister)

culture.CompareInfo.IndexOf(paragraph, word, CompareOptions.IgnoreCase) >= 0

Onde culture é a instância de CultureInfo descrevendo a linguagem na qual o texto está escrito.

Essa solução é transparente sobre a definição de insensibilidade a maiúsculas e minúsculas, que é dependente de idioma . Por exemplo, o idioma inglês usa os caracteres I e i para as versões maiúsculas e minúsculas da nona letra, enquanto a língua turca usa esses caracteres para a décima primeira e décima segunda letras de seu alfabeto de 29 letras. A versão em maiúsculas do 'i' é o caractere desconhecido 'İ'.

Assim, as strings tin e TIN são a mesma palavra em inglês , mas diferentes palavras em turco . Pelo que entendi, um significa "espírito" e o outro é uma palavra onomatopéia. (Turcos, por favor me corrijam se eu estiver errado, ou sugiram um exemplo melhor)

Para resumir, você só pode responder à pergunta "essas duas sequências são as mesmas, mas em casos diferentes", se você souber em que idioma o texto está . Se você não sabe, você terá que dar uma chutada. Dada a hegemonia do inglês no software, você provavelmente deveria recorrer ao CultureInfo.InvariantCulture , porque ele estará errado de formas familiares.


Por fim, uma operação genérica "contém" se resume a uma função como essa,

/// <summary>
/// Determines whether the source contains the sequence.
/// </summary>
/// <typeparam name="T">The type of the items in the sequences.</typeparam>
/// <param name="sourceEnumerator">The source enumerator.</param>
/// <param name="sequenceEnumerator">The sequence enumerator.</param>
/// <param name="equalityComparer">An equality comparer.</param>
/// <remarks>
/// An empty sequence will return <c>true</c>.
/// The sequence must support <see cref="IEnumerator.Reset"/>
/// if it does not begin the source.
/// </remarks>
/// <returns>
/// <c>true</c> if the source contains the sequence;
/// otherwise <c>false</c>.
/// </returns>
public static bool Contains<T>(
    IEnumerator<T> sourceEnumerator,
    IEnumerator<T> sequenceEnumerator,
    IEqualityComparer<T> equalityComparer)
{
    if (equalityComparer == null)
    {
        equalityComparer = EqualityComparer<T>.Default;
    }

    while (sequenceEnumerator.MoveNext())
    {
        if (sourceEnumerator.MoveNext())
        {
            if (!equalityComparer.Equals(
                sourceEnumerator.Current,
                sequenceEnumerator.Current))
            {
                sequenceEnumerator.Reset();
            }
        }
        else
        {
            return false;
        }
    }

    return true;
}

isso pode ser trivialmente envolto em uma versão de extensão aceitando IEnumerable como este,

public static bool Contains<T>(
        this IEnumerable<T> source,
        IEnumerable<T> sequence,
        IEqualityComparer<T> equalityComparer = null)
{
    if (sequence == null)
    {
        throw new ArgumentNullException("sequence");
    }

    using(var sequenceEnumerator = sequence.GetEnumerator())
    using(var sourceEnumerator = source.GetEnumerator())
    {
        return Contains(
            sourceEnumerator,
            sequenceEnumerator,
            equalityComparer);
    }
}

Agora, isso funcionará para a comparação ordinal de qualquer sequência, incluindo strings, já que a string implementa IEnumerable<char> ,

// The optional parameter ensures the generic overload is invoked
// not the string.Contains() implementation.
"testable".Contains("est", EqualityComparer<char>.Default)

No entanto, como sabemos, as strings não são genéricas, elas são especializadas. Existem dois fatores principais em jogo.

  1. A questão da "caixa", que por sua vez, tem vários casos de borda dependentes do idioma.
  2. A questão bastante envolvida de como um conjunto de "Elementos de Texto" (letras / números / símbolos, etc.) é representado por Pontos de Código Unicode e quais seqüências válidas de caracteres podem representar uma determinada string, os detalhes são expandidos these answers .

O efeito líquido é o mesmo. As strings que você pode afirmar são linguisticamente iguais podem ser validamente representadas por diferentes combinações de caracteres. E mais, as regras de validade mudam entre culturas.

Tudo isso leva a uma string especializada com base na implementação de "Contém" como esta.

using System.Globalization;

public static bool Contains(
         this string source,
         string value,
         CultureInfo culture = null,
         CompareOptions options = CompareOptions.None)
{
    if (value == null)
    {
        throw new ArgumentNullException("value");
    }

    var compareInfo = culture == null ? 
            CultureInfo.CurrentCulture.CompareInfo :
            culture.CompareInfo;

    var sourceEnumerator = StringInfo.GetTextElementEnumerator(source);
    var sequenceEnumerator = StringInfo.GetTextElementEnumerator(value);

    while (sequenceEnumerator.MoveNext())
    {
        if (sourceEnumerator.MoveNext())
        {
            if (!(compareInfo.Compare(
                    sourceEnumerator.Current,
                    sequenceEnumerator.Current,
                    options) == 0))
            {
                sequenceEnumerator.Reset();
            }
        }
        else
        {
            return false;
        }
    }

    return true;
}

Essa função pode ser usada para executar uma "diferenciação" específica de maiúsculas e minúsculas que funcionará, seja qual for a normalização das cadeias. por exemplo

"testable".Contains("EST", StringComparer.CurrentCultureIgnoreCase)

Solução alternativa usando o Regex:

bool contains = Regex.IsMatch("StRiNG to search", "string", RegexOptions.IgnoreCase);

Aviso prévio

Como o @cHao apontou em seu comentário, há cenários que farão com que esta solução retorne resultados incorretos. Certifique-se de que você sabe o que está fazendo antes de implementar essa solução ao acaso.


Um problema com a resposta é que ele lançará uma exceção se uma string for nula. Você pode adicionar isso como um cheque para que não:

public static bool Contains(this string source, string toCheck, StringComparison comp)
{
    if (string.IsNullOrEmpty(toCheck) || string.IsNullOrEmpty(source))
        return true;

    return source.IndexOf(toCheck, comp) >= 0;
} 

Usando um RegEx é uma maneira direta de fazer isso:

Regex.IsMatch(title, "string", RegexOptions.IgnoreCase);

Você pode usar IndexOf() assim:

string title = "STRING";

if (title.IndexOf("string", 0, StringComparison.CurrentCultureIgnoreCase) != -1)
{
    // The string exists in the original
}

Como 0 (zero) pode ser um índice, você verifica contra -1.

MSDN

A posição de índice baseada em zero do valor, se essa cadeia for encontrada, ou -1, se não for. Se o valor for String.Empty, o valor de retorno será 0.


Você poderia sempre apenas aumentar ou diminuir as strings primeiro.

string title = "string":
title.ToUpper().Contains("STRING")  // returns true

Oops, acabei de ver a última parte. Uma comparação insensível a maiúsculas * provavelmente * faria o mesmo, e se o desempenho não é um problema, não vejo problema em criar cópias em maiúsculas e compará-las. Eu poderia jurar que uma vez eu vi uma comparação insensível a maiúsculas uma vez ...


Você poderia usar o método String.IndexOf e passar StringComparison.OrdinalIgnoreCase como o tipo de pesquisa a ser usada:

string title = "STRING";
bool contains = title.IndexOf("string", StringComparison.OrdinalIgnoreCase) >= 0;

Melhor ainda é definir um novo método de extensão para string:

public static class StringExtensions
{
    public static bool Contains(this string source, string toCheck, StringComparison comp)
    {
        return source?.IndexOf(toCheck, comp) >= 0;
    }
}

Note que essa propagação nula ?. está disponível desde C # 6.0 (VS 2015), para versões mais antigas

if (source == null) return false;
return source.IndexOf(toCheck, comp) >= 0;

USO:

string title = "STRING";
bool contains = title.Contains("string", StringComparison.OrdinalIgnoreCase);

OrdinalIgnoreCase, CurrentCultureIgnoreCase ou InvariantCultureIgnoreCase?

Como isso está faltando, aqui estão algumas recomendações sobre quando usar qual:

Dos

  • Use StringComparison.OrdinalIgnoreCase para comparações como seu padrão seguro para correspondência de seqüência de caracteres agnóstica de cultura.
  • Use comparações StringComparison.OrdinalIgnoreCase para aumentar a velocidade.
  • Use operações de string StringComparison.CurrentCulture-based ao exibir a saída para o usuário.
  • Alterne o uso atual de operações de cadeia de caracteres com base na cultura invariável para usar o StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase não linguístico quando a comparação for
    linguisticamente irrelevante (simbólico, por exemplo).
  • Use ToUpperInvariant vez de ToLowerInvariant ao normalizar strings para comparação.

Não é

  • Use sobrecargas para operações de cadeia de caracteres que não especificam ou implicitamente especificam o mecanismo de comparação de cadeia de caracteres.
  • Use StringComparison.InvariantCulture -based
    operações na maioria dos casos; uma das poucas exceções seria
    persistindo dados lingüisticamente significativos, mas culturalmente agnósticos.

Com base nessas regras, você deve usar:

string title = "STRING";
if (title.IndexOf("string", 0, StringComparison.[YourDecision]) != -1)
{
    // The string exists in the original
}

enquanto [YourDecision] depende das recomendações acima.

link da fonte: http://msdn.microsoft.com/en-us/library/ms973919.aspx


if ("strcmpstring1".IndexOf(Convert.ToString("strcmpstring2"), StringComparison.CurrentCultureIgnoreCase) >= 0){return true;}else{return false;}




case-insensitive