Runtime: Changement de rupture avec string.IndexOf (string) de .NET Core 3.0 -> .NET 5.0

Créé le 22 oct. 2020  ·  76Commentaires  ·  Source: dotnet/runtime

La description

J'étends un package pour prendre en charge .NET 5.0 et j'ai rencontré un changement radical. Compte tenu de l'application 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)}");
        }
    }
}

J'obtiens des résultats différents en fonction du runtime de .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

Configuration

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

Régression?

Oui, cela a fonctionné via .NET Core 3.1.9

area-System.Globalization question

Commentaire le plus utile

@tarekgh , je suis d'accord que les différents résultats entre Contains et IndexOf ne sont pas le problème en soi.

Le problème est clairement IndexOf qui est incapable de trouver une chaîne ASCII uniquement à l'intérieur d'une autre chaîne ASCII uniquement (je ne suis pas sûr qu'il y ait jamais eu de comportement dépendant des paramètres régionaux imposé aux chaînes ASCII uniquement!).

Ce n'est pas quelque chose que j'attendrais de tout changement lié aux paramètres régionaux / NLS / ICU; en fait, je ne pouvais penser à aucun autre langage de programmation / runtime se comportant comme ça.

Voici un cas de test simplifié, cassé (je veux dire, me donnant un résultat totalement inattendu) sur .NET 5 RC 2:

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

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

Est-ce que ça devrait vraiment fonctionner comme ça? Et pourquoi? Qu'est-ce qu'il essaie de faire réellement?

Était-ce vraiment un changement planifié?

Oui, le passage à l'ICU est un changement intentionnel pour différentes raisons.

Je suis désolé, mais je ne crois pas que ce soit un changement planifié, alors j'aimerais souligner: je ne pourrais pas imaginer quiconque _ planifier_ un tel changement. Par exemple, des membres de l'équipe .NET se sont réunis et ont discuté:

La chaîne "\ n \ r \ nTest" contient-elle "\ nTest" avec ICU activé? Non, clairement pas!

Et personne ne s'est plaint? Aucune chance!

Cela ne ressemble pas à un changement planifié ou attendu, mais ressemble à un bogue très sérieux, un gros bloqueur de compatibilité. Pour cette raison, les applications .NET nouvelles et portées ne fonctionneront pas correctement sur le nouveau runtime, car elles ne pourront pas trouver de sous-chaînes à l'intérieur de string!

Pourquoi l'ICU se soucie-t-elle des fins de ligne de toute façon? Certains paramètres régionaux ont-ils leurs propres fins de ligne spécifiques aux paramètres régionaux?

PS Oui, vous pourriez dire qu'il faut vraiment toujours appeler une variante de IndexOf indépendante de la culture, comme avec le drapeau ordinal. Mais, si vous avez décidé de le briser _that_ dur dans .NET 5, ne pourriez-vous pas simplement le faire pour utiliser la valeur par défaut normale et ordinale? Je pense que cela casserait moins d'applications que le changement actuel que nous constatons dans .NET 5 RC 2.

De plus, je pense que nous comprenons tous que, bien que IndexOf se comporte toujours d'une manière spécifique à la culture, il y a des tonnes de code dans la nature qui utilisent IndexOf sans les indicateurs ordinaux, et ce code _utilisé pour travailler_ (dans certains / la plupart des cas, au moins). Et il cessera de fonctionner après la mise à jour .NET 5.

Tous les 76 commentaires

@tarekgh

Marquage des abonnés à cette zone: @tarekgh , @safern , @krwq
Voir les informations dans area-owners.md si vous souhaitez être abonné.

C'est par conception comme dans .NET 5.0, nous avons changé en utilisant ICU au lieu de NLS. Vous pouvez consulter https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/globalization-icu pour plus de détails.

Vous avez la possibilité d'utiliser le commutateur de configuration System.Globalization.UseNls pour revenir à l'ancien comportement, mais nous ne recommandons pas de le faire car ICU est plus correct et avancer en utilisant ICU donnera une cohérence à travers les systèmes d'exploitation.

j'ai oublié de dire, si vous voulez que IndexOf se comporte comme Contains , vous devriez utiliser les comparaisons ordinales à ce moment-là.

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

Vous avez la possibilité d'utiliser le commutateur de configuration System.Globalization.UseNls pour revenir à l'ancien comportement, mais nous ne recommandons pas de le faire car ICU est plus correct et avancer en utilisant ICU donnera une cohérence à travers les systèmes d'exploitation.

Ouais, si vous exécutez ce code dans le ciblage Unix netcoreapp3.1 vous verrez ce même comportement:

 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

et comme @tarekgh avec Ordinal comparaison, il renvoie le résultat attendu.

 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

Je pense que cela échoue parce que le mélange de \r\n et \n dans la chaîne source. Si je remplace toutes les instances de \r\n par \n cela fonctionne. La même chose est vraie si je fais tout \r\n . C'est juste le mélange qui donne des résultats différents de la comparaison de l'ICU.

Le problème signalé sur Twitter était que dans le runtime 5.0, des appels répétés à string.IndexOf avec les mêmes entrées donnaient des résultats différents à chaque appel.

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

Edit: ci-dessus était un malentendu.

@GrabYourPitchforks pouvons-nous mettre à jour le titre du problème alors? Comme il s'agit d'un changement technique, cela se produit sous Windows et Unix ... n'est-ce pas?

J'ai demandé à Jimmy hors ligne pour des éclaircissements. Il est possible que j'ai mal compris son rapport de problème original. Les forums de 280 caractères ne sont pas toujours efficaces pour communiquer clairement les bogues. ;)

Juste pour clarifier, Contains API effectue une opération ordinale. IndexOf sans aucun indicateur de comparaison de chaîne est une opération linguistique et non ordinale. Si vous voulez comparer le comportement Contains avec IndexOf, vous devez utiliser IndexOf(expected, StringComparison.Ordinal) .
Si vous avez besoin d'en savoir plus sur la différence, https://docs.microsoft.com/en-us/dotnet/csharp/how-to/compare-strings est un lien utile.

J'ai reçu des éclaircissements sur Twitter. L'application n'appelle pas IndexOf dans une boucle. Ceci est juste un rapport de différence de comportement standard 3.0 contre 5.0.

@GrabYourPitchforks pourriez-vous partager le lien https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/globalization-icu sur vos réponses Twitter et mentionner que nous avons un commutateur de configuration pour revenir à l'ancien comportement?

J'ai reçu des éclaircissements sur Twitter. L'application n'appelle pas IndexOf dans une boucle. Ceci est juste un rapport de différence de comportement standard 3.0 contre 5.0.

Merci, @GrabYourPitchforks ... sur cette base, en le fermant comme par conception.

Pour en ajouter ici, si vous souhaitez obtenir l'ancien comportement sans revenir à NLS, vous pouvez faire

`` C #
CultureInfo.CurrentCulture.CompareInfo.IndexOf (réel, attendu, CompareOptions.IgnoreSymbols)


or 

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

au lieu de

C# actual.IndexOf(expected)

et vous devriez obtenir le comportement souhaité.

Je ne vois rien à propos de \r\n vs \n dans la documentation liée à l'ICU (https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/ globalisation-icu).

Était-ce vraiment un changement planifié?

@ForNeVeR, il sera difficile de lister chaque différence entre ICU et NLS. le document parle du changement principal du passage à l'ICU. Comme je l'ai indiqué précédemment, il n'est pas correct de comparer les résultats de Contains avec IndexOf sans les paramètres StringComparison. J'ai énuméré ci-dessus quelques façons dont vous pouvez obtenir le comportement précédent si vous le souhaitez. D'après le rapport de ce problème, il me semble que l'utilisation d'IndexOf devrait utiliser l'option Ordinal et il est incorrect d'utiliser les comparaisons linguistiques dans un tel cas. L'utilisation de la comparaison linguistique dans un tel cas peut dépendre de la culture actuelle qui peut donner des résultats différents sur différents environnements.

Était-ce vraiment un changement planifié?

Oui, le passage à l'ICU est un changement intentionnel pour différentes raisons. Windows fait actuellement la promotion de l'utilisation d'ICU sur NLS. L'ICU est de toute façon l'avenir. En outre, ICU donnera la possibilité d'avoir des comportements cohérents sur Windows / Linux / OSX ou sur toute plate-forme prise en charge. L'utilisation d'ICU donnera l'opportunité aux applications de personnaliser le comportement de globalisation si elles le souhaitent.

Comme indiqué dans le document, vous avez toujours la possibilité de revenir à l'ancien comportement si vous le souhaitez.

Aïe, le document référencé indique que le comportement ICU / NLS sous Windows peut basculer silencieusement en fonction de la disponibilité icu.dll dans l'environnement réel. Cela pourrait être une grande surprise pour les applications autonomes publiées. Je m'attendrais à ce que .NET expédie ICU pour contourner ce problème si le commutateur était décidé, et comme ICU n'est pas disponible sur tous les environnements cibles. Cette dépendance d'exécution facultative rend les choses encore plus amusantes.

Je m'attendrais à ce que .NET expédie ICU pour contourner ce problème si le commutateur était décidé, et comme ICU n'est pas disponible sur tous les environnements cibles. Cette dépendance d'exécution facultative rend les choses encore plus amusantes.

L'ICU est désormais publiée en tant que package NuGet. Les applications peuvent utiliser de tels packages pour que l'application autonome s'assure d'avoir ICU. regardez la section locale de l'

@tarekgh , je suis d'accord que les différents résultats entre Contains et IndexOf ne sont pas le problème en soi.

Le problème est clairement IndexOf qui est incapable de trouver une chaîne ASCII uniquement à l'intérieur d'une autre chaîne ASCII uniquement (je ne suis pas sûr qu'il y ait jamais eu de comportement dépendant des paramètres régionaux imposé aux chaînes ASCII uniquement!).

Ce n'est pas quelque chose que j'attendrais de tout changement lié aux paramètres régionaux / NLS / ICU; en fait, je ne pouvais penser à aucun autre langage de programmation / runtime se comportant comme ça.

Voici un cas de test simplifié, cassé (je veux dire, me donnant un résultat totalement inattendu) sur .NET 5 RC 2:

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

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

Est-ce que ça devrait vraiment fonctionner comme ça? Et pourquoi? Qu'est-ce qu'il essaie de faire réellement?

Était-ce vraiment un changement planifié?

Oui, le passage à l'ICU est un changement intentionnel pour différentes raisons.

Je suis désolé, mais je ne crois pas que ce soit un changement planifié, alors j'aimerais souligner: je ne pourrais pas imaginer quiconque _ planifier_ un tel changement. Par exemple, des membres de l'équipe .NET se sont réunis et ont discuté:

La chaîne "\ n \ r \ nTest" contient-elle "\ nTest" avec ICU activé? Non, clairement pas!

Et personne ne s'est plaint? Aucune chance!

Cela ne ressemble pas à un changement planifié ou attendu, mais ressemble à un bogue très sérieux, un gros bloqueur de compatibilité. Pour cette raison, les applications .NET nouvelles et portées ne fonctionneront pas correctement sur le nouveau runtime, car elles ne pourront pas trouver de sous-chaînes à l'intérieur de string!

Pourquoi l'ICU se soucie-t-elle des fins de ligne de toute façon? Certains paramètres régionaux ont-ils leurs propres fins de ligne spécifiques aux paramètres régionaux?

PS Oui, vous pourriez dire qu'il faut vraiment toujours appeler une variante de IndexOf indépendante de la culture, comme avec le drapeau ordinal. Mais, si vous avez décidé de le briser _that_ dur dans .NET 5, ne pourriez-vous pas simplement le faire pour utiliser la valeur par défaut normale et ordinale? Je pense que cela casserait moins d'applications que le changement actuel que nous constatons dans .NET 5 RC 2.

De plus, je pense que nous comprenons tous que, bien que IndexOf se comporte toujours d'une manière spécifique à la culture, il y a des tonnes de code dans la nature qui utilisent IndexOf sans les indicateurs ordinaux, et ce code _utilisé pour travailler_ (dans certains / la plupart des cas, au moins). Et il cessera de fonctionner après la mise à jour .NET 5.

Le problème est clairement IndexOf qui est incapable de trouver une chaîne ASCII uniquement à l'intérieur d'une autre chaîne ASCII uniquement (je ne suis pas sûr qu'il y ait jamais eu de comportement dépendant des paramètres régionaux imposé aux chaînes ASCII uniquement!).

Ce n'est pas vrai que l'ASCII est indépendant des paramètres régionaux. regardez le lien http://userguide.icu-project.org/collation/concepts comme exemple de la façon dont le comportement des caractères ASCII peut différer selon les cultures.

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.

De plus, je tiens à préciser que l'ICU choisit ses données et son comportement dans Unicode Standard, bien pensé par de nombreux experts. @GrabYourPitchforks va publier plus de détails sur \r\n\ cas dont nous parlons ici. mais en attendant, vous pouvez vous familiariser avec la doc https://unicode.org/reports/tr29/ en particulier dans les sections mentionnant ce qui suit:

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)

Pourquoi l'ICU se soucie-t-elle des fins de ligne de toute façon? Certains paramètres régionaux ont-ils leurs propres fins de ligne spécifiques aux paramètres régionaux?

Ceci est traité dans le dernier paragraphe.

Je suis désolé, mais je ne pense pas que ce soit un changement planifié, alors j'aimerais souligner: je ne pourrais pas imaginer quiconque planifie un tel changement. Par exemple, des membres de l'équipe .NET se sont réunis et ont discuté:
Cela ne ressemble pas à un changement planifié ou attendu, mais ressemble à un bogue très sérieux, un gros bloqueur de compatibilité. Pour cette raison, les applications .NET nouvelles et portées ne fonctionneront pas correctement sur le nouveau runtime, car elles ne pourront pas trouver de sous-chaînes à l'intérieur de string!

C'est bien planifié, travaillé et réfléchi en profondeur. vous pouvez consulter le numéro https://github.com/dotnet/runtime/issues/826 que nous avons publié il y a longtemps et que nous l'avons partagé publiquement.
Je tiens également à souligner que le comportement de la mondialisation peut changer à tout moment non seulement pour le .NET mais pour les systèmes d'exploitation et d'autres plates-formes. C'est également la raison pour laquelle nous avons pris en charge la fonctionnalité locale de l'application ICU pour permettre aux applications d'utiliser une version ICU spécifique pour garantir que le comportement qu'elles utilisent ne changera pas. Autre chose, Windows lui-même est en train de promouvoir l'utilisation de l'ICU et un jour, le comportement de l'ICU sera ce que la majorité des utilisateurs utilisera.

comme avec le drapeau ordinal. Mais, si vous avez décidé de le casser aussi dur dans .NET 5, ne pourriez-vous pas simplement le faire pour utiliser la valeur normale par défaut? Je pense que cela casserait moins d'applications que le changement actuel que nous constatons dans .NET 5 RC 2.

En fait, nous avons déjà essayé de faire du comportement Ordinal la valeur par défaut pendant les versions de Silverlight et cela a causé beaucoup plus de problèmes que ce qui a été rapporté ici. Nous cherchons d'autres moyens d'aider les développeurs à être conscients lorsqu'ils appellent quelque chose comme IndexOf et à fournir intentionnellement des indicateurs StringComparison pour exprimer l'intention. Nous accueillons également toutes les idées que vous pourriez proposer.

De plus, je pense que nous comprenons tous que, bien qu'IndexOf se comporte toujours d'une manière spécifique à la culture, il y a des tonnes de code dans la nature qui utilisent IndexOf sans les indicateurs ordinaux, et ce code fonctionnait (dans certains / la plupart des cas, au moins). Et il cessera de fonctionner après la mise à jour .NET 5.

C'est pourquoi nous fournissons un commutateur de configuration pour revenir à l'ancien comportement si vous le souhaitez également. regardez System.Globalization.UseNls

@tarekgh , merci pour une explication approfondie!

Pour l'instant, je pense qu'il est préférable pour moi d'attendre des détails sur ce comportement \r\n . Il n'est pas clair pour moi comment IndexOf utilise les "Règles de limites de grappes de graphèmes" (et s'il devrait le faire) lors de l'exécution de cette recherche particulière).

De plus, même si la spécification Unicode est pertinente ici (ce qui peut très bien l'être!), En lisant https://unicode.org/reports/tr29/ , je ne suis pas sûr que cela interdit de faire correspondre ce dernier \n . En lisant la spécification, il est dit CR | × | LF , où × signifie "Pas de limite (ne pas autoriser la rupture ici)". Donc, lors de la rupture d'une séquence \r\n\n , il interdit seulement de placer le "break" entre le premier et le deuxième caractère, mais il devrait être correct de placer le "break" avant le troisième, non? Donc, j'ai lu \r\n\n comme deux "clusters de graphèmes" séparés, et, même si IndexOf ne doit correspondre qu'à des clusters de graphèmes complets et ne jamais toucher des parties de clusters, il devrait toujours trouver la sous-chaîne \nTest intérieur de la chaîne \n\r\nTest .

En outre, dites-vous que d'autres environnements d'exécution / langages de programmation reposant sur ICU et / ou spécification Unicode devraient se comporter de la même manière dans cet exemple particulier?

Nous cherchons d'autres moyens d'aider les développeurs à être conscients lorsqu'ils appellent quelque chose comme IndexOf et à fournir intentionnellement des indicateurs StringComparison pour exprimer l'intention. Nous accueillons également toutes les idées que vous pourriez proposer.

_ (Un avertissement nécessaire: je travaille pour JetBrains sur plusieurs projets, dont ReSharper.) _

Je ne voulais pas au départ amener ce point ici pour ne pas ressembler à une publicité, mais je suppose que c'est très pertinent, donc je vais devoir le faire. ReSharper affichera par défaut l'avertissement suivant pour le code utilisateur appelant IndexOf :
image

_ (veuillez noter que je n'étais pas au courant de ce diagnostic particulier de ReSharper avant d'être impliqué dans ce fil, donc je ne participe

Donc, je pense que ce serait une très bonne idée d'afficher une telle notification par défaut dans tous les autres outils également, ou peut-être même de déconseiller totalement cette méthode bidon avec un tel avis.

@ForNeVeR

De plus, même si la spécification Unicode est pertinente ici (ce qui peut très bien l'être!), À la lecture de unicode.org/reports/tr29, je ne suis pas sûr qu'il interdit de correspondre à ce dernier \ n. En lisant les spécifications, il est dit CR | × | LF, où × signifie "Pas de limite (ne pas autoriser la rupture ici)". Donc, lors de la rupture d'une séquence \ r \ n \ n, il interdit seulement de placer le "break" entre le premier et le deuxième caractère, mais il devrait être correct de placer le "break" avant le troisième, non? Donc, je lis \ r \ n \ n comme deux "grappes de graphèmes" séparées

C'est correct. \r\n\n sera 2 clusters comme \r\n et \n .

et, même si IndexOf ne doit correspondre qu'à des clusters de graphèmes complets et ne jamais toucher des parties de clusters, il doit toujours trouver la sous-chaîne \ nTest à l'intérieur de la chaîne \ n \ r \ nTest.

C'est inexact. \n\r\nTest sera divisé en parties. \n , \r\n et Test . il est évident que \nTest ne peut pas faire partie de cette chaîne. pensez à remplacer le cluster \r\n par un symbole X . maintenant la chaîne source sera \nXTest qui ne contient pas \nTest .

En outre, dites-vous que d'autres environnements d'exécution / langages de programmation reposant sur ICU et / ou spécification Unicode devraient se comporter de la même manière dans cet exemple particulier?

si vous utilisez le niveau de force de classement par défaut, la réponse est oui. L'ICU peut permettre de changer le niveau de force qui peut affecter le résultat. Par exemple, comme je l'ai mentionné plus tôt, faire quelque chose comme:

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

changera le niveau de force et fera que l'opération ignore les symboles (ce qui changera le comportement de \n et \r car il sera ignoré à ce moment-là)

De plus, j'ai écrit une application C native ICU pure et exécuté le même cas:

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

cette application affichera les résultats:

search result = -1
search result = 1

qui est identique au comportement que vous constatez avec .NET.

Pour l'instant, je pense qu'il est préférable pour moi d'attendre des détails sur ce comportement \ r \ n particulier. Il n'est pas clair pour moi comment IndexOf utilise les "Règles de limites de grappes de graphèmes" (et s'il doit le faire) lors de l'exécution de cette recherche particulière).

Pour sûr, le clustering affecte l'opération de classement. Si vous regardez http://unicode.org/reports/tr29/tr29-7.html , il est clairement indiqué ce qui suit:

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. Les limites du cluster de graphèmes sont importantes pour le classement , 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.

Je ne sais pas s'il y aura plus de détails, mais je laisserai @GrabYourPitchforks commenter s'il a plus à ajouter ici.

Donc, je pense que ce serait une très bonne idée d'afficher une telle notification par défaut dans tous les autres outils également, ou peut-être même de déconseiller totalement cette méthode bidon avec un tel avis.

Merci!
C'est la même direction dans laquelle nous pensons aussi.

Pour mon propre plaisir, j'ai comparé les différentes surcharges entre les versions:

| Méthode | netcoreapp3.1 | net5.0 |
| ------------------------------------------------- --------------- | ----------------- | ---------- |
| actual.Contains(expected) | Vrai | Vrai |
| actual.IndexOf(expected) | 1475 | -1 |
| actual.Contains(expected, StringComparison.CurrentCulture) | Vrai | Faux |
| actual.IndexOf(expected, StringComparison.CurrentCulture) | 1475 | -1 |
| actual.Contains(expected, StringComparison.Ordinal) | Vrai | Vrai |
| actual.IndexOf(expected, StringComparison.Ordinal) | 1475 | 1475 |
| actual.Contains(expected, StringComparison.InvariantCulture) | Vrai | Faux |
| actual.IndexOf(expected, StringComparison.InvariantCulture) | 1475 | -1 |

Veuillez inclure un analyseur pour cela.

Cela semble être l'un de ces changements qui, bien que bons à long terme, créent une énorme quantité de désabonnement une fois que .NET 5 est lancé. Donc, si le comportement de ces méthodes diffère entre .NET 5 et .NET Core 3.1, que va-t-il se passer lorsqu'un .NET 5 appelle un objet défini dans une bibliothèque .NET Standard 2.0 qui manipule un string passé du site d'appel .NET? L'ancien comportement est-il utilisé ou le nouveau comportement?

@Aaronontheweb nouveau comportement. J'ai vu cela initialement à partir d'une assertion dans NUnit3, qui cible netstandard2.0 . Après la mise à niveau, mon test a commencé à échouer lorsque je n'ai changé que le cadre cible.

Ce n'est pas génial - je ne peux pas contrôler ce que font les anciennes bibliothèques que je référence si je veux mettre à niveau.

Combien d'applications ne détecteront pas cela dans les tests unitaires et les mettront en production?
L'équipe .NET a-t-elle pris en compte la douleur, les problèmes et le coût que cela pourrait causer?

Ce n'est pas génial - je ne peux pas contrôler ce que font les anciennes bibliothèques que je référence si je veux mettre à niveau.

beau piège!
heureux de perdre du temps en chassant des insectes étranges 🎉
mais pourquoi InvariantGlobalization n'aide pas avec ce problème?

Veuillez inclure un analyseur pour cela.

Ouais. Et n'oubliez pas le support F #.

Combien d'applications ne détecteront pas cela dans les tests unitaires

Zéro - puisque les tests unitaires ne sont pas destinés à tester des bibliothèques / frameworks externes comme le .NET BCL lui-même.

Mon avis est qu'il devrait y avoir un attribut au niveau de l'assembly qui peut contrôler le mode de ce changement de comportement. Au moins dans ce cas, vous pouvez vous y inscrire / vous désinscrire au niveau de chaque assemblage. Cela signifie alors que le problème .NET Standard disparaît également.

Ce n'est pas génial - je ne peux pas contrôler ce que font les anciennes bibliothèques que je référence si je veux mettre à niveau.

Je ne vois pas pourquoi vous ne pouvez pas le contrôler. Toutes les comparaisons de chaînes utilisent ICU ou NLS. Vous pouvez désactiver ICU en utilisant le commutateur compat si vous le souhaitez, et toutes vos bibliothèques reviendront à l'ancien comportement.

Vous ne pouvez pas compter sur la stabilité des données de mondialisation au fil du temps. Sachez que l'équipe Windows n'a pas peur de briser les gens qui dépendent de données de mondialisation stables. Les fonctions de mondialisation devraient être une boîte noire; Je ne pense pas qu'il soit logique que les bibliothèques (en particulier celles qui ciblent .NET Standard) disent qu'elles dépendent de détails d'implémentation comme celui-ci.

Je suis sûr que tout autant de gens se sont plaints des fonctions de globalisation .NET retournant des résultats différents sous Windows par rapport à Linux (et peut-être même plus de gens ne l'ont-ils pas encore remarqué). Il est préférable d'unifier le comportement entre Windows et les autres plates-formes, et tout code correct ne doit pas reposer sur le fait que les données de globalisation sont immuables, quoi qu'il en soit.

Envisageriez-vous de faire un changement radical pour également faire de StringComparison.Ordinal la stratégie de comparaison par défaut? Étant donné que la mondialisation est si instable, il est logique qu'au moins l'implémentation par défaut utilise un algorithme stable à la place. Je suis prêt à parier que 99,9% des personnes qui utilisent string.Equals(...) ou string.Contains(...) etc. sans passer StringComparison ne le font pas avec l'intention explicite de gérer des bizarreries étranges liées à locales.

Edit: Je suppose que ma question a déjà reçu une réponse:

En fait, nous avons déjà essayé de faire du comportement Ordinal la valeur par défaut pendant les versions de Silverlight et cela a causé beaucoup plus de problèmes que ce qui a été rapporté ici. Nous cherchons plus de moyens pour aider les développeurs à être conscients lorsqu'ils appellent quelque chose comme IndexOf et à fournir intentionnellement des indicateurs StringComparison pour exprimer l'intention. Nous accueillons également toutes les idées que vous pourriez proposer.

Vous pouvez désactiver ICU en utilisant le commutateur compat si vous le souhaitez, et toutes vos bibliothèques reviendront à l'ancien comportement.

Je crée des bibliothèques pour gagner ma vie, pas des applications. Je préférerais avoir un moyen de gérer cela au moment de la compilation.

La plupart du travail que nous faisons est de InvariantCulture , ce qui, selon ma compréhension précédente, est censé être immuable par conception. On dirait que le comportement de IndexOf est différent entre .NET 5.0 et .NET Core 3.1 dans ces circonstances également.

Comment un analyseur peut-il aider pour les projets existants?

@petarrepac c'est aussi une chose

@isaacabraham et VB aussi;)

Je préférerais avoir un moyen de gérer cela au moment de la compilation.

@Aaronontheweb, vous disposez d'un moyen de gérer cela au moment de la compilation (lors de la compilation d'applications). Vous pouvez ajouter ceci à votre projet:

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

EDIT: ce n'est que pour les applications consommant une bibliothèque, malheureusement vous ne pouvez pas contrôler cela lors de l'écriture d'une bibliothèque.

À long terme, l'équipe Windows promeut le passage à l'ICU, donc à un moment donné, ICU sera l'histoire de la mondialisation, et nous ne sommes qu'un mince emballage autour des bibliothèques de systèmes d'exploitation.

que va-t-il se passer lorsqu'un .NET 5 appelle un objet défini dans une bibliothèque .NET Standard 2.0 qui manipule une chaîne qui lui est transmise depuis le site d'appel .NET? L'ancien comportement est-il utilisé ou le nouveau comportement?

Le nouveau comportement sera utilisé, car il dépend du runtime et .NET Standard n'est qu'un standard à implémenter pour les runtimes. Cependant, notez que cela apporte une cohérence de plate-forme entre Unix et Windows, si vous exécutez ces mêmes bibliothèques qui préoccupent les gens sous Unix, vous obtiendrez le résultat ICU car ICU est le support bibliothèque sous Unix.

@reflectronic a raison dans tous ses points https://github.com/dotnet/runtime/issues/43736#issuecomment -716681586.

pour commenter les résultats @jbogard mentionnés ici https://github.com/dotnet/runtime/issues/43736#issuecomment -716527590, vous pouvez simplement résumer les résultats en comparant le comportement linguistique entre Windows et ICU. À propos, ICU est maintenant utilisé par de nombreuses applications sous Windows et son utilisation devrait augmenter. En outre, ces résultats excluent Linux avec .NET Core 3.1 et versions antérieures. qui montrera la cohérence entre .NET 5.0 et les versions précédentes sur Linux.

Le point concernant la bibliothèque qui fonctionnait auparavant sera rompu, ce n'est pas tout à fait vrai car ces bibliothèques étaient déjà cassées sous Linux.

Envisageriez-vous d'apporter une modification de rupture pour également faire de StringComparison.Ordinal la stratégie de comparaison par défaut?

J'ai mentionné plus tôt que nous avions déjà essayé cela auparavant, mais la taille des plaintes était très grande et nous ne pouvions pas l'appliquer. Nous recherchons des moyens d'aider les développeurs à être conscients de la spécification des indicateurs de comparaison de chaînes lors de l'appel des API de classement.

Je crée des bibliothèques pour gagner ma vie, pas des applications. Je préférerais avoir un moyen de gérer cela au moment de la compilation.

Oui, certains analyseurs peuvent vous aider dans ce cas. regardez généralement les appels des API de classement et regardez lequel ne montrait pas l'intention d'utiliser une opération ordinale ou linguistique.

Comment un analyseur peut-il aider pour les projets existants?

Les analyseurs analysent le code et détectent les appels des API de classement qui ne transmettent pas les indicateurs aideraient à examiner ces appels et à résoudre le problème en cas de détection.

c'est aussi une chose uniquement C #.

La modification est à l'intérieur du runtime .NET qui doit être global et non limité à C #.

@tarekgh Comment un analyseur C # se manifeste-t-il dans une base de code VB ou F #?

La plupart du travail que nous faisons est de InvariantCulture , ce qui, selon ma compréhension précédente, est censé être immuable par conception.

De manière critique, avant ce changement, il était impossible de dépendre réellement de cela pour le code multiplateforme; ce que NLS et ICU se comportent, même lors de l'utilisation de la culture invariante, ne sont pas toujours les mêmes (comme en témoigne ce problème). Comme @tarekgh l'a dit, ce code a agi différemment sous Linux pendant tout ce temps. Maintenant que cette modification a été apportée, pour toute installation Windows à jour, ce que signifie «culture invariante» devrait _en fait_ être cohérent sur toutes les plates-formes.

Cela peut paraître surprenant, mais nous avons finalement trouvé et corrigé des dizaines de bogues liés aux différences de plate-forme au fil des ans à la suite de rapports d'utilisateurs, comme je suis sûr que de nombreux autres auteurs de bibliothèques l'ont fait pendant des années et des années.

Je ne suis tout simplement pas enthousiasmé par la perspective que .NET 5 introduise une nouvelle récolte d'inconnues inconnues et revisite ces correctifs non seulement pour mes bibliothèques, mais aussi pour nos dépendances en aval. C'est un coût économique important pour nous qui ne crée pas de nouvelles améliorations de productivité pour nos utilisateurs. Quelqu'un au sein de MSFT devrait tenir compte de cette question dans sa discussion sur les coûts / avantages de ce changement.

Edit: comme, j'ai déjà les mérites techniques - oui, merci. Veuillez aider à vendre les avantages économiques non évidents de cette modification par opposition aux coûts. Pourquoi les utilisateurs devraient-ils quand même faire le saut et passer à .NET 5 étant donné ce que ce problème semble être un nid de rat?

@tarekgh Comment un analyseur C # se manifeste-t-il dans une base de code VB ou F #?

Nous pouvons couvrir C # et VB avec un seul analyseur (nous nous efforçons de rendre nos analyseurs indépendants du langage dans la mesure du possible), mais nous ne pouvons pas obtenir la couverture de l'analyseur pour F # pour le moment.

@Aaronontheweb toute personne utilisant la fonctionnalité culturelle et supposant qu'elle ne changera pas est déjà cassée même si nous n'avons pas fait ces changements ICU. Regardez le blog https://docs.microsoft.com/en-us/archive/blogs/shawnste/locale-culture-data-churn de l'équipe Windows qui dit que même le comportement NLS est en train de changer pour des améliorations aussi. Donc, le problème ici n'est pas de passer à l'ICU plus que de simplement capturer de fausses hypothèses du point de vue des applications / bibliothèques. la mise à niveau vers la version 5.0 équivaut à la mise à niveau vers d'autres versions précédentes. Les applications recevront de nombreuses nouvelles fonctionnalités intéressantes et les applications doivent être testées, tout comme des changements majeurs entre les versions. Je ne considère pas que le changement de comportement de la mondialisation est vraiment cassant, car nous disons toujours que la mondialisation peut changer à tout moment entre les versions de système d'exploitation et de système d'exploitation. Comme indiqué précédemment, nous avons le commutateur de configuration pour choisir de passer à la version 5.0 en continuant à utiliser NLS. qui fera ICU n'est pas vraiment un facteur dans la décision de mise à niveau. Désormais, avec ICU, les applications auront la possibilité d'obtenir plus de cohérence entre les systèmes d'exploitation et pourront même avoir plus de contrôle sur le comportement de mondialisation si elles décidaient d'utiliser l'ICU local de l'application. Nous fournissons beaucoup plus de contrôle aux applications qu'auparavant.

En relation: https://github.com/dotnet/runtime/issues/43802

(Ce problème ne suit pas IndexOf _per se_. Il traite plutôt des conséquences inattendues de la routine Compare défaut sur un comparateur sensible à la culture.)

Comment un analyseur peut-il aider pour les projets existants?

Les analyseurs analysent le code et détectent les appels des API de classement qui ne transmettent pas les indicateurs aideraient à examiner ces appels et à résoudre le problème en cas de détection.

Je suppose que des analyseurs doivent être ajoutés au csproj.
Cela ne se produira pas automatiquement. Ainsi, de nombreux projets existants seront déplacés vers .NET 5 sans ces analyseurs.
De plus, comme déjà mentionné, cela n'aidera pas pour les projets F #.

@jeffhandley merci, cela confirme ce que j'ai déjà compris. Il est donc important de reconnaître que la «solution de contournement» possible n'aidera pas les utilisateurs de F # (un petit marché mais qui est néanmoins pleinement pris en charge par MS en tant que citoyen de première classe de .NET). Je n'ai aucune idée de ce que cela signifie:

La modification est à l'intérieur du runtime .NET qui doit être global et non limité à C #.

Je n'ai aucune idée de ce que cela signifie:
La modification est à l'intérieur du runtime .NET qui doit être global et non limité à C #.

Je voulais dire que tout langage utilisant le runtime .NET sera affecté et pas seulement C #.

Je vais à nouveau exprimer fermement mon opinion selon laquelle il devrait y avoir un analyseur prêt à l'emploi pour découvrir ces pièges dans le nouveau monde, surtout si le plan est de changer le comportement actuel.

Je comprends parfaitement les avantages techniques de le faire et je ne suggère pas de ne pas faire de changement (à long terme, il semble que ce soit la bonne décision). Je ne dis pas non plus que cela n’était pas déjà documenté en tant que meilleure pratique. Ce que je dis, c'est que nous avons vraiment besoin que ce soit une grosse erreur rouge et clignotante pour les développeurs qui tentent de passer à .NET 5. Hors de la boîte Les développeurs vont supposer que cela "fonctionne simplement" sinon.

À l'heure actuelle, vous pouvez utiliser cette bibliothèque Roslyn Analyzer de @meziantou pour trouver les zones affectées: https://github.com/meziantou/Meziantou.Analyzer/tree/master/docs.

Dans ce cas particulier, cela lèvera un

image

Cela étant dit, cela doit vraiment être hors de la boîte, j'ai ouvert ce problème Roslyn ici: https://github.com/dotnet/roslyn-analyzers/issues/4367

@tarekgh Merci d'avoir clarifié. Mon point initial était que les analyseurs ne sont pas la réponse ici si vous recherchez une solution qui fonctionne pour tous les utilisateurs .NET.

Nous cherchons plus de moyens pour aider les développeurs à être conscients lorsqu'ils appellent quelque chose comme IndexOf et à fournir intentionnellement des indicateurs StringComparison pour exprimer l'intention. Nous accueillons également toutes les idées que vous pourriez proposer.

Pourquoi ne pas abandonner les anciennes méthodes (en utilisant un attribut [Obsolete] )?

Qu'arrive-t-il à la compatibilité .NET Standard lorsque la surface de l'API est la même mais que le comportement change?

Un changement pour honorer les grappes de graphèmes ne me dérange pas, tant que l'impact est bien documenté. Un changement qui fait que les membres étroitement liés de la famille des méthodes de chaîne se comportent de manière incohérente les uns par rapport aux autres.

Quelqu'un qui fait du munging informel de chaînes, indifférent aux obscurités des caractères, des grappes de graphèmes ou des paramètres régionaux, le considérerait comme étant donné que si str.Contains (peu importe) réussit, il n'est pas nécessaire d'inspecter le résultat de str.IndexOf (peu importe ) parce qu'on vient de nous dire qu'il est là-dedans et qu'il peut donc être trouvé. Peu importe le deuxième paramètre dont ils ne se soucient pas de connaître le paramètre par défaut, car la valeur par défaut se comporte certainement de la même manière dans toutes les méthodes , ce qui les libère du besoin d'étudier toutes les subtilités pour les utiliser.

Des incohérences comme celle-ci produisent un langage qui ne peut être utilisé avec succès que par des experts et aliénent les gens qui sortent du camp du code. N'élevez pas la barre pour entrer de cette façon.

Je suis d'accord avec @lupestro. L'incohérence dans le comportement des méthodes est très préoccupante. Si vous avez des méthodes de longue date agissant à la fois différemment et de manière incohérente, il y aura beaucoup de tristesse. Les gens se heurteront à cela, ils se sentiront trahis par l'API et ensuite ils se demanderont quelles autres bombes à retardement attendent d'exploser. Beaucoup seraient des adoptés .NET écarteront C # pour ce genre de problème. Il semble que vous devriez soit supprimer les surcharges qui n'acceptent pas de paramètres régionaux, soit normaliser les paramètres régionaux par défaut pour les méthodes. Il existe déjà un Compare et CompareOrdinal, peut-être qu'un (Last) IndexOf (Any) et (Last) IndexOf (Any) Ordinal est nécessaire. Je n'aime pas cette solution mais au moins elle serait cohérente avec ce qui existe actuellement. Peut-être que l'idée d'utilisation ordinale dans la chaîne devrait être dépréciée. Si je dois choisir entre rapide ou bien, je choisirai «bien» à chaque fois. Les comportements incohérents et non intuitifs sont extrêmement frustrants.

Je vois que ce problème est déjà clos, donc je suppose qu'il est déjà décidé que cela ira de l'avant dans .NET 5.0. Je sais que ce truc est difficile et que les informations sur la culture (comme le temps) peuvent changer pour toutes sortes de raisons non techniques. Les développeurs doivent en être conscients, mais ils doivent également dépendre de leurs API pour être cohérents. Il devrait au moins y avoir un avertissement (en 5.0) comme indiqué par @aolszowka qui indique un problème ... et surtout pourquoi c'est un problème. Aller de l'avant est important et cela signifie parfois que vous devez «casser» d'anciens comportements / hypothèses. Cela n'implique pas que de nouvelles incohérences doivent être introduites. Ce changement brise les attentes ainsi que le code. S'il n'est pas possible de rendre les méthodes cohérentes, je préférerais de loin que CultureInfo soit forcé d'être explicite (que je pourrais ensuite adresser via une méthode d'extension) que d'avoir simplement la possibilité d'une erreur non intuitive faisant exploser mon code pendant un cycle de développement stressant ou pire chez un client.

TLDR: Changez les choses qui doivent changer, mais ne rendez pas l'API incohérente pour le faire. Si vous voulez le casser, remplacez-le par quelque chose de mieux.

J'utilise .NET depuis des années et toujours par défaut sur InvariantCulture lorsque j'utilise ces fonctions. En ce qui concerne les langues autres que l'anglais, j'ai toujours été conscient des paires de caractères qui fonctionnent comme des alias pour d'autres lettres spécifiques à une langue, et du travail supplémentaire qui consiste à vérifier ces paires lors de comparaisons avec CurrentCulture par défaut. Cela m'a mordu, par exemple, lors de l'écriture de code ASP.NET qui définit la CurrentCulture du thread en fonction de la langue préférée de l'utilisateur envoyée par le navigateur, et les comparaisons pour un utilisateur n'utilisant pas l'anglais font que le code se brise de manière subtile, en particulier lorsque les chaînes contiennent un mélange de texte saisi par l'utilisateur (linguistique) et ordinal.

Ce comportement de cluster de graphèmes Unicode est nouveau pour moi, car je m'attendais à un comportement ordinal pour les sauts de ligne et les retours chariot, même si j'avais utilisé des surcharges invariantes comme je le fais généralement. Il me semble que le comportement qui à la fois fait plus de travail et nécessite des connaissances d'experts devrait être un comportement opt-in, indépendamment de la droiture technique. Peut-être que ce navire a navigué il y a longtemps, mais _ce changement de rupture_ devrait être effectué de manière aussi transparente que, par exemple, des changements de langue, sans avoir à lire un blog obscur. Mes collègues sont à peine au courant des nouvelles fonctionnalités de C # 9.0, sans parler de certaines règles obscures de l'ICU qui peuvent avoir un impact sur le code qu'ils écrivent aujourd'hui et qui pourrait un jour être porté et compilé dans .NET 5 (ou, plus probablement, .NET 6 ou 7).

Nous envisageons de rendre obsolètes les anciennes méthodes. J'ai terminé une ébauche de la proposition hier soir et je la recherche pour une révision interne, et je la publierai comme un nouveau numéro ici dans quelques heures.

Le projet est publié sur https://github.com/dotnet/runtime/issues/43956. Il répertorie plusieurs alternatives pour les voies possibles (y compris ne rien faire) et pèse les avantages et les inconvénients de chaque approche. N'hésitez pas à y laisser vos commentaires sur les plans d'action proposés.

Si vous signalez un bogue, veuillez déposer un nouveau problème et utiliser ce nouveau problème pour décrire le bogue.

Si vous avez des commentaires sur ce problème particulier ( "\r\n" vs "\n" ), veuillez continuer à répondre dans ce fil de discussion. Merci!

Réouverture pendant que nous considérons les approches décrites dans le document de @GrabYourPitchforks .

Nous vous entendons - il y a beaucoup de commentaires clairs ici - nous travaillons à trouver la bonne voie à suivre, et nous garderons ce numéro à jour.

Un changement pour honorer les grappes de graphèmes ne me dérange pas, tant que l'impact est bien documenté. Un changement qui fait que les membres étroitement liés de la famille des méthodes de chaîne se comportent de manière incohérente les uns par rapport aux autres.

Quelqu'un qui fait du munging informel de chaînes, indifférent aux obscurités des caractères, des grappes de graphèmes ou des paramètres régionaux, le considérerait comme étant donné que si str.Contains (peu importe) réussit, il n'est pas nécessaire d'inspecter le résultat de str.IndexOf (peu importe ) parce qu'on vient de nous dire qu'il est là-dedans et qu'il peut donc être trouvé. Peu importe le deuxième paramètre dont ils ne se soucient pas de connaître est le paramètre par défaut, car la valeur par défaut est sûre de se comporter de la même manière dans toutes les méthodes, ce qui les libère du besoin d'étudier toutes les subtilités pour les utiliser.

Des incohérences comme celle-ci produisent un langage qui ne peut être utilisé avec succès que par des experts et aliénent les gens qui sortent du camp du code. N'élevez pas la barre pour entrer de cette façon.

Oui, cela exprimait totalement mes préoccupations. En tant que développeur chinois typique, nous mettons rarement StringComparison ou CultureInfo explicitement lors de l'appel de méthodes liées aux chaînes dans nos codes d'application, et cela fonctionne. Nous ne nous attendons pas à un comportement différent entre IndexOf et Contains !
.net 5.0
image
.net core 3.1
image
Framework .net
image

Je suis d'accord avec @lupestro. L'incohérence dans le comportement des méthodes est très préoccupante. Si vous avez des méthodes de longue date agissant à la fois différemment et de manière incohérente, il y aura beaucoup de tristesse.

Peut-être un point clé ici est que les deux méthodes ont toujours été incohérentes. Ils ne sont pas devenus soudainement incohérents dans .NET 5.0. Si je suis les choses correctement, IndexOf a toujours utilisé la comparaison de culture actuelle, Contains a toujours utilisé la comparaison ordinale. Accordé .NET 5.0 ajoute plus d'incohérence. Mais l'erreur ici était dans la conception originale de l'API qui permettait cette incohérence.

Si je suis les choses correctement, IndexOf a toujours utilisé la comparaison ordinale, Contains a toujours utilisé la comparaison de culture actuelle. Accordé .NET 5.0 ajoute plus d'incohérence. Mais l'erreur ici était dans la conception originale de l'API qui permettait cette incohérence.

C'est correct mais c'est l'inverse, IndexOf(string) utilise la culture actuelle, IndexOf(char) utilise Ordinal et Contains utilise l'ordinal.

Je vais élaborer brièvement sur les différences entre IndexOf et Contains auxquelles d'autres ont fait allusion récemment.

IndexOf(string) a toujours supposé la comparaison _CurrentCulture_, et Contains(string) a toujours supposé la comparaison _Ordinal_. Cet écart existait depuis longtemps dans .NET Framework. Il ne s'agit pas d'une nouvelle divergence introduite dans .NET 5. Par exemple, sur .NET Framework (qui utilise la fonction NLS de Windows), la ligature «æ» et la chaîne de deux caractères «ae» se comparent comme égales sous un comparateur linguistique. Cela entraîne la différence suivante:

// 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)

Cet écart existe depuis plus d'une décennie. Il est documenté et il existe des indications à ce sujet. Est-ce une erreur? Peut-être. Nous discutons sur https://github.com/dotnet/runtime/issues/43956 des moyens de rendre l'écosystème plus sain à l'avenir.

Pour ce problème spécifique, ce que nous nous demandons vraiment est: "Étant donné que l'écart existe depuis toujours, à quelle distance ces deux méthodes sont-elles autorisées à s'écarter _en pratique_ avant de nuire à l'écosystème plus large?" Je pense que nous essayons toujours de définir où devrait se situer ce seuil. Des rapports comme celui-ci sont _extrêmement_ utiles car ils nous donnent un aperçu des utilisateurs de ces API dans la pratique et des attentes des clients concernant leur comportement. En tant que gardiens du cadre, nous devons tenir compte non seulement de la documentation technique, mais également de la manière dont les applications du monde réel utilisent ces API.

Ce commentaire n'est pas vraiment destiné à défendre un point de vue particulier. Mon intention est de clarifier certaines idées fausses que j'ai vues et d'aider à expliquer comment nous avons défini le problème.

Même si InvariantCulture est spécifié, est-ce un comportement correct que \n ne correspond pas dans la version ICU?

image
image

Peut-être que le code suivant affiche 5 sous Linux et -1 sous Windows si nous utilisons git et ses paramètres par défaut (autocrlf = true)?

using System;

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

@ufcpp Oui, par ICU qui est le comportement attendu, comme indiqué précédemment dans ce fil. Les deux caractères <CR><LF> lorsqu'ils sont adjacents l'un à l'autre sont considérés comme une unité incassable à des fins linguistiques. La recherche de <LF> aide d'un comparateur linguistique (tel que _InvariantCulture_) ne produira aucune correspondance car elle diviserait cette unité incassable.

J'aimerais partager une mise à jour avec tout le monde: nous avons décidé de conserver ICU par défaut pour la version 5.0 GA. Nous allons essayer d'améliorer le piège de l'utilisation accidentelle de la comparaison linguistique lorsque l'ordinal était prévu, qui est suivi dans https://github.com/dotnet/runtime/issues/43956. Nous contacterons également certaines des bibliothèques que nous avons identifiées comme affectées par ce problème. Dans les jours à venir, nous partagerons plus de documents pour accompagner la prochaine version 5.0 qui aidera les gens à mieux identifier le code problématique et à le résoudre pour éviter ce problème.

C'était une décision très difficile à prendre et nous avons dû peser l'impact de la compatibilité sur l'écosystème par rapport à la normalisation entre les plates-formes.

Nous laisserons ce problème ouvert pour envisager d'autres mesures d'atténuation du problème \r\n lors de la maintenance s'il est déterminé que les atténuations actuelles sont insuffisantes.

Je voudrais également encourager les gens à continuer à signaler les problèmes qui sont rencontrés à la suite du commutateur ICU, même si le comportement peut être d'une cause connue, cela ne signifie pas qu'il est "par conception". Nous continuerons d'étudier les différences, d'en comprendre la cause et de déterminer si nous devons apporter des modifications à ICU ou .NET pour y remédier.

Il existe une règle d'analyse pour toujours spécifier StringComparison.

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

J'aimerais partager une mise à jour avec tout le monde: nous avons décidé de conserver ICU par défaut pour la version 5.0 GA. Nous allons chercher à améliorer l'écueil de l'utilisation accidentelle de la comparaison linguistique lorsque l'ordinal était prévu, qui est suivi dans # 43956. Nous contacterons également certaines des bibliothèques que nous avons identifiées comme affectées par ce problème. Dans les jours à venir, nous partagerons plus de documents pour accompagner la prochaine version 5.0 qui aidera les gens à mieux identifier le code problématique et à le résoudre pour éviter ce problème.

C'était une décision très difficile à prendre et nous avons dû peser l'impact de la compatibilité sur l'écosystème par rapport à la normalisation entre les plates-formes.

Nous laisserons ce problème ouvert pour envisager d'autres mesures d'atténuation du problème \r\n lors de la maintenance s'il est déterminé que les atténuations actuelles sont insuffisantes.

Je voudrais également encourager les gens à continuer à signaler les problèmes qui sont rencontrés à la suite du commutateur ICU, même si le comportement peut être d'une cause connue, cela ne signifie pas qu'il est "par conception". Nous continuerons d'étudier les différences, d'en comprendre la cause et de déterminer si nous devons apporter des modifications à ICU ou .NET pour y remédier.

J'aimerais savoir si je peux toujours utiliser les dépendances .NET Standard sur lesquelles je n'ai aucun contrôle.

Je soupçonne que je ne pourrai pas. Que diriez-vous de vous assurer que les gens font la transition vers, je ne sais pas, .NET Standard 2.2? Changer l'API?

Pour que je puisse savoir avec certitude que j'obtiens le comportement attendu.

Je ne sais pas comment nous pouvons éviter de briser l'écosystème actuel, qui a déjà sa juste part de problèmes.

Je serais heureux d'avoir tort, s'il vous plaît, faites-le avec préjugé - cela me soulagerait :)

@rcollina

J'aimerais savoir si je peux toujours utiliser les dépendances .NET Standard sur lesquelles je n'ai aucun contrôle.

Les bibliothèques .Net Standard sont généralement supposées être multiplateformes. Et si une bibliothèque fonctionne correctement sur .Net Core 3 sur Unix aujourd'hui (qui utilise ICU), alors elle fonctionnera presque certainement aussi sur .Net 5 sous Windows (qui utilise également ICU).

.NET Standard 2.2

.Net Standard vNext existe effectivement, bien qu'il soit appelé ".Net 5.0". (Par exemple, si vous écrivez une bibliothèque et que vous ne vous souciez pas de la prise en charge d'anciens frameworks, aujourd'hui, vous ciblerez .Net Standard 2.1. Dans un mois, vous cibleriez .Net 5.0 à la place.)

@svick je comprends. Je crois que je comprends déjà comment fonctionne .NET Standard. Je comprends que .NET 5 est le nouveau standard .NET, pour ainsi dire.

Je m'excuse, mais je ne suis toujours pas sûr de ce qui se passe lorsque je référence une bibliothèque .NET Standard 2.1 qui reposait sur une incohérence de comportement préexistante entre IndexOf et Contains.

Ce qui change ici, c'est quelque chose de hors bande, ICU contre NLS. Ce changement élargit l'écart que nous avions déjà et brise les attentes.
Il n'y a rien d'encodant ces informations dans des bibliothèques.

Je ne prétends pas comprendre toutes les implications, mais je ne peux pas me débarrasser du sentiment que nous sommes «techniquement corrects» sans un chemin d'intégration transparent vers la nouvelle norme. Ce qui est cruellement nécessaire.

Comme quelqu'un d'autre l'a mentionné, la plupart des gens ne sont même pas conscients des nouvelles fonctionnalités du langage, sans parler des changements sismiques tels que celui-ci découvert au hasard par les meilleurs membres de notre communauté. Quelles sont les chances que nous puissions résister à cela?

J'aimerais savoir si je peux toujours utiliser les dépendances .NET Standard sur lesquelles je n'ai aucun contrôle.

Ce qui change ici, c'est quelque chose de hors bande, ICU contre NLS. Ce changement élargit l'écart que nous avions déjà et brise les attentes.

Non, ce n'est pas le cas. On ne soulignera jamais assez ici que ICU a toujours été utilisé sous Unix. Les bibliothèques .NET Standard sont censées être portables de par leur conception et tout ce qui fonctionnait auparavant sous Linux .NET Core 3.x fonctionnera dans .NET 5.

La plupart du travail que nous faisons est InvariantCulture, qui, selon ma compréhension précédente, est censé être immuable par conception.

Pas vrai. InvariantCulture est uniquement censé ignorer le paramètre de langue de l'utilisateur. Il peut encore changer avec les mises à jour de la spécification Unicode ou des bibliothèques de globalisation, etc.

L'équipe .NET a-t-elle pris en compte la douleur, les problèmes et le coût que cela pourrait causer?

Des remarques comme celle-ci m'irritent sans fin. Tout code qui rompt soudainement avec ce changement était incorrect au départ. Comment la plate-forme est-elle censée évoluer si l'équipe .NET doit conserver le comportement d'un code utilisateur incorrect ou reposant sur des détails d'implémentation? Ce n'est pas comme s'ils n'avaient pas fourni de commutateur de compatibilité. Une grande partie de la raison pour laquelle .NET Core est sorti de .NET Framework était de résoudre ce problème grâce à des fonctionnalités telles que les installations côte à côte et les déploiements d'exécution locale de l'application. Si vous ne pouvez pas passer à .NET 5 à cause de cela, ne passez pas à .NET 5.

Je ne suis tout simplement pas enthousiasmé par la perspective que .NET 5 introduise une nouvelle récolte d'inconnues inconnues et revisite ces correctifs non seulement pour mes bibliothèques, mais aussi pour nos dépendances en aval.

Si vous avez éliminé tous vos bogues de différence de plate-forme comme vous le prétendez, vous ne devriez pas vous inquiéter.

L'équipe .NET a-t-elle pris en compte la douleur, les problèmes et le coût que cela pourrait causer?

Des remarques comme celle-ci m'irritent sans fin.

Des millions de projets s'exécutent sur .NET et la manipulation de chaînes est une opération très fréquente.
L'effort nécessaire à l'équipe .NET pour corriger / modifier ce problème est minime par rapport à l'effort nécessaire pour vérifier tout le code existant qui sera migré vers .NET 5/6.

Il est donc tout à fait juste de poser des questions sur le «plan» pour y remédier.
Y a-t-il un plan?
L'effet de ce changement a-t-il été estimé?
Est-ce 0,001% de tous les projets? Est-ce 75%?
Quels sont les autres changements similaires dont nous ne sommes pas au courant?

Peut-être que cela n'affecte qu'un petit nombre de projets.
Mais, était-il estimé?

BTW, je suis tout à fait pour la rupture des changements pour une raison suffisante. Mais nous avons également besoin d'un chemin de migration qui ne soit pas trop risqué.

@petarrepac Ne vous méprenez pas, je comprends cela. Mais comme cela a été souligné à plusieurs reprises dans ce fil:

  1. Il existe un plan, et il est de fournir un commutateur de configuration d'exécution.
  2. Le comportement prétendument cassant est le comportement existant de .NET sur toutes les plates-formes non Windows.
  3. Cela ne devrait affecter que le code qui effectue des opérations sensibles à la culture là où ordinal était prévu.

Compte tenu des deux derniers points, il est probablement raisonnable de supposer que cela affecte un pourcentage assez faible de projets.

À 100%, il est juste de poser des questions à ce sujet, mais les personnes qui écrivent des commentaires comme celui que j'ai cité supposent souvent qu'aucune considération n'a été prise et écrite avant d'essayer de comprendre la situation dans son ensemble derrière le changement.

Bonjour à tous. Nous voulions donner un bref résumé des actions que nous avons prises lorsque ce problème était ouvert et à la fin pourquoi nous avons décidé de conserver la valeur par défaut sur Windows 10 May 2019 Update ou version ultérieure pour être ICU pour .NET 5.0.

Lorsque le problème a été ouvert, nous avons entamé des discussions internes sur l'impact potentiel et la douleur que cela aurait pu avoir avec nos clients étant donné l'incohérence entre Contains(string) soit Ordinal et IndexOf(string) étant sensible à la culture, plus d'autres API étant sensibles à la culture par défaut lorsqu'elles fonctionnent sur une chaîne, mais étant Ordinal lorsqu'elles fonctionnent à plus de Span<char> ou ReadOnlySpan<char> . Ainsi, après des discussions sur ce problème, nous avons commencé à analyser les packages NuGet susceptibles d'être affectés et avons collecté des données pour obtenir une image claire. Voici nos résultats:

  • "\ n" est # 30 sur les "littéraux de chaîne les plus utilisés" passés à IndexOf, LastIndexOf, EndsWith, StartsWith, Compare et CompareTo.
  • 1% des sites d'appel vers String.EndsWith sont avec une chaîne de recherche commençant par \ n. N'importe lequel de ces sites d'appel où la chaîne testée contient des "fins de ligne de style Windows" serait interrompu.
  • Il existe 2040 ID de package hébergés sur NuGet.org où une version contient un assembly avec un site d'appel à risque.
  • La grande majorité de ces sites d'appel sont à EndsWith et IndexOf, ce qui est cohérent avec l'hypothèse selon laquelle ce modèle est souvent utilisé pour les algorithmes de saut de ligne naïfs.

Parmi ces 2040 packages qui avaient un site d'appel à risque, seuls 539 prennent en charge une version de .NET Standard ou .NET Core, ce qui signifie que seuls 0,54% des packages répertoriés dans NuGet.org sont susceptibles d'être exposés à la rupture.

Nous avons examiné les packages dans la liste des 539 identifiants de package potentiellement affectés pour avoir une idée de leur impact réel. Nous avons regardé le top 70 (par nombre de téléchargements), 20 n'ont pas exposé le modèle dans la dernière version, parmi ceux qui ont exposé le modèle, nous ne pouvions regarder que 32 qui avaient une licence permise:

  • 14 n'étaient pas sujets au bogue
  • 13 étaient potentiellement cassés
  • 5 étaient incertains (il n'y avait aucune indication claire de ruptures en raison du codage défensif pour divers modèles de sauts de ligne ou d'autres atténuations).

Cela signifie donc que sur les 70 meilleurs packages téléchargés, seuls 18% ont été potentiellement affectés.

Ces pourcentages sont empilés et non par rapport au nombre total de packages sur NuGet.org, qui est de 229 536. Donc, si nous utilisions le nombre total de packages et le nombre total de téléchargements dans NuGet.org, nous examinerions 539 packages potentiellement affectés sur 229 536, soit 0,24%.

Et bien qu'il soit formidable pour nous d'analyser les bibliothèques, nuget ne représente qu'une petite fraction du code C # là-bas. Et même si quelqu'un possède le code, a) les bogues peuvent ne pas être faciles à localiser, et b) ils peuvent ne plus avoir la source.

Cependant, c'était une bonne source de données pour conclure que, bien que cela puisse être un changement de comportement très notable, c'est une rupture qui s'est déjà produite sous Unix lors de la lecture d'entrées pouvant contenir des fins de ligne Windows (ce qui n'est peut-être pas si courant).

Dans .NET Core et .NET 5+, nous nous efforçons d'assurer la cohérence entre les systèmes d'exploitation et, compte tenu de l'impact de ce changement, cela semblait être la bonne chose à faire. Nous nous soucions de la compatibilité et fournissons donc un commutateur d'exécution compatible pour que les gens puissent revenir au comportement hérité.

En outre, une conclusion des packages que nous avons pu inspecter, étant donné que le comportement est déjà différent sous Unix, nous avons vu beaucoup de programmation défensive contre ce problème, pour atténuer les pannes potentielles entre les systèmes d'exploitation.

Pour ajouter à cela, la mondialisation peut changer à tout moment car nous sommes un wrapper mince à travers le système d'exploitation, donc c'était la bonne chose pour le moment d'être simplement le même wrapper sur tous les systèmes d'exploitation que nous prenons en charge.

Dans ce cadre, nous avons amélioré notre documentation avec des exemples pratiques, des règles d'analyse Roslyn et la manière dont le code affecté peut atténuer ce problème.

Merci pour tous vos précieux commentaires car cela nous emmène toujours dans un meilleur endroit et nous essaierons de continuer à améliorer cette expérience pour .NET 6, comme indiqué sur: https://github.com/dotnet/runtime/issues/43956

Puisque nous comprenons la douleur que cela peut causer en raison des différences entre les fins de ligne sous Unix et Windows, nous gardons ce problème ouvert et nous étudierons un moyen possible d'atténuer le cas \r\n pour .NET 5.0 .x qui devrait faire partie d'une version de maintenance.

Il y a aussi une différence avec char et string:

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

@mattleibow lors de l'utilisation de la recherche de caractères, il effectue une recherche ordinale. Le doc https://docs.microsoft.com/en-us/dotnet/api/system.string.indexof?view=net-5.0#System_String_IndexOf_System_Char_ qui indique This method performs an ordinal (culture-insensitive) search . Si vous effectuez la recherche de chaîne avec l'option ordinal, vous obtiendrez le même résultat que le caractère.

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

~ Il semble que CA1307 ne se déclenche que sur indexof(char) mais pas sur indexof(string) . Existe-t-il une règle pour la version string ? Cela semble plus important car la valeur par défaut de char utilise automatiquement l'ordinal et le changement de rupture n'affecte pas vraiment cela, tandis que le comportement de string a changé et vous devez spécifier l'ordinal pour restaurer le comportement dans certains cas. ~

~ Comment détectez-vous indexof(string) ? ~

Je l'ai trouvé, c'est la règle CA1310. Nos documents sont erronés pour https://docs.microsoft.com/en-us/dotnet/standard/globalization-localization/globalization-icu#use -nls-au lieu-of-icu et ne mentionnent pas cette variation spécifique. Je mettrai à jour ces documents.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

btecu picture btecu  ·  3Commentaires

GitAntoinee picture GitAntoinee  ·  3Commentaires

Timovzl picture Timovzl  ·  3Commentaires

yahorsi picture yahorsi  ·  3Commentaires

jzabroski picture jzabroski  ·  3Commentaires