web-dev-qa-db-fra.com

Pourquoi la longueur de cette chaîne est-elle plus longue que le nombre de caractères qu'elle contient?

Ce code:

string a = "abc";
string b = "A????C";
Console.WriteLine("Length a = {0}", a.Length);
Console.WriteLine("Length b = {0}", b.Length);

les sorties:

Length a = 3
Length b = 4

Pourquoi? La seule chose que je puisse imaginer, c’est que le caractère chinois a une longueur de 2 octets et que le .Length La méthode retourne le nombre d'octets.

144
weini37

Tout le monde donne la réponse de surface, mais il existe une logique plus profonde aussi: le nombre de "caractères" est une question difficile à définir et peut être étonnamment coûteux à calculer, alors qu'une propriété de longueur devrait être rapide.

Pourquoi est-ce difficile à définir? Eh bien, il y a quelques options et aucune n'est vraiment plus valable qu'une autre:

  • Le nombre d'unités de code (octets ou autre bloc de données de taille fixe; C # et Windows utilisent généralement UTF-16 pour qu'il renvoie le nombre d'éléments de deux octets) est certainement pertinent, car l'ordinateur doit encore traiter les données sous cette forme. à de nombreuses fins (écrire dans un fichier, par exemple, se soucie des octets plutôt que des caractères)

  • Le nombre de points de code Unicode est assez facile à calculer (bien que O(n) car vous devez analyser la chaîne pour rechercher des paires de substitution)) et pourrait avoir de l'importance pour un éditeur de texte .... mais ce n'est pas le cas correspond au nombre de caractères imprimés à l’écran (appelés graphèmes). Par exemple, certaines lettres accentuées peuvent être représentées sous deux formes: un seul point de code ou deux points appariés, l’un représentant la lettre et l’autre "ajouter un accent sur ma lettre de partenaire ". La paire aurait-elle deux caractères ou un? Vous pouvez normaliser les chaînes pour vous aider, mais toutes les lettres valides ne possèdent pas une seule représentation de code.

  • Même le nombre de graphèmes n'est pas la même chose que la longueur d'une chaîne imprimée, ce qui dépend de la police, entre autres facteurs, et puisque certains caractères sont imprimés avec un certain chevauchement dans de nombreuses polices (crénage), la longueur d'une chaîne à l'écran n’est pas forcément égale à la somme de la longueur des graphèmes!

  • Certains points Unicode ne sont même pas des caractères au sens traditionnel, mais plutôt une sorte de marqueur de contrôle. Comme un marqueur d'ordre d'octet ou un indicateur de droite à gauche. Est-ce que ça compte?

En bref, la longueur d'une chaîne est en fait une question ridiculement complexe et son calcul peut prendre beaucoup de temps CPU et de tables de données.

De plus, quel est le point? Pourquoi ces métriques sont-elles importantes? Eh bien, vous êtes le seul à pouvoir répondre à cette question, mais personnellement, j’estime qu’elles ne sont généralement pas pertinentes. Je trouve que la limitation de la saisie de données se fait de manière plus logique par limite d'octet, car c'est ce qui doit être transféré ou stocké de toute façon. Il est préférable de limiter la taille d'affichage par le logiciel d'affichage: si vous avez 100 pixels pour le message, le nombre de caractères que vous adaptez dépend de la police, etc., qui de toute façon n'est pas connu du logiciel de couche de données. Enfin, étant donné la complexité de la norme Unicode, vous allez probablement avoir des bugs dans les cas Edge si vous essayez autre chose.

C'est donc une question difficile avec peu d'utilisation générale. Le nombre d'unités de code est simple à calculer - il ne s'agit que de la longueur du tableau de données sous-jacent - et le plus significatif/utile en règle générale, avec une définition simple.

C'est pourquoi b a une longueur 4 au-delà de l'explication superficielle de "parce que la documentation l'indique".

232
Adam D. Ruppe

De la documentation de la String.Length propriété:

La propriété Length renvoie le nombre d'objets Char dans cette instance, pas le nombre de caractères Unicode. La raison en est qu'un caractère Unicode peut être représenté par plusieurs Char . Utilisez la classe System.Globalization.StringInfo pour utiliser chaque caractère Unicode au lieu de chaque Char .

61
nanny

Votre personnage à l'index 1 dans "A????C" est un SurrogatePair

Le point clé à retenir est que les paires de substitution représentent des caractères uniques 32 bits .

Vous pouvez essayer ce code et il retournera True

Console.WriteLine(char.IsSurrogatePair("A????C", 1));

Méthode Char.IsSurrogatePair (String, Int32)

true si le paramètre s inclut des caractères adjacents aux positions index et index + 1 , ainsi que la valeur numérique du caractère aux plages d'index de position de U + D800 à U + DBFF, et la valeur numérique du caractère à l'indice de position +1 se situe entre U + DC00 et U + DFFF; sinon, false.

Ceci est expliqué plus en détail dans la propriété String.Length :

La propriété Length renvoie le nombre d'objets Char dans cette instance, pas le nombre de caractères Unicode. La raison en est qu'un caractère Unicode peut être représenté par plusieurs plus d'un car. Utilisez la classe System.Globalization.StringInfo pour utiliser chaque caractère Unicode au lieu de chaque caractère.

32
Habib

Comme les autres réponses l'ont souligné, même s'il y a 3 caractères visibles, ils sont représentés avec 4 objets char. C'est pourquoi le Length vaut 4 et non 3.

MSDN indique que

La propriété Length renvoie le nombre d'objets Char dans cette instance, pas le nombre de caractères Unicode.

Cependant, si ce que vous voulez vraiment savoir, c'est le nombre "d'éléments de texte" et non pas le nombre d'objets Char, vous pouvez utiliser la classe StringInfo .

var si = new StringInfo("A????C");
Console.WriteLine(si.LengthInTextElements); // 3

Vous pouvez aussi énumérer chaque élément de texte comme ceci

var enumerator = StringInfo.GetTextElementEnumerator("A????C");
while(enumerator.MoveNext()){
    Console.WriteLine(enumerator.Current);
}

Utiliser foreach sur la chaîne divisera la "lettre" du milieu en deux objets char et le résultat imprimé ne correspondra pas à la chaîne.

23
dee-see

En effet, la propriété Length renvoie le nombre d'objets char et non le nombre de caractères unicode. Dans votre cas, l'un des caractères Unicode est représenté par plusieurs objets char (SurrogatePair).

La propriété Length renvoie le nombre d'objets Char dans cette instance, pas le nombre de caractères Unicode. La raison en est qu'un caractère Unicode peut être représenté par plusieurs caractères. Utilisez la classe System.Globalization.StringInfo pour utiliser chaque caractère Unicode au lieu de chaque caractère.

20
Yuval Itzchakov

Comme d'autres l'ont dit, ce n'est pas le nombre de caractères de la chaîne, mais le nombre d'objets Char. Le personnage ???? est le point de code U + 20213. Comme la valeur est en dehors de la plage du type de caractère 16 bits, elle est codée en UTF-16 en tant que paire de substitution D840 DE13.

La manière d'obtenir la longueur en caractères a été mentionnée dans les autres réponses. Toutefois, il convient de l’utiliser avec précaution car il peut exister de nombreuses manières de représenter un caractère dans Unicode. "à" peut être composé de 1 caractère ou de 2 caractères (a + diacritiques). La normalisation peut être nécessaire, comme dans le cas de Twitter .

Vous devriez lire ceci
Le minimum absolu que chaque développeur de logiciel a absolument, absolument besoin de savoir sur Unicode et les jeux de caractères (sans excuses!)

10
phuclv

D'accord, en .Net et C #, toutes les chaînes sont codées comme TF-16LE . Un string est stocké sous la forme d'une séquence de caractères. Chaque char encapsule la mémoire de 2 octets ou 16 bits.

Ce que nous voyons "sur papier ou à l'écran" en tant que lettre, caractère, glyphe, symbole ou signe de ponctuation peut être considéré comme un élément de texte unique. Comme décrit dans la section Annexe Unicode standard n ° 29 SEGMENTATION DE TEXTE UNICODE , chaque élément de texte est représenté par un ou plusieurs points de code. Une liste exhaustive de codes peut être trouvée ici .

Chaque point de code doit être encodé en binaire pour une représentation interne par un ordinateur. Comme indiqué, chaque char stocke 2 octets. Points de code inférieurs ou égaux à U+FFFF peut être stocké dans un seul char . Points de code ci-dessus U+FFFF sont stockés en tant que paire de substitution, en utilisant deux caractères pour représenter un seul point de code.

Compte tenu de ce que nous savons maintenant que nous pouvons en déduire, un élément de texte peut être stocké sous la forme d'un char , sous forme d'une paire de substitution de deux caractères ou, si l'élément de texte est représenté par plusieurs points de code une combinaison de caractères simples et de paires de substitution. Comme si cela n’était pas assez compliqué, certains éléments de texte peuvent être représentés par différentes combinaisons de points de code comme décrit in, Annexe Unicode Standard # 15, FORMULAIRES DE NORMALISATION UNICODE .


Interlude

Ainsi, les chaînes qui se ressemblent lors du rendu peuvent en réalité être composées d'une combinaison différente de caractères. Une comparaison ordinale (octet par octet) de deux chaînes de ce type permettrait de détecter une différence, ce qui peut être inattendu ou indésirable.

Vous pouvez ré-encoder des chaînes .Net. afin qu'ils utilisent le même formulaire de normalisation. Une fois normalisées, deux chaînes avec les mêmes éléments de texte seront codées de la même manière. Pour ce faire, utilisez la fonction string.Normalize . Cependant, rappelez-vous que certains éléments de texte se ressemblent. : -s


Alors, qu'est-ce que tout cela signifie par rapport à la question? L'élément de texte '????' est représenté par le code unique U + 20213 extension des idéogrammes unifiés cjk b. Cela signifie qu'il ne peut pas être codé de manière unique char et doit être codé en tant que paire de substitution, à l'aide de deux caractères. C'est pourquoi string b est un char plus longtemps que string a.

Si vous avez besoin de compter de manière fiable (voir l'avertissement) sur le nombre d'éléments de texte dans un string, vous devez utiliser le System.Globalization.StringInfo classe comme ça.

using System.Globalization;

string a = "abc";
string b = "A????C";

Console.WriteLine("Length a = {0}", new StringInfo(a).LengthInTextElements);
Console.WriteLine("Length b = {0}", new StringInfo(b).LengthInTextElements);

donnant la sortie,

"Length a = 3"
"Length b = 3"

comme prévu.


Mise en garde

L’implémentation .Net de la segmentation de texte Unicode dans les classes StringInfo et TextElementEnumerator devrait être généralement utile et, dans la plupart des cas, donnerait une réponse attendue par l'appelant. Cependant, comme indiqué dans Annexe Unicode Standard 29, "L'objectif de faire correspondre les perceptions des utilisateurs ne peut pas toujours être atteint avec précision, car le texte seul ne contient pas toujours suffisamment d'informations pour définir des limites sans ambiguïté."

6
Jodrell

En effet, length() ne fonctionne que pour les points de code Unicode dont la taille est inférieure à U+FFFF. Cet ensemble de points de code est appelé plan multilingue de base (BMP) et utilise seulement 2 octets.

Les points de code Unicode situés en dehors de BMP sont représentés dans UTF-16 à l'aide de paires de substitution de 4 octets.

Pour compter correctement le nombre de caractères (3), utilisez StringInfo

StringInfo b = new StringInfo("A????C");
Console.WriteLine(string.Format("Length 2 = {0}", b.LengthInTextElements));