Runtime: Quebrando a mudança com string.IndexOf (string) do .NET Core 3.0 -> .NET 5.0

Criado em 22 out. 2020  ·  76Comentários  ·  Fonte: dotnet/runtime

Descrição

Estou estendendo um pacote para oferecer suporte ao .NET 5.0 e encontrei uma alteração importante. Dado o aplicativo de console:

using System;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var actual = "Detail of supported commands\n============\n## Documentation produced for DelegateDecompiler, version 0.28.0 on Thursday, 22 October 2020 16:03\n\r\nThis file documents what linq commands **DelegateDecompiler** supports when\r\nworking with [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/) (EF).\r\nEF has one of the best implementations for converting Linq `IQueryable<>` commands into database\r\naccess commands, in EF's case T-SQL. Therefore it is a good candidate for using in our tests.\r\n\r\nThis documentation was produced by compaired direct EF Linq queries against the same query implemented\r\nas a DelegateDecompiler's `Computed` properties. This produces a Supported/Not Supported flag\r\non each command type tested. Tests are groups and ordered to try and make finding things\r\neasier.\r\n\r\nSo, if you want to use DelegateDecompiler and are not sure whether the linq command\r\nyou want to use will work then clone this project and write your own tests.\r\n(See [How to add a test](HowToAddMoreTests.md) documentation on how to do this). \r\nIf there is a problem then please fork the repository and add your own tests. \r\nThat will make it much easier to diagnose your issue.\r\n\r\n*Note: The test suite has only recently been set up and has only a handful of tests at the moment.\r\nMore will appear as we move forward.*\r\n\r\n\r\n### Group: Unit Test Group\n#### [My Unit Test1](../TestGroup01UnitTestGroup/Test01MyUnitTest1):\n- Supported\n  * Good1 (line 1)\n  * Good2 (line 2)\n\r\n#### [My Unit Test2](../TestGroup01UnitTestGroup/Test01MyUnitTest2):\n- Supported\n  * Good1 (line 1)\n  * Good2 (line 2)\n\r\n\r\n\nThe End\n";

            var expected = "\n#### [My Unit Test2](";

            Console.WriteLine($"actual.Contains(expected): {actual.Contains(expected)}");
            Console.WriteLine($"actual.IndexOf(expected): {actual.IndexOf(expected)}");
        }
    }
}

Obtenho resultados diferentes com base no tempo de execução do .NET Core 3.0 -> .NET 5.0:

.NET Core 3.0:

actual.Contains(expected): True
actual.IndexOf(expected): 1475

.NET 5.0:

actual.Contains(expected): True
actual.IndexOf(expected): -1

Configuração

Windows 10 Pro Build 19041 x64
.NET Core 3.1.9
.NET 5.0.0-rc.2.20475.5

Regressão?

Sim, funcionou por meio do .NET Core 3.1.9

area-System.Globalization question

Comentários muito úteis

@tarekgh , concordo que os resultados diferentes entre Contains e IndexOf não são o problema em si.

O problema é claramente IndexOf que não consegue encontrar uma string somente ASCII dentro de outra string somente ASCII (não tenho certeza se alguma vez houve qualquer comportamento dependente da localidade imposto a strings somente ASCII!).

Isso não é algo que eu esperaria de quaisquer alterações relacionadas ao local / NLS / ICU; na verdade, eu não conseguia pensar em nenhuma outra linguagem de programação / tempo de execução se comportando assim.

Aqui está um caso de teste simplificado, quebrado (quer dizer, me dando um resultado totalmente inesperado) no .NET 5 RC 2:

var actual = "\n\r\nTest";
var expected = "\nTest";

Console.WriteLine($"actual.IndexOf(expected): {actual.IndexOf(expected)}"); // => -1

Deveria realmente funcionar assim? Além disso, por quê? O que ele está tentando fazer na verdade?

Foi realmente uma mudança planejada?

Sim, mudar para a UTI é uma mudança intencional por diferentes motivos.

Sinto muito, mas eu não acredito que isso foi uma mudança planejada, então eu gostaria de enfatizar: Eu não poderia imaginar alguém _planning_ tal mudança. Tipo, o pessoal da equipe .NET se reuniu e discutiu:

A string "\ n \ r \ nTest" contém "\ nTest" com ICU habilitado? Não, claramente não!

E ninguém reclamou? Sem chance!

Isso não parece uma mudança planejada ou esperada e, em vez disso, parece um bug muito sério, um grande bloqueador de compatibilidade. Por causa disso, os aplicativos .NET novos e portados não funcionarão adequadamente no novo tempo de execução, porque não serão capazes de encontrar substrings dentro da string!

Por que a UTI se preocupa com as terminações de linha, afinal? Algumas localidades têm suas próprias terminações de linha específicas da localidade?

PS Sim, você poderia argumentar que realmente sempre se deve chamar alguma variante de IndexOf independente de cultura, como com o sinalizador ordinal. Mas, se você decidiu quebrá-lo _tão_ difícil no .NET 5, você não poderia simplesmente fazê-lo usar o padrão normal, então? Acho que interromperia menos aplicativos do que a mudança atual que estamos vendo no .NET 5 RC 2.

Além disso, acho que todos nós entendemos que, apesar de IndexOf sempre se comportar de uma maneira específica da cultura, existem _tons_ de código em liberdade que usam IndexOf sem os sinalizadores ordinais, e esse código _usado para trabalhar_ (em alguns / na maioria dos casos, pelo menos). E ele irá parar de funcionar após a atualização do .NET 5.

Todos 76 comentários

@tarekgh

Marcando assinantes nesta área: @tarekgh , @safern , @krwq
Veja informações em area-owners.md se você quiser se inscrever.

Isso ocorre por design, pois no .NET 5.0 trocamos o ICU em vez do NLS. Você pode consultar https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/globalization-icu para obter mais detalhes.

Você tem a opção de usar o switch de configuração System.Globalization.UseNls para voltar ao comportamento antigo, mas não recomendamos fazer isso porque o ICU é mais correto e avançar usando o ICU dará consistência entre os sistemas operacionais.

esqueci de dizer, se você deseja que IndexOf se comporte como Contains , você deve usar comparações ordinais nesse momento.

C# actual.IndexOf(expected, StringComparison.Ordinal)

Você tem a opção de usar o switch de configuração System.Globalization.UseNls para voltar ao comportamento antigo, mas não recomendamos fazer isso porque o ICU é mais correto e avançar usando o ICU dará consistência entre os sistemas operacionais.

Sim, se você executar este código no Unix que visa netcoreapp3.1 você verá o mesmo comportamento:

 santifdezm  DESKTOP-1J7TFMI  ~  experimental  indexof  $   /home/santifdezm/experimental/indexof/bin/Debug/netcoreapp3.1/linux-x64/publish/indexof
actual.Contains(expected): True
actual.IndexOf(expected): -1

e como @tarekgh com Ordinal retorna o resultado esperado.

 santifdezm  DESKTOP-1J7TFMI  ~  experimental  indexof  $  /home/santifdezm/experimental/indexof/bin/Debug/net5.0/linux-x64/publish/indexof
actual.Contains(expected): True
actual.IndexOf(expected): 1475

Acho que isso está falhando porque a mistura de \r\n e \n na string de origem. Se eu substituir todas as instâncias de \r\n por \n , funciona. O mesmo se aplica se eu fizer tudo \r\n . É apenas a mistura que está resultando em resultados diferentes da comparação da UTI.

O problema relatado no Twitter era que, no tempo de execução do 5.0, chamadas repetidas para string.IndexOf com as mesmas entradas davam resultados diferentes em cada chamada.

https://twitter.com/jbogard/status/1319381273585061890?s=21

Edit: Acima foi um mal-entendido.

@GrabYourPitchforks podemos atualizar o título do problema então? Como essa é uma mudança técnica, mas isso acontece no Windows e no Unix ... certo?

Eu chamei Jimmy offline para esclarecimentos. É possível que eu tenha entendido mal seu relatório de problema original. Os fóruns de 280 caracteres nem sempre são eficientes em comunicar bugs com clareza. ;)

Só para esclarecer, Contains API está executando uma operação ordinal. IndexOf sem nenhum sinalizador de comparação de string é uma operação linguística e não ordinal. Se quiser comparar o comportamento Contains com o IndexOf, é necessário usar IndexOf(expected, StringComparison.Ordinal) .
Se precisar saber mais sobre a diferença, https://docs.microsoft.com/en-us/dotnet/csharp/how-to/compare-strings é um link útil.

Recebi esclarecimentos no Twitter. O aplicativo não está chamando IndexOf em um loop. Este é apenas um relatório padrão de diferenças comportamentais 3.0 vs. 5.0.

@GrabYourPitchforks você poderia compartilhar o link https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/globalization-icu em suas respostas do Twitter e mencionar que temos um switch de configuração para voltar ao comportamento antigo?

Recebi esclarecimentos no Twitter. O aplicativo não está chamando IndexOf em um loop. Este é apenas um relatório padrão de diferenças comportamentais 3.0 vs. 5.0.

Obrigado, @GrabYourPitchforks ... com base nisso, fechando-o como intuito.

Para adicionar mais aqui, se você deseja obter o comportamento antigo sem voltar para NLS, você pode fazer

`` `C #
CultureInfo.CurrentCulture.CompareInfo.IndexOf (real, esperado, CompareOptions.IgnoreSymbols)


or 

```C#
    actual.IndexOf(expected, StringComparison.Ordinal)

ao invés de

C# actual.IndexOf(expected)

e você deve obter o comportamento desejado.

Não consigo ver nada sobre \r\n vs \n na documentação relacionada ao ICU vinculada (https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/ globalização-icu).

Foi realmente uma mudança planejada?

@ForNeVeR será difícil listar todas as diferenças entre ICU e NLS. o documento fala sobre a principal mudança da mudança para a UTI. Como mencionei antes, não é correto comparar os resultados de Contains com IndexOf sem os parâmetros StringComparison. Listei acima algumas maneiras pelas quais você pode obter o comportamento anterior, se desejar. A partir do relatório deste problema, parece-me que o uso do IndexOf deve usar a opção Ordinal e é incorreto usar as comparações linguísticas neste caso. O uso de comparação linguística em tal caso pode depender da cultura atual, que pode dar resultados diferentes em ambientes diferentes.

Foi realmente uma mudança planejada?

Sim, mudar para a UTI é uma mudança intencional por diferentes motivos. O Windows está promovendo o uso de ICU em vez de NLS. A UTI é o futuro de qualquer maneira. Além disso, o ICU dará a oportunidade de ter comportamentos consistentes no Windows / Linux / OSX ou em qualquer plataforma compatível. O uso do ICU dará aos aplicativos a oportunidade de personalizar o comportamento de globalização, se desejarem.

Conforme indicado no documento, você ainda tem a opção de voltar ao comportamento antigo, se desejar.

Ouch, o documento referenciado diz que o comportamento ICU / NLS no Windows pode mudar silenciosamente com base na disponibilidade de icu.dll no ambiente real. Isso pode ser uma grande surpresa para aplicativos independentes publicados. Eu esperaria que o .NET enviasse o ICU para contornar esse problema se a opção fosse decidida, e como o ICU não está disponível em todos os ambientes de destino. Essa dependência de tempo de execução opcional torna as coisas ainda mais engraçadas.

Eu esperaria que o .NET enviasse o ICU para contornar esse problema se a opção fosse decidida, e como o ICU não está disponível em todos os ambientes de destino. Essa dependência de tempo de execução opcional torna as coisas ainda mais engraçadas.

O ICU agora é publicado como pacote NuGet. Os aplicativos podem usar esses pacotes para que o aplicativo independente garanta o ICU. consulte a seção local do

@tarekgh , concordo que os resultados diferentes entre Contains e IndexOf não são o problema em si.

O problema é claramente IndexOf que não consegue encontrar uma string somente ASCII dentro de outra string somente ASCII (não tenho certeza se alguma vez houve qualquer comportamento dependente da localidade imposto a strings somente ASCII!).

Isso não é algo que eu esperaria de quaisquer alterações relacionadas ao local / NLS / ICU; na verdade, eu não conseguia pensar em nenhuma outra linguagem de programação / tempo de execução se comportando assim.

Aqui está um caso de teste simplificado, quebrado (quer dizer, me dando um resultado totalmente inesperado) no .NET 5 RC 2:

var actual = "\n\r\nTest";
var expected = "\nTest";

Console.WriteLine($"actual.IndexOf(expected): {actual.IndexOf(expected)}"); // => -1

Deveria realmente funcionar assim? Além disso, por quê? O que ele está tentando fazer na verdade?

Foi realmente uma mudança planejada?

Sim, mudar para a UTI é uma mudança intencional por diferentes motivos.

Sinto muito, mas eu não acredito que isso foi uma mudança planejada, então eu gostaria de enfatizar: Eu não poderia imaginar alguém _planning_ tal mudança. Tipo, o pessoal da equipe .NET se reuniu e discutiu:

A string "\ n \ r \ nTest" contém "\ nTest" com ICU habilitado? Não, claramente não!

E ninguém reclamou? Sem chance!

Isso não parece uma mudança planejada ou esperada e, em vez disso, parece um bug muito sério, um grande bloqueador de compatibilidade. Por causa disso, os aplicativos .NET novos e portados não funcionarão adequadamente no novo tempo de execução, porque não serão capazes de encontrar substrings dentro da string!

Por que a UTI se preocupa com as terminações de linha, afinal? Algumas localidades têm suas próprias terminações de linha específicas da localidade?

PS Sim, você poderia argumentar que realmente sempre se deve chamar alguma variante de IndexOf independente de cultura, como com o sinalizador ordinal. Mas, se você decidiu quebrá-lo _tão_ difícil no .NET 5, você não poderia simplesmente fazê-lo usar o padrão normal, então? Acho que interromperia menos aplicativos do que a mudança atual que estamos vendo no .NET 5 RC 2.

Além disso, acho que todos nós entendemos que, apesar de IndexOf sempre se comportar de uma maneira específica da cultura, existem _tons_ de código em liberdade que usam IndexOf sem os sinalizadores ordinais, e esse código _usado para trabalhar_ (em alguns / na maioria dos casos, pelo menos). E ele irá parar de funcionar após a atualização do .NET 5.

O problema é claramente IndexOf, que não consegue encontrar uma string somente ASCII dentro de outra string somente ASCII (não tenho certeza se alguma vez houve qualquer comportamento dependente da localidade imposto em strings somente ASCII!).

Não é verdade que o ASCII é independente do local. veja o link http://userguide.icu-project.org/collation/concepts como um exemplo de como o comportamento de caracteres ASCII pode ser diferente para diferentes culturas.

For example, in the traditional Spanish sorting order, "ch" is considered a single letter. All words that begin with "ch" sort after all other words beginning with "c", but before words starting with "d".
Other examples of contractions are "ch" in Czech, which sorts after "h", and "lj" and "nj" in Croatian and Latin Serbian, which sort after "l" and "n" respectively.

Além disso, quero esclarecer que o ICU escolhe seus dados e comportamento do padrão Unicode, que é bem pensado por muitos especialistas. @GrabYourPitchforks vai postar mais detalhes sobre \r\n\ caso que estamos falando aqui. mas enquanto isso, você pode se familiarizar com o documento https://unicode.org/reports/tr29/, especialmente nas seções que mencionam o seguinte:

Do not break between a CR and LF. Otherwise, break before and after controls.
--
GB3 | CR | × | LF
GB4 | (Control \| CR \| LF) | ÷ |  
GB5 |   | ÷ | (Control \| CR \| LF)

Por que a UTI se preocupa com as terminações de linha, afinal? Algumas localidades têm suas próprias terminações de linha específicas da localidade?

Isso é tratado no último parágrafo.

Sinto muito, mas não acredito que tenha sido uma mudança planejada, então gostaria de enfatizar: eu não poderia imaginar alguém planejando tal mudança. Tipo, o pessoal da equipe .NET se reuniu e discutiu:
Isso não parece uma mudança planejada ou esperada e, em vez disso, parece um bug muito sério, um grande bloqueador de compatibilidade. Por causa disso, os aplicativos .NET novos e portados não funcionarão adequadamente no novo tempo de execução, porque não serão capazes de encontrar substrings dentro da string!

Isso é bem planejado, trabalhado e pensado profundamente nisso. você pode olhar para o problema https://github.com/dotnet/runtime/issues/826 que publicamos há muito tempo e o compartilhamos publicamente.
Quero enfatizar também que o comportamento da globalização pode mudar a qualquer momento, não apenas para o .NET, mas para os sistemas operacionais e outras plataformas. É também por isso que oferecemos suporte ao recurso local do aplicativo ICU para permitir que os aplicativos usem uma versão específica do ICU para garantir que o comportamento que usam não seja alterado. Outra coisa, o próprio Windows está em processo de promoção do uso da UTI e um dia o comportamento da UTI será o que a maioria dos usuários vai usar.

como com a bandeira ordinal. Mas, se você decidiu quebrá-lo tanto no .NET 5, não poderia simplesmente fazê-lo usar o padrão normal ordinal? Acho que interromperia menos aplicativos do que a mudança atual que estamos vendo no .NET 5 RC 2.

Na verdade, já tentamos antes tornar o comportamento Ordinal o padrão durante os lançamentos do Silverlight e isso causou muito mais problemas do que o relatado aqui. Estamos procurando mais maneiras de ajudar os desenvolvedores a serem conscientes ao chamar algo como IndexOf e fornecer intencionalmente sinalizadores StringComparison para expressar a intenção. Agradecemos quaisquer ideias que você possa ter também.

Além disso, acho que todos nós entendemos que, apesar de IndexOf sempre se comportar de uma maneira específica da cultura, há toneladas de código que usam IndexOf sem os sinalizadores ordinais, e esse código costumava funcionar (em alguns / na maioria dos casos, finalmente). E ele irá parar de funcionar após a atualização do .NET 5.

É por isso que estamos fornecendo um switch de configuração para voltar ao comportamento antigo, se você desejar. olhe para System.Globalization.UseNls

@tarekgh , obrigado pela explicação completa!

Por enquanto, acho melhor esperar pelos detalhes desse comportamento específico de \r\n . Não está claro para mim como IndexOf usa as "Regras de limite de cluster Grapheme" (e se deve fazer isso) ao realizar esta pesquisa específica).

Além disso, mesmo que a especificação Unicode seja relevante aqui (o que pode muito bem ser!), Ao ler https://unicode.org/reports/tr29/ , não tenho certeza se ela proíbe corresponder aos últimos \n . Conforme li a especificação, dizia CR | × | LF , onde × significa "Sem limite (não permitir quebra aqui)". Então, ao quebrar uma sequência \r\n\n , só proíbe colocar a "quebra" entre o primeiro e o segundo caracteres, mas deve estar tudo bem colocar a "quebra" antes do terceiro, não? Então, eu li \r\n\n como dois "clusters de grafema" separados e, mesmo que IndexOf tenha que corresponder apenas a clusters de grafema completos e nunca tocar em partes dos clusters, ele ainda deve encontrar a substring \nTest dentro da string \n\r\nTest .

Além disso, você está dizendo que outros tempos de execução / linguagens de programação que dependem da especificação ICU e / ou Unicode devem se comportar da mesma forma neste exemplo específico?

Estamos procurando mais maneiras de ajudar os desenvolvedores a serem conscientes ao chamar algo como IndexOf e fornecer intencionalmente sinalizadores StringComparison para expressar a intenção. Agradecemos quaisquer ideias que você possa ter também.

_ (Uma isenção de responsabilidade necessária: Eu trabalho para JetBrains em vários projetos, incluindo ReSharper.) _

Inicialmente, eu não queria trazer esse ponto aqui para não soar como um anúncio, mas acho que isso é muito relevante, então terei de fazer isso. Por padrão, o ReSharper mostrará o seguinte aviso para o código do usuário que chama IndexOf :
image

_ (Por favor note que eu não estava ciente desta ReSharper especial de diagnóstico antes de ser envolvidos nesta discussão, então eu não estou participando aqui apenas para trazer esse argumento) _

Então, eu acho que seria uma ideia muito boa mostrar essa notificação por padrão em todas as outras ferramentas também, ou talvez até mesmo descontinuar totalmente esse método falso com esse aviso.

@Para nunca

Além disso, mesmo se a especificação Unicode for relevante aqui (o que pode muito bem ser!), Ao ler unicode.org/reports/tr29, não tenho certeza se ela proíbe a correspondência com a última \ n. Ao ler as especificações, está escrito CR | × | LF, onde × significa "Sem limite (não permitir quebra aqui)". Então, ao quebrar uma sequência \ r \ n \ n, só proíbe colocar a "quebra" entre o primeiro e o segundo caracteres, mas deve estar tudo bem colocar a "quebra" antes do terceiro, não? Então, eu li \ r \ n \ n como dois "grupos de grafemas" separados

Está correto. \r\n\n serão 2 clusters como \r\n e \n .

e, mesmo que o IndexOf tenha que corresponder apenas a clusters completos de grafemas e nunca tocar em partes dos clusters, ele ainda deve encontrar substring \ nTest dentro da string \ n \ r \ nTest.

Isso está incorreto. \n\r\nTest será dividido em partes. \n , \r\n e Test . é óbvio que \nTest não pode fazer parte desta string. pense em substituir o cluster \r\n por algum símbolo X . agora a string de origem será \nXTest que não contém \nTest .

Além disso, você está dizendo que outros tempos de execução / linguagens de programação que dependem da especificação ICU e / ou Unicode devem se comportar da mesma forma neste exemplo específico?

se estiver usando o nível de força de agrupamento padrão, a resposta é sim. A UTI pode permitir a alteração do nível de força que pode afetar o resultado. Por exemplo, como mencionei anteriormente, fazer algo como:

CultureInfo.CurrentCulture.CompareInfo.IndexOf(actual, expected, CompareOptions.IgnoreSymbols)

mudará o nível de força e fará com que a operação ignore os símbolos (o que mudará o comportamento de \n e \r , pois será ignorado naquele momento)

Além disso, escrevi um aplicativo C nativo ICU puro e executei o mesmo caso:

void SearchString(const char *target, int32_t targetLength, const char *source, int32_t sourceLength)
{
    static UChar usource[100];
    static UChar utarget[100];

    u_charsToUChars(source, usource, sourceLength);
    u_charsToUChars(target, utarget, targetLength);

    UErrorCode status = U_ZERO_ERROR;
    UStringSearch* pSearcher = usearch_open(utarget, targetLength, usource, sourceLength, "en_US", nullptr, &status);
    if (!U_SUCCESS(status))
    {
        printf("usearch_open failed with %d\n", status);
        return;
    }

    int32_t index = usearch_next(pSearcher, &status);
    if (!U_SUCCESS(status))
    {
        printf("usearch_next failed with %d\n", status);
        return;
    }

    printf("search result = %d\n", index);
    usearch_close(pSearcher);
}


int main()
{
    SearchString("\nT", 2, "\r\nT", 3);
    SearchString("\nT", 2, "\n\nT", 3);
}

este aplicativo produzirá os resultados:

search result = -1
search result = 1

que é idêntico ao comportamento que você está vendo com .NET.

Por enquanto, acho melhor esperar pelos detalhes sobre esse comportamento \ r \ n específico. Não está claro para mim como IndexOf usa "Grapheme Cluster Boundary Rules" (e se deve fazer isso) ao realizar esta pesquisa específica).

Com certeza o clustering está afetando a operação de agrupamento. Se você olhar http://unicode.org/reports/tr29/tr29-7.html , verá claramente o seguinte:

Grapheme clusters include, but are not limited to, combining character sequences such as (g + °), digraphs such as Slovak “ch”, and sequences with letter modifiers such as kw. Os limites do cluster de grapheme são importantes para o agrupamento , regular-expressions, and counting “character” positions within text. Word boundaries, line boundaries and sentence boundaries do not occur within a grapheme cluster. In this section, the Unicode Standard provides a determination of where the default grapheme boundaries fall in a string of characters. This algorithm can be tailored for specific locales or other customizations, which is what is done in providing contracting characters in collation tailoring tables.

Não tenho certeza se haverá mais detalhes, mas deixarei @GrabYourPitchforks comentar se ele tem mais a acrescentar aqui.

Então, eu acho que seria uma ideia muito boa mostrar essa notificação por padrão em todas as outras ferramentas também, ou talvez até mesmo descontinuar totalmente esse método falso com esse aviso.

Obrigado!
Esta é a mesma direção em que estamos pensando também.

Para meu próprio bem, comparei as várias sobrecargas entre as versões:

| Método | netcoreapp3.1 | net5.0 |
| ------------------------------------------------- --------------- | ----------------- | ---------- |
| actual.Contains(expected) | True | True |
| actual.IndexOf(expected) | 1475 | -1 |
| actual.Contains(expected, StringComparison.CurrentCulture) | True | False |
| actual.IndexOf(expected, StringComparison.CurrentCulture) | 1475 | -1 |
| actual.Contains(expected, StringComparison.Ordinal) | True | True |
| actual.IndexOf(expected, StringComparison.Ordinal) | 1475 | 1475 |
| actual.Contains(expected, StringComparison.InvariantCulture) | True | False |
| actual.IndexOf(expected, StringComparison.InvariantCulture) | 1475 | -1 |

Inclua um analisador para isso.

Esta parece ser uma daquelas mudanças que, embora boa a longo prazo, cria uma grande quantidade de rotatividade assim que o .NET 5 é iniciado. Portanto, se o comportamento desses métodos for diferente entre o .NET 5 e o .NET Core 3.1, o que acontecerá quando um .NET 5 chamar um objeto definido dentro de uma biblioteca .NET Standard 2.0 que manipula um string passado para ele do site de chamadas .NET? O antigo comportamento é usado ou o novo comportamento?

@Aaronontheweb novo comportamento. Eu vi isso inicialmente a partir de uma declaração em NUnit3, que tem netstandard2.0 alvo

Isso não é ótimo - não posso controlar o que as bibliotecas mais antigas que estou referenciando fazem se eu quiser atualizar.

Quantos aplicativos não detectarão isso nos testes de unidade e os colocarão em produção?
A equipe .NET considerou a dor, os problemas e os custos que isso poderia causar?

Isso não é ótimo - não posso controlar o que as bibliotecas mais antigas que estou referenciando fazem se eu quiser atualizar.

Armadilha legal!
feliz perda de tempo caçando insetos estranhos 🎉
mas por que InvariantGlobalization não ajuda com esse problema?

Inclua um analisador para isso.

Sim. E não se esqueça do suporte F #.

Quantos aplicativos não detectarão isso em testes de unidade

Zero - uma vez que os testes de unidade não pretendem testar bibliotecas / estruturas externas como o próprio .NET BCL.

Minha opinião é que deve haver um Atributo de nível de montagem que pode controlar o modo dessa mudança de comportamento. Pelo menos então, você pode optar por aceitar ou não em um nível por montagem. Isso significa que o problema do .NET Standard também desaparece.

Isso não é ótimo - não posso controlar o que as bibliotecas mais antigas que estou referenciando fazem se eu quiser atualizar.

Não vejo por que você não pode controlar isso. Todas as comparações de strings estão usando ICU ou NLS. Você pode cancelar o ICU usando o switch compat se desejar, e todas as suas bibliotecas serão revertidas para o comportamento antigo.

Você não pode confiar que os dados de globalização permanecerão estáveis ​​ao longo do tempo. Acredite na equipe do Windows que eles não têm medo de quebrar as pessoas que dependem de dados de globalização estáveis. As funções de globalização devem ser uma caixa preta; Não acho que faça sentido para as bibliotecas (especialmente aquelas voltadas para o .NET Standard) dizer que elas dependem de detalhes de implementação como este.

Tenho certeza de que muitas pessoas reclamaram sobre as funções de globalização do .NET retornando resultados diferentes no Windows vs. Linux (e talvez mais pessoas ainda não tenham notado). É melhor unificar o comportamento entre o Windows e outras plataformas, e qualquer código correto não deve depender de dados de globalização imutáveis, de qualquer maneira.

Você consideraria fazer uma alteração significativa para também tornar StringComparison.Ordinal a estratégia de comparação padrão? Visto que a globalização é tão instável, faz sentido que pelo menos a implementação padrão use um algoritmo estável. Estou disposto a apostar que 99,9% das pessoas que usam string.Equals(...) ou string.Contains(...) etc. sem passar StringComparison não estão fazendo isso com a intenção explícita de lidar com peculiaridades estranhas relacionadas a locales.

Edit: Acho que minha pergunta já foi respondida:

Na verdade, já tentamos antes tornar o comportamento Ordinal o padrão durante os lançamentos do Silverlight e isso causou muito mais problemas do que o relatado aqui. Estamos procurando outras maneiras de ajudar os desenvolvedores a serem conscientes ao chamar algo como IndexOf e fornecer intencionalmente sinalizadores StringComparison para expressar a intenção. Agradecemos quaisquer ideias que você possa ter também.

Você pode cancelar o ICU usando o switch compat se desejar, e todas as suas bibliotecas serão revertidas para o comportamento antigo.

Eu faço bibliotecas para viver, não aplicativos. Eu prefiro ter uma maneira de tempo de compilação para lidar com isso.

A maior parte do trabalho que fazemos é InvariantCulture , que pelo meu entendimento anterior é suposto ser imutável por design. Parece que o comportamento de IndexOf é diferente entre o .NET 5.0 e o .NET Core 3.1 nessas circunstâncias também.

Como qualquer analisador pode ajudar em projetos existentes?

@petarrepac também é algo

@isaacabraham e VB também;)

Eu prefiro ter uma maneira de tempo de compilação para lidar com isso.

@Aaronontheweb você tem uma maneira de tempo de compilação de lidar com isso (ao compilar aplicativos). Você pode adicionar isto ao seu projeto:

<ItemGroup>
  <RuntimeHostConfigurationOption Include="System.Globalization.UseNls" Value="true" />
</ItemGroup>

EDITAR: isso é apenas para aplicativos que consomem uma biblioteca, infelizmente você não pode controlar isso ao escrever uma biblioteca.

No longo prazo, a equipe do Windows está promovendo a mudança para o ICU, então em algum ponto o ICU será a história da globalização, e nós somos apenas um invólucro fino em torno das bibliotecas do sistema operacional.

o que vai acontecer quando um .NET 5 chamar um objeto definido dentro de uma biblioteca .NET Standard 2.0 que manipula uma string passada a partir do site de chamada .NET? O antigo comportamento é usado ou o novo comportamento?

O novo comportamento será utilizado, pois depende do tempo de execução e o .NET Standard é apenas um padrão para tempos de execução implementar. No entanto, observe que isso está trazendo consistência de plataforma entre Unix e Windows, se você executar essas mesmas bibliotecas que as pessoas têm dúvidas em Unix, você obterá o ICU resultado como ICU é o apoio biblioteca no Unix.

@reflectronic está certo em todos os seus pontos https://github.com/dotnet/runtime/issues/43736#issuecomment -716681586.

para comentar os resultados de @jbogard mencionados aqui https://github.com/dotnet/runtime/issues/43736#issuecomment -716527590, você pode apenas resumir os resultados comparando o comportamento linguístico entre o Windows e o ICU. A propósito, ICU agora é usado por muitos aplicativos no Windows e espera-se que o uso aumente. Além disso, esses resultados excluem o Linux com .NET Core 3.1 e inferior. que mostrará consistência entre .NET 5.0 e versões anteriores no Linux.

O ponto sobre a biblioteca que costumava funcionar será quebrado, isso não é totalmente verdade porque tais bibliotecas já estavam quebradas no Linux.

Você consideraria fazer uma alteração significativa para também tornar StringComparison.Ordinal a estratégia de comparação padrão?

Mencionei antes que já tentamos isso antes, mas o tamanho das reclamações era muito grande e não podíamos aplicá-lo. Estamos procurando maneiras de ajudar os desenvolvedores a serem mais conscientes ao especificar os sinalizadores de comparação de strings ao chamar as APIs de agrupamento.

Eu faço bibliotecas para viver, não aplicativos. Eu prefiro ter uma maneira de tempo de compilação para lidar com isso.

Sim, algum analisador pode ajudar nesse caso. geralmente olhe para as chamadas de APIs de agrupamento e veja qual delas não estava mostrando a intenção de usar a operação ordinal ou linguística.

Como qualquer analisador pode ajudar em projetos existentes?

Os analisadores verificam o código e detectam as chamadas de APIs de agrupamento que não passam sinalizadores ajudariam a examinar essas chamadas e corrigir se detectado um problema.

isso também é algo exclusivo do C #.

A mudança está dentro do runtime do .NET, que deve ser global e não restrito ao C #.

@tarekgh como um analisador C # se manifesta em uma base de código VB ou F #?

A maior parte do trabalho que fazemos é InvariantCulture , que pelo meu entendimento anterior é suposto ser imutável por design.

Antes dessa mudança, era impossível realmente depender disso para o código de plataforma cruzada; como NLS e ICU se comportam, mesmo quando usando cultura invariável, nem sempre são os mesmos (como evidenciado por este problema). Como @tarekgh disse, esse código tem agido de maneira diferente no Linux esse tempo todo. Agora que essa alteração foi feita, para qualquer instalação do Windows atualizada, o que "cultura invariável" significa deve _atualmente_ ser consistente em todas as plataformas.

Isso pode ser uma surpresa, mas eventualmente encontramos e corrigimos dezenas de bugs relacionados a diferenças de plataforma ao longo dos anos, como resultado de relatórios de usuários, como tenho certeza que muitos outros autores de bibliotecas fizeram por anos e anos.

Não estou empolgado com a perspectiva do .NET 5 apresentar uma nova safra de desconhecidos desconhecidos e revisitar essas correções não apenas para minhas bibliotecas, mas também para nossas dependências downstream. Isso é um custo econômico significativo para nós que não cria novas melhorias de produtividade para nossos usuários. Alguém na MSFT deve levar isso em consideração em sua discussão de custos / benefícios ao fazer essa mudança.

Editar: tipo, eu já entendi os méritos técnicos - sim, obrigado. Ajude a vender os benefícios econômicos não óbvios de fazer essa alteração, em oposição aos custos. Por que os usuários deveriam dar o salto e atualizar para o .NET 5 de qualquer maneira, considerando como esse problema parece um ninho de ratos?

@tarekgh como um analisador C # se manifesta em uma base de código VB ou F #?

Podemos cobrir C # e VB com um único analisador (nos esforçamos para tornar nossos analisadores agnósticos de linguagem sempre que possível), mas não podemos obter cobertura de analisador para F # no momento.

@Aaronontheweb qualquer pessoa usando funcionalidade cultural e assumir que não vai mudar já está quebrada, mesmo que não tenhamos feito essas mudanças na UTI. Consulte o blog https://docs.microsoft.com/en-us/archive/blogs/shawnste/locale-culture-data-churn da equipe do Windows, que informa até que o comportamento do NLS está mudando em prol de melhorias também. Portanto, a questão aqui não é mudar para a UTI, mais do que apenas pegar quaisquer suposições erradas da perspectiva de aplicativos / bibliotecas. atualizar para 5.0 é o mesmo que atualizar para outras versões anteriores. os aplicativos receberão muitos recursos novos e interessantes e os aplicativos precisam ser testados, pois podem haver algumas mudanças importantes entre os lançamentos. Não estou considerando que a mudança de comportamento da globalização está realmente quebrando, já que sempre dizemos que a globalização pode mudar a qualquer momento entre os sistemas operacionais e as versões dos sistemas operacionais. Conforme indicado antes, temos o switch de configuração para escolher atualizar para 5.0 e continuar usando NLS. isso fará com que a UTI não seja realmente um fator na decisão de atualização. agora com o ICU, os aplicativos terão a oportunidade de obter mais consistência em todos os sistemas operacionais e ainda podem ter mais controle sobre o comportamento de globalização se decidirem usar o ICU local do aplicativo. Estamos fornecendo muito mais controle aos aplicativos do que antes.

Relacionado: https://github.com/dotnet/runtime/issues/43802

(Esse problema não acompanha IndexOf _per se_. Em vez disso, discute as consequências não intencionais da rotina Compare padronizar para um comparador ciente da cultura.)

Como qualquer analisador pode ajudar em projetos existentes?

Os analisadores verificam o código e detectam as chamadas de APIs de agrupamento que não passam sinalizadores ajudariam a examinar essas chamadas e corrigir se detectado um problema.

Eu acho que os analisadores devem ser adicionados ao csproj.
Isso não acontecerá automaticamente. Portanto, muitos projetos existentes serão movidos para o .NET 5 sem esses analisadores.
Além disso, como já foi mencionado, isso não ajudará em projetos em F #.

@jeffhandley obrigado, isso confirma o que já entendi. Portanto, é importante reconhecer que a possível "solução alternativa" não ajudará os usuários do F # (um mercado pequeno, mas que é totalmente suportado pela MS como um cidadão de primeira classe do .NET). Eu não tenho ideia do que isso significa:

A mudança está dentro do runtime do .NET, que deve ser global e não restrito ao C #.

Eu não tenho ideia do que isso significa:
A mudança está dentro do runtime do .NET, que deve ser global e não restrito ao C #.

Eu quis dizer que qualquer linguagem usando .NET runtime será afetada e não apenas C #.

Vou expressar com veemência minha opinião novamente de que, fora da caixa, deve haver um analisador para descobrir essas pegadinhas no novo mundo, especialmente se o plano for mudar o comportamento atual.

Eu entendo perfeitamente os méritos técnicos de fazer isso e não estou sugerindo que você não faça uma alteração (a longo prazo, parece que esta é a mudança correta). Nem estou dizendo que ainda não foi documentado como prática recomendada. O que estou dizendo é que realmente precisamos que isso seja um grande erro vermelho e intermitente para os desenvolvedores que tentam passar para o .NET 5. Pronto para usar, os desenvolvedores presumem que, de outra forma, isso "simplesmente funciona".

Agora mesmo, você pode usar esta Biblioteca do Roslyn Analyzer de @meziantou para encontrar áreas afetadas: https://github.com/meziantou/Meziantou.Analyzer/tree/master/docs.

Neste caso específico, isso lançará um MA0074 - Evite métodos sensíveis à cultura implícitos

image

Dito isso, isso realmente precisa estar pronto para uso, abri este problema do Roslyn aqui: https://github.com/dotnet/roslyn-analyzers/issues/4367

@tarekgh Obrigado por esclarecer. Meu ponto original era que os analisadores não são a resposta aqui se você está procurando uma solução que funcione para todos os usuários .NET.

Estamos procurando outras maneiras de ajudar os desenvolvedores a serem conscientes ao chamar algo como IndexOf e fornecer intencionalmente sinalizadores StringComparison para expressar a intenção. Agradecemos quaisquer ideias que você possa ter também.

Que tal descontinuar os métodos antigos (usando um atributo [Obsolete] )?

O que acontece com a compatibilidade do .NET Standard quando a superfície da API é a mesma, mas o comportamento muda?

Uma mudança para honrar os clusters de grafemas não me incomoda, desde que o impacto seja bem documentado. Uma mudança que faz com que membros intimamente relacionados da família de métodos de string se comportem de maneira inconsistente uns com os outros.

Alguém que está fazendo munging de string informal, indiferente às obscuridades dos caracteres, grupos de grafemas ou localidade, consideraria que se str.Contains (qualquer) for bem-sucedido, não há necessidade de inspecionar o resultado de str.IndexOf (qualquer ) porque acabamos de nos dizer que ele está lá e, portanto, pode ser encontrado. Não importa qual segundo parâmetro eles não se importam em saber é o padrão, porque o padrão certamente se comportará da mesma forma em todos os métodos , livrando-os da necessidade de estudar todas as sutilezas para usá-los.

Inconsistências como essa produzem uma linguagem que só pode ser usada com sucesso por especialistas e alienam as pessoas que saem do campo do código. Não eleve a barra para entrar dessa maneira.

Eu concordo com @lupestro. A inconsistência no comportamento do método é muito preocupante. Se você quiser que métodos antigos atuem de maneira diferente e inconsistente, haverá muita tristeza. As pessoas se depararão com isso, se sentirão traídas pela API e se perguntarão quais outras bombas-relógio estão esperando para explodir. Muitos que adotaram o .NET irão descartar o C # por causa desse tipo de problema. Parece que você deve remover sobrecargas que não aceitam uma localidade ou normalizar a localidade padrão para métodos. Já existe um Compare e CompareOrdinal, talvez seja necessário um (Último) IndexOf (Qualquer) e (Último) IndexOf (Qualquer) Ordinal. Não gosto dessa solução, mas pelo menos seria consistente com o que existe atualmente. Talvez a ideia de uso ordinal em string deva ser depreciada. Se eu tiver que escolher entre rápido ou certo, escolherei 'certo' sempre. Comportamentos inconsistentes e não intuitivos são extremamente frustrantes.

Vejo que esse problema já foi resolvido, então suponho que já esteja decidido que isso continuará no .NET 5.0. Eu sei que essas coisas são difíceis e as informações de cultura (como o tempo) podem mudar por todos os tipos de razões não técnicas. Os desenvolvedores precisam estar cientes disso, mas também precisam depender de suas APIs para serem autoconsistentes. Deve haver pelo menos um aviso (no 5.0) como apontado por @aolszowka que indica um problema ... e mais importante porque é um problema. Avançar é importante e, às vezes, significa que você precisa "quebrar" antigos comportamentos / suposições. Isso não significa que novas inconsistências precisam ser introduzidas. Essa mudança quebra as expectativas e também o código. Se não for possível tornar os métodos consistentes, então prefiro que o CultureInfo seja forçado a ser explícito (o que eu poderia então abordar por meio de um método de extensão) do que apenas ter a possibilidade de um erro não intuitivo explodir meu código durante um ciclo de desenvolvimento estressante ou pior em uma instalação do cliente.

TLDR: altera coisas que precisam ser alteradas, mas não torne a API inconsistente para isso. Se você for quebrá-lo, substitua-o por algo melhor.

Eu uso o .NET há anos e sempre uso o padrão InvariantCulture ao usar essas funções. Com relação a outros idiomas além do inglês, sempre estive ciente dos pares de caracteres que funcionam como apelidos para outras letras específicas do idioma e do trabalho extra que é necessário para verificar esses pares ao fazer comparações com CurrentCulture como padrão. Isso me incomodou, por exemplo, ao escrever código ASP.NET que define a CurrentCulture do thread com base no idioma preferido do usuário enviado pelo navegador, e as comparações para um usuário que não usa o inglês fazem o código quebrar de maneiras sutis, especialmente quando as strings contêm uma mistura de texto inserido pelo usuário (linguístico) e ordinal.

Este comportamento de agrupamento de grafemas Unicode é novo para mim, pois eu esperava um comportamento ordinal para avanços de linha e retornos de carro, mesmo se eu tivesse usado sobrecargas invariantes como normalmente faço. Parece-me que o comportamento que faz mais trabalho e requer conhecimento especializado deveria ser opt-in, independentemente da retidão técnica. Talvez aquele navio tenha navegado há muito tempo, mas _esta mudança radical_ deve ser feita de forma tão transparente quanto, por exemplo, mudanças de idioma, sem ter que ler um blog obscuro. Meus colegas mal conhecem os novos recursos do C # 9.0, muito menos alguma regra de ICU misteriosa que pode afetar o código que eles escrevem hoje, que um dia pode ser portado e compilado no .NET 5 (ou, mais provavelmente, no .NET 6 ou 7).

Obsoletar os métodos antigos é algo que estamos considerando. Terminei um rascunho da proposta ontem à noite e estou procurando por uma revisão interna, e vou postá-lo como uma nova edição aqui em algumas horas.

O rascunho está postado em https://github.com/dotnet/runtime/issues/43956. Ele lista várias alternativas para possíveis caminhos a seguir (incluindo não fazer nada) e pesa os prós e os contras de cada abordagem. Sinta-se à vontade para deixar comentários sobre as ações propostas aqui.

Se você estiver relatando um bug, registre um novo problema e use-o para descrever o bug.

Se você tiver comentários sobre este problema específico ( "\r\n" vs. "\n" ), continue respondendo neste tópico. Obrigado!

Reabrindo enquanto consideramos as abordagens descritas no documento de @GrabYourPitchforks .

Ouvimos você - há muitos comentários claros aqui - estamos trabalhando para descobrir o caminho certo a seguir e manteremos esse problema atualizado.

Uma mudança para honrar os clusters de grafemas não me incomoda, desde que o impacto seja bem documentado. Uma mudança que faz com que membros intimamente relacionados da família de métodos de string se comportem de maneira inconsistente uns com os outros.

Alguém que está fazendo munging de string informal, indiferente às obscuridades dos caracteres, grupos de grafemas ou localidade, consideraria que se str.Contains (qualquer) for bem-sucedido, não há necessidade de inspecionar o resultado de str.IndexOf (qualquer ) porque acabamos de nos dizer que ele está lá e, portanto, pode ser encontrado. Não importa qual segundo parâmetro eles não se importam em saber é o padrão, porque o default é certo se comportar da mesma forma em todos os métodos, livrando-os da necessidade de estudar todas as sutilezas para usá-los.

Inconsistências como essa produzem uma linguagem que só pode ser usada com sucesso por especialistas e alienam as pessoas que saem do campo do código. Não eleve a barra para entrar dessa maneira.

Sim, isso expressou totalmente minhas preocupações. Como um desenvolvedor chinês típico, raramente colocamos StringComparison ou CultureInfo explicitamente ao chamar métodos relacionados a strings em nossos códigos de aplicativo, e isso simplesmente funciona. Definitivamente, não esperamos um comportamento diferente entre IndexOf e Contains !
.net 5.0
image
.net core 3.1
image
framework .net
image

Eu concordo com @lupestro. A inconsistência no comportamento do método é muito preocupante. Se você quiser que métodos antigos atuem de maneira diferente e inconsistente, haverá muita tristeza.

Talvez um ponto chave aqui seja que os dois métodos sempre foram inconsistentes. Eles não se tornaram inconsistentes repentinamente no .NET 5.0. Se estou seguindo as coisas corretamente, IndexOf sempre usou a comparação de cultura atual, Contains sempre usou a comparação ordinal. O .NET 5.0 concedido adiciona mais inconsistência. Mas o erro aqui foi no design original da API que permitiu essa inconsistência.

Se estou seguindo as coisas corretamente, IndexOf sempre usou comparação ordinal, Contains sempre usou comparação de cultura atual. O .NET 5.0 concedido adiciona mais inconsistência. Mas o erro aqui foi no design original da API que permitiu essa inconsistência.

Isso está correto, mas é o contrário, IndexOf(string) usa a cultura atual, IndexOf(char) usa Ordinal e Contains usa ordinal.

Vou elaborar brevemente as diferenças de IndexOf vs. Contains quais outros fizeram alusão recentemente.

IndexOf(string) sempre assumiu a comparação _CurrentCulture_, e Contains(string) sempre assumiu a comparação _Ordinal_. Essa discrepância existia no .NET Framework. Não é uma discrepância nova introduzida no .NET 5. Por exemplo, no .NET Framework (que usa o recurso NLS do Windows), a ligadura "æ" e a string de dois caracteres "ae" são comparadas como iguais em um comparador linguístico. Isso resulta na seguinte discrepância:

// Sample on .NET Framework, showing Contains & IndexOf returning inconsistent results
Console.WriteLine("encyclopædia".Contains("ae")); // prints 'False'
Console.WriteLine("encyclopædie".IndexOf("ae")); // prints '8' (my machine is set to en-US)

Essa discrepância existe há mais de uma década. Está documentado e há orientações a respeito. É um projeto incorreto? Possivelmente. Estamos discutindo em https://github.com/dotnet/runtime/issues/43956 maneiras de tornar o ecossistema mais saudável no futuro.

Para essa questão específica, o que realmente estamos nos perguntando é: "Considerando que a discrepância existiu desde sempre, quão distantes um do outro é permitido que esses dois métodos se desviem _na prática_ antes de prejudicar o ecossistema maior?" Acho que ainda estamos tentando definir onde esse limite deve estar. Relatórios como este são _extremamente_ úteis porque nos fornecem uma visão sobre as pessoas que usam essas APIs na prática e quais são as expectativas dos clientes em relação a seu comportamento. Como administradores da estrutura, precisamos considerar não apenas a documentação técnica, mas também a maneira como os aplicativos do mundo real consomem essas APIs.

Este comentário não tem a intenção de defender nenhum ponto de vista específico. Minha intenção é esclarecer alguns equívocos que vi e ajudar a explicar como estruturamos o problema.

Mesmo se InvariantCulture especificado, é comportamento correto que \n não corresponda à versão ICU?

image
image

Talvez, o código a seguir exiba 5 no Linux e -1 no Windows se usarmos git e suas configurações padrão (autocrlf = true)?

using System;

var s = @"hello
world";
Console.WriteLine(s.IndexOf("\n", StringComparison.InvariantCulture));

@ufcpp Sim, por ICU esse é o comportamento esperado, conforme discutido anteriormente neste tópico. Os dois caracteres <CR><LF> quando ocorrem adjacentes um ao outro são considerados uma unidade inquebrável para fins linguísticos. Pesquisar <LF> usando um comparador linguístico (como _InvariantCulture_) não produzirá nenhuma correspondência, pois dividiria esta unidade inquebrável.

Eu gostaria de compartilhar uma atualização com todos: Decidimos manter o ICU como o padrão para 5.0 GA. Veremos como melhorar a armadilha de usar acidentalmente a comparação linguística quando o ordinal era pretendido, que é rastreado em https://github.com/dotnet/runtime/issues/43956. Também entraremos em contato com algumas das bibliotecas que identificamos como afetadas por esse problema. Nos próximos dias, compartilharemos mais documentos para acompanhar o próximo lançamento 5.0 que ajudará as pessoas a identificar melhor o código problemático e corrigi-lo para evitar esse problema.

Essa foi uma decisão muito difícil de tomar e tivemos que pesar o impacto da compatibilidade para o ecossistema em relação ao impulso de padronização entre plataformas.

Deixaremos este problema em aberto para considerarmos mais atenuações para o problema \r\n na manutenção, se for determinado que as atenuações atuais são insuficientes.

Eu também gostaria de encorajar as pessoas a continuarem relatando problemas que são encontrados como resultado da troca da UTI, mesmo que o comportamento possa ser de causa conhecida, isso não significa que seja "intencionalmente". Continuaremos a investigar as diferenças e entender a causa raiz e determinar se precisamos conduzir mudanças no ICU ou .NET para resolvê-las.

Existe uma regra do analisador para sempre especificar StringComparison.

https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1307

Eu gostaria de compartilhar uma atualização com todos: Decidimos manter o ICU como o padrão para 5.0 GA. Veremos como melhorar a armadilha de usar acidentalmente a comparação linguística quando se pretendia o ordinal, rastreado em # 43956. Também entraremos em contato com algumas das bibliotecas que identificamos como afetadas por esse problema. Nos próximos dias, compartilharemos mais documentos para acompanhar o próximo lançamento 5.0 que ajudará as pessoas a identificar melhor o código problemático e corrigi-lo para evitar esse problema.

Essa foi uma decisão muito difícil de tomar e tivemos que pesar o impacto da compatibilidade para o ecossistema em relação ao impulso de padronização entre plataformas.

Deixaremos este problema em aberto para considerarmos mais atenuações para o problema \r\n na manutenção, se for determinado que as atenuações atuais são insuficientes.

Eu também gostaria de encorajar as pessoas a continuarem relatando problemas que são encontrados como resultado da troca da UTI, mesmo que o comportamento possa ser de causa conhecida, isso não significa que seja "intencionalmente". Continuaremos a investigar as diferenças e entender a causa raiz e determinar se precisamos conduzir mudanças no ICU ou .NET para resolvê-las.

Gostaria de saber se ainda posso usar dependências do .NET Standard sobre as quais não tenho controle.

Suspeito que não serei capaz. Que tal garantir que as pessoas façam a transição para, eu não sei, .NET Standard 2.2? Alterar a API?

Para que eu possa saber com certeza que obtenho o comportamento esperado.

Não tenho certeza de como podemos evitar quebrar o ecossistema atual, que já tem seu quinhão de problemas.

Eu ficaria feliz em ser provado que estou errado, por favor, faça-o com preconceito - isso aliviaria minha mente :)

@rcollina

Gostaria de saber se ainda posso usar dependências do .NET Standard sobre as quais não tenho controle.

As bibliotecas .Net Standard geralmente devem ser multiplataforma. E se alguma biblioteca funciona corretamente no .Net Core 3 no Unix hoje (que usa ICU), então quase certamente também funcionará no .Net 5 no Windows (que também usa ICU).

.NET Standard 2.2

.Net Standard vNext existe efetivamente, embora seja chamado de ".Net 5.0". (Ou seja, se você está escrevendo uma biblioteca e não se preocupa em oferecer suporte a estruturas mais antigas, hoje, você direcionaria o .Net Standard 2.1. Em um mês, você direcionaria o .Net 5.0.)

@svick entendi. Acredito que já entendo como funciona o .NET Standard. Eu entendo que o .NET 5 é o novo .NET Standard, por assim dizer.

Peço desculpas, mas ainda não tenho certeza sobre o que acontece quando faço referência a uma biblioteca .NET Standard 2.1 que dependia de uma inconsistência de comportamento pré-existente entre IndexOf e Contains.

O que está mudando aqui é algo fora da banda, ICU versus NLS. Essa mudança amplia a discrepância que já tínhamos e quebra as expectativas.
Não há nada codificando essas informações em bibliotecas.

Não pretendo entender todas as implicações, mas não consigo me livrar da sensação de que estamos sendo “tecnicamente corretos” sem um caminho de integração perfeito para o novo normal. O que é extremamente necessário.

Como outra pessoa mencionou, a maioria das pessoas nem mesmo tem conhecimento dos novos recursos da linguagem, muito menos das mudanças sísmicas como esta descoberta aleatoriamente por nossos melhores membros da comunidade. Quais são as chances de podermos suportar isso?

Gostaria de saber se ainda posso usar dependências do .NET Standard sobre as quais não tenho controle.

O que está mudando aqui é algo fora da banda, ICU versus NLS. Essa mudança amplia a discrepância que já tínhamos e quebra as expectativas.

Não, não importa. Nunca é demais enfatizar aqui que o ICU _sempre_ foi usado no Unix. As bibliotecas do .NET Standard devem ser portáveis ​​por design e tudo que funcionava anteriormente no Linux .NET Core 3.x funcionará no .NET 5.

A maior parte do trabalho que fazemos é InvariantCulture, que pelo meu entendimento anterior é considerado imutável por design.

Não é verdade. InvariantCulture só deve ignorar a configuração de idioma do usuário. Ele ainda pode ser alterado com atualizações nas especificações Unicode ou nas bibliotecas de globalização, etc.

A equipe .NET considerou a dor, os problemas e os custos que isso poderia causar?

Comentários como este me irritam infinitamente. Qualquer código que quebrasse repentinamente com essa mudança estava incorreto para começar. Como a plataforma deve avançar se a equipe .NET precisa manter o comportamento do código do usuário que está incorreto ou depende de detalhes de implementação? Não é como se eles não tivessem um interruptor de compatibilidade. Grande parte do motivo pelo qual o .NET Core se originou do .NET Framework foi para resolver esse problema por meio de recursos como instalações lado a lado e implantações de tempo de execução local do aplicativo. Se você não pode mudar para o .NET 5 por causa disso, não mude para o .NET 5.

Não estou empolgado com a perspectiva do .NET 5 apresentar uma nova safra de desconhecidos desconhecidos e revisitar essas correções não apenas para minhas bibliotecas, mas também para nossas dependências downstream.

Se você eliminou todos os bugs de diferença de plataforma conforme afirma, não deve se preocupar com nada.

A equipe .NET considerou a dor, os problemas e os custos que isso poderia causar?

Comentários como este me irritam infinitamente.

Milhões de projetos estão rodando em .NET e a manipulação de strings é uma operação muito frequente.
O esforço necessário para a equipe .NET corrigir / alterar isso é mínimo em comparação com o esforço necessário para verificar todo o código existente que será migrado para .NET 5/6.

Portanto, é justo perguntar sobre o "plano" para resolver isso.
Existe algum plano?
O efeito dessa mudança foi estimado?
É 0,001% de todos os projetos? É 75%?
Quais são outras mudanças semelhantes que não conhecemos?

Talvez isso afete apenas um pequeno número de projetos.
Mas, foi estimado?

BTW, eu sou totalmente favorável às alterações por motivos bons o suficiente. Mas, também precisamos de um caminho de migração que não seja muito arriscado.

@petarrepac Não me entenda mal, eu entendo isso. Mas, como foi apontado várias vezes neste tópico:

  1. Existe um plano e é fornecer uma chave de configuração de tempo de execução.
  2. O comportamento alegadamente quebrado é o comportamento existente do .NET em todas as plataformas não Windows.
  3. Isso só deve afetar o código que executa operações sensíveis à cultura onde ordinal foi planejado.

Dados os dois últimos pontos, é provavelmente razoável supor que isso afete uma porcentagem bastante pequena dos projetos.

100%, é justo perguntar sobre isso, mas as pessoas que escrevem comentários como o que citei estão frequentemente presumindo que nenhuma consideração foi colocada e escrita antes de tentar entender o quadro geral por trás da mudança.

Olá a todos. Queríamos fornecer um breve resumo das ações que tomamos quando esse problema foi aberto e, no final, por que decidimos manter o padrão na atualização do Windows 10 de maio de 2019 ou posterior como ICU para .NET 5.0.

Quando o problema foi aberto, começamos algumas discussões internas sobre o potencial impacto e sofrimento que isso poderia ter causado em nossos clientes, dada a inconsistência entre Contains(string) que é Ordinal e IndexOf(string) sendo ciente da cultura, além de ter outras APIs que reconhecem a cultura por padrão ao operar sobre uma string, mas sendo Ordinal ao operar sobre Span<char> ou ReadOnlySpan<char> . Portanto, após discussões sobre esse problema, começamos a fazer análises nos pacotes NuGet que podem ser afetados e coletamos dados para obter uma imagem clara. Estas foram nossas descobertas:

  • "\ n" é # 30 nos "literais de string mais usados" passados ​​para IndexOf, LastIndexOf, EndsWith, StartsWith, Compare e CompareTo.
  • 1% dos locais de chamada para String.EndsWith estão com uma string de pesquisa que começa com \ n. Qualquer um desses locais de chamada onde a string sendo testada contém "terminações de linha no estilo do Windows" seria quebrado.
  • Existem 2040 ids de pacote hospedados em NuGet.org, onde uma versão contém um assembly com um callite em risco.
  • A grande maioria desses callites são para EndsWith e IndexOf, o que é consistente com a hipótese de que esse padrão é usado frequentemente para algoritmos de quebra de linha ingênuos.

Destes 2040 pacotes que tinham um callite de risco, apenas 539 suportam alguma versão do .NET Standard ou .NET Core, o que significa que apenas 0,54% dos pacotes listados em NuGet.org provavelmente serão expostos à quebra.

Nós olhamos os pacotes na lista de 539 ids de pacotes potencialmente afetados para ter uma idéia do impacto real sobre eles. Analisamos os 70 principais (por contagem de download), 20 não expuseram o padrão na versão mais recente; dos que expuseram o padrão, só pudemos olhar para 32 que tinham uma licença permissiva:

  • 14 não foram sujeitos ao bug
  • 13 estavam potencialmente quebrados
  • 5 eram incertos (não havia indicação clara de quebras devido à codificação defensiva para diversos padrões de quebra de linha ou outras atenuações).

Portanto, isso significa que dos 70 principais pacotes por download, apenas 18% foram potencialmente afetados.

Essas porcentagens são empilhadas e não em relação ao número total de pacotes no NuGet.org que é 229.536. Portanto, se usássemos o número total de pacotes e o número total de downloads no NuGet.org, veríamos 539 pacotes potencialmente afetados de 229.536, o que é 0,24%.

E embora seja ótimo para nós analisarmos bibliotecas, o nuget representa apenas uma pequena fração do código C # que existe. E mesmo se alguém for o proprietário do código, a) os bugs podem não ser fáceis de rastrear eb) eles podem não ter mais o código-fonte.

No entanto, esta foi uma boa fonte de dados para concluir que, embora possa ser uma mudança muito notável no comportamento, é uma quebra que já aconteceu no Unix ao ler entradas que podem conter terminações de linha do Windows (o que pode não ser tão comum).

No .NET Core e .NET 5+, estamos nos esforçando para obter consistência entre os sistemas operacionais e, devido ao impacto dessa mudança, parecia a coisa certa a fazer. Nós nos preocupamos com a compatibilidade e, portanto, estamos fornecendo uma opção de tempo de execução compat para que as pessoas possam voltar ao comportamento legado.

Além disso, uma conclusão dos pacotes que pudemos inspecionar, dado que o comportamento já é diferente no Unix, vimos muita programação defensiva contra esse problema, para mitigar possíveis quebras nos sistemas operacionais.

Para adicionar a isso, a globalização pode mudar a qualquer momento, já que somos um wrapper fino no sistema operacional, então parecia a coisa certa no momento ser o mesmo wrapper em todos os sistemas operacionais que suportamos.

Como parte disso, melhoramos nossa documentação com exemplos práticos, regras do analisador roslyn e como o código afetado pode atenuar isso.

Obrigado por todos os seus valiosos comentários, pois sempre nos levam a um lugar melhor e tentaremos continuar melhorando essa experiência para .NET 6, conforme discutido em: https://github.com/dotnet/runtime/issues/43956

Como entendemos a dor que isso pode causar devido às diferenças entre as terminações de linha no Unix e no Windows, estamos mantendo esse problema em aberto e investigaremos uma possível maneira de mitigar o caso \r\n do .NET 5.0 .x que deve ser parte de uma versão de serviço.

Também há uma diferença com char e string:

Console.WriteLine("com/test/test/test/a/b/ʹ$ʹ".IndexOf("$"));
Console.WriteLine("com/test/test/test/a/b/ʹ$ʹ".IndexOf('$'));
-1
24

@mattleibow ao usar a pesquisa de caracteres, realiza a pesquisa ordinal. O documento https://docs.microsoft.com/en-us/dotnet/api/system.string.indexof?view=net-5.0#System_String_IndexOf_System_Char_ que está declarando This method performs an ordinal (culture-insensitive) search . Se você fizer a pesquisa de string com a opção ordinal, obterá o mesmo resultado do caractere.

C# Console.WriteLine("com/test/test/test/a/b/ʹ$ʹ".IndexOf("$", StringComparison.Ordinal));

~ Parece que CA1307 só dispara em indexof(char) mas não em indexof(string) . Existe uma regra para a versão string ? Isso parece mais importante porque o padrão de char usa automaticamente o ordinal e a alteração significativa não afeta realmente isso, enquanto o comportamento de string mudou e você precisa especificar o ordinal para restaurar o comportamento em alguns casos. ~

~ Como você detecta indexof(string) ? ~

Encontrei, é a regra CA1310. Nossos documentos estão errados para https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/globalization-icu#use -nls-instead-of-icu e não menciona essa variação específica. Vou atualizar esses documentos.

Esta página foi útil?
0 / 5 - 0 avaliações