C# - Como a localização afeta a manipulação de strings
Hoje veremos como a localização pode afetar a manipulação de strings na linguagem C#.

Muitas operações com strings podem se comportar de maneira diferente com base nas configurações de localidade que estão em uso.

Na plataforma .NET, todas as operações com strings baseadas na cultura usam a cultura atual que é definida  para a thread atual ( propriedade CurrentCulture de Thread.CurrentThread), a menos que uma cultura diferente seja especificada para um chamada específica.

Por padrão, o valor da propriedade CurrentCulture corresponde à região dos usuários e configurações de idioma no sistema operacional, que é o que o usuário normalmente espera.

O fato do comportamento depender das configurações do usuário tornam esses padrões inadequados para processamento interno de strings e comunicação entre diferentes componentes e sistemas.

Em tais cenários, é melhor que os resultados sejam sempre iguais. Isso pode ser alcançado alterando a propriedade CurrentCulture na thread ou especificando a cultura desejada para cada operação.

As operações mais óbvias afetadas pelas configurações de cultura são a formatação de datas e os valores numéricos. Assim, quando o método ToString for chamado em um valor sem uma cultura especificada, o valor de CurrentCulture será usado.

Vejamos a seguir alguns exemplos que foram feitos usando um projeto Console do tipo .NET Core no VS 2019 Community onde definimos os namespaces:

  • using System
  • using System.Globalization

Vamos iniciar com a formatação de datas:

         static void Main(string[] args)
        {
            Console.WriteLine($"O valor de CurrentCulture é {CultureInfo.CurrentCulture.Name}.");
            var data = new DateTime(2020, 9, 1);
            var ptbrData = data.ToString("d");
            Console.WriteLine($"{ptbrData}");
            var eslovenaCultura = CultureInfo.GetCultureInfo("sl-SI");
            Console.WriteLine($"Cultura Eslovênia = {eslovenaCultura}");
            var slsiData = data.ToString("d", eslovenaCultura); 
            Console.WriteLine($"{slsiData}");
            Console.ReadLine();
        }

Resultado:

Neste exemplo a cultura padrão é pt-Br e a seguir definimos uma nova cultura  eslovena (sl-SI) para obter a data em um formato diferente.

O mesmo ocorre com valores numéricos embora exista uma menor variedade em como os valores são formatados.

        private static void CultureNumeros()
        {
            Console.WriteLine($"O valor de CurrentCulture é {CultureInfo.CurrentCulture.Name}.");
            var pi = 3.14;
            var ptbrPi = pi.ToString(); // = "3,14
            Console.WriteLine($"{ptbrPi}");
            var enusCulture = CultureInfo.GetCultureInfo("en-US");
            Console.WriteLine($"Cultura Americana = {enusCulture}");
            var enusPi = pi.ToString(enusCulture); // = "3.14
            Console.WriteLine($"{enusPi}");
        }

Resultado:

Podemos ainda especificar uma cultura fixa quando o método string.Format for usado para formatação composta. A mesma cultura será usada formatar todos os valores no formato string definidos:

        static void Main(string[] args)
        {
            var eslovenaCulture = CultureInfo.GetCultureInfo("sl-SI");
            var temperatura = 21.5;
            var timestamp = new DateTime(2018, 9, 1, 16, 15, 30);
            var formatString = "Temperatura: {0} °C em {1} ";
            var formatopt_br = string.Format(formatString, temperatura, timestamp); 
            var formatosl_si = string.Format(eslovenaCulture, formatString ,temperatura, timestamp);
            Console.WriteLine($"cultura {CultureInfo.CurrentCulture.Name}");
            Console.WriteLine(formatopt_br);
            Console.WriteLine($"cultura {eslovenaCulture}");
            Console.WriteLine(formatosl_si);
            Console.ReadLine();
        }

A comparação de strings também é afetada pela localização. No exemplo abaixo vamos comparar a string eszett com um ss duplo na cultura atual e a seguir no Alemão. usando a comparação ordinal e cultural:

       static void Main(string[] args)
        {
            Console.WriteLine($"Cultura atual = {CultureInfo.CurrentCulture.Name}.");
            var doubleS = "ss";
            var eszett = "ß";   //O eszett representa o fonema s no Alemão
            var equalOrdinal = string.Equals(doubleS, eszett); // = false
            Console.WriteLine($"{equalOrdinal}");
            // Mudando a cultura atual para Alemão
            CultureInfo.CurrentCulture = new CultureInfo("de-DE", false);
            Console.WriteLine($"Cultura atual = {CultureInfo.CurrentCulture.Name}.");
            var equalCulture = string.Equals(doubleS, eszett, StringComparison.CurrentCulture); // = true
            Console.WriteLine($"{equalCulture}");
            Console.ReadLine();
        }

Os resultados da comparação de strings não mudam apenas com base na cultura. Existem duas configurações importantes adicionais:

  • Distinção entre maiúsculas e minúsculas: os caracteres maiúsculos e minúsculos são tratados da mesma forma ou não
  • Ordinal ou cultural: a comparação ordinal é baseada apenas na representação de bytes dos caracteres, , enquanto a comparação baseada na cultura segue regras linguísticas;

A comparação padrão de strings no .NET Framework é ordinal e diferencia maiúsculas de minúsculas. É por isso que as dois maneiras diferentes, mas equivalentes, de escrever o mesmo caractere em alemão, são tratadas como
strings diferentes.

Quando as strings são comparadas de acordo com a cultura, a regra linguística  é considerada e as strings são tratadas como iguais.

A comparação ordinal de cadeias sem distinção entre maiúsculas e minúsculas é um caso especial que merece
explicação.

Quando cadeias são comparadas de acordo com esta regra, elas são convertidas internamente para maiúsculas, usando a cultura invariável (uma cultura especial baseada no idioma inglês sem especificações regionais). Eles são comparados com base na representação de byte, como no caso da comparação ordinal padrão que diferencia maiúsculas de minúsculas.

As configurações de comparação de strings também são importantes ao classificar strings.

As coleções na biblioteca de classes base do .NET Framework tem uma opção para especificar as regras de classificação a serem usadas. Com base nessa configuração, as strings serão classificadas de maneira diferente na coleção conforme mostra o exemplo a seguir:

        static void Main(string[] args)
        {
            var czCulture = CultureInfo.GetCultureInfo("cs-CZ");            
            var palavras = new[] { "channel", "double" };
            var ordinalSortedSet = new SortedSet<string>(palavras);
            Console.WriteLine(">>ordinal");
            foreach (var palavra in ordinalSortedSet)
            {
                Console.WriteLine(palavra);
            }
            var cultureSortedSet = new SortedSet<string>(palavras,StringComparer.Create(czCulture, ignoreCase: true));
            Console.WriteLine($"Cultura = {czCulture}");
            Console.WriteLine(">>ignoreCase");
            foreach (var palavra in cultureSortedSet)
            {
                Console.WriteLine(palavra);
            }
             Console.ReadLine();
        }

Resultado:

Como usei o código de idioma tcheco para a segunda coleção, a ordem das duas strings foi invertida.

Isso acontece porque o idioma tcheco trata a sequência "ch" como um letra diferente de C. Ela está posicionada próximo à letra H, ou seja, várias letras após a letra D, portanto, é considerada "maior que" D.

E estamos conversados.

Pegue o projeto aqui: CShp_CultureStrings.zip

(Porque a vida foi manifestada, e nós a vimos, e testificamos dela, e vos anunciamos a vida eterna, que estava com o Pai, e nos foi manifestada);
1 João 1:2

Referências:


José Carlos Macoratti