web-dev-qa-db-ja.com

この文字列の長さが文字列の文字数よりも長いのはなぜですか?

このコード:

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

出力:

Length a = 3
Length b = 4

どうして?私が想像できる唯一のことは、漢字の長さが2バイトであり、.Lengthメソッドがバイトカウントを返すことです。

144
weini37

他の誰もが表面的な答えを与えていますが、より深い理論的根拠もあります。「文字」の数は定義するのが難しい質問であり、計算するのに驚くほど高価ですが、長さのプロパティは高速です。

定義するのが難しいのはなぜですか?さて、いくつかのオプションがありますが、どれも他のオプションよりも実際に有効なものはありません。

  • コンピューターがその形式でデータを処理する必要があるため、コード単位(バイトまたはその他の固定サイズのデータ​​チャンク; C#とWindowsは通常UTF-16を使用して2バイトの断片の数を返す)の数は確かに関連しています多くの目的のために(たとえば、ファイルへの書き込みは、文字ではなくバイトを重視します)

  • Unicodeコードポイントの数はかなり簡単に計算できます(ただし、サロゲートペアの文字列をスキャンする必要があるためO(n))。また、テキストエディターにとっては問題になる可能性がありますが、実際は同じではありません。画面上に印刷された文字の数としてのこと(グラフェンと呼ばれる)。たとえば、いくつかのアクセント付き文字は、2つの形式で表すことができます。1つのコードポイント、または2つのポイントがペアになり、1つは文字を表し、もう1つは「パートナー文字にアクセントを加える」と言います。ペアは2文字ですか、それとも1文字ですか?これを支援するために文字列を正規化できますが、すべての有効な文字が単一のコードポイント表現を持つわけではありません。

  • 書記素の数でさえ、印刷された文字列の長さと同じではなく、これは他の要因の中でもフォントに依存します。また、多くのフォントで一部の文字がオーバーラップして印刷されるため(カーニング)、画面上の文字列の長さとにかく書記素の長さの合計と必ずしも等しいとは限りません!

  • 一部のUnicodeポイントは、従来の意味では文字ではなく、何らかの種類の制御マーカーです。バイトオーダーマーカーまたは右から左へのインジケーターのように。これらはカウントされますか?

要するに、文字列の長さは実際にはとてつもなく複雑な質問であり、それを計算するにはデータテーブルと同様に多くのCPU時間を要する可能性があります。

また、ポイントは何ですか?これらのメトリックが重要なのはなぜですか?まあ、あなただけがあなたの場合に答えることができますが、個人的には、それらは一般的に無関係であると思います。私が見つけたデータ入力の制限は、バイト制限によってより論理的に行われます。とにかく転送または保存する必要があるためです。表示サイズの制限は、表示側のソフトウェアがより適切に行われます。メッセージに100ピクセルがある場合、適合する文字数はフォントなどに依存しますが、データレイヤーソフトウェアでは認識されません。最後に、Unicode標準の複雑さを考えると、他の方法を試してみると、おそらくとにかくEdgeのケースでバグが発生するでしょう。

したがって、一般的な用途があまり多くないのは難しい質問です。コード単位の数は計算するのは簡単です-それは基礎となるデータ配列の長さです-そして、一般的なルールとして最も意味のある/有用な、簡単な定義です。

それが、bの長さが4である理由は、「ドキュメントがそう言っているから」という表面的な説明を超えているためです。

232
Adam D. Ruppe

String.Lengthプロパティの documentation から:

Lengthプロパティは、Unicode文字の数ではなく、このインスタンスの Char オブジェクトの数を返します。理由は、Unicode文字が複数の Char で表される可能性があるためです。 System.Globalization.StringInfo クラスを使用して、各 Char の代わりに各Unicode文字を操作します。

61
nanny

"A????C"のインデックス1のキャラクターは SurrogatePair です

覚えておくべき重要な点は、サロゲートペアが2-bit単一文字を表すことです。

このコードを試すと、Trueが返されます

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

Char.IsSurrogatePairメソッド(String、Int32)

true sパラメーターに位置indexおよびindex + 1の隣接文字が含まれ、位置indexの文字の数値の範囲がU + D800からU + DBFFの場合、および数値位置index + 1の文字の範囲はU + DC00からU + DFFFまでです。それ以外の場合は、false

これは String.Length プロパティでさらに説明されています:

Lengthプロパティは、nicode文字の数ではなく、このインスタンスのCharオブジェクトの数。を返します。理由は、Unicode文字が複数のCharで表される可能性があるためです。 System.Globalization.StringInfoクラスを使用して、各Charではなく各Unicode文字を操作します。

32
Habib

他の答えが指摘しているように、目に見える文字が3つあっても、それらは4つのcharオブジェクトで表されます。 Lengthが3ではなく4である理由です。

MSDNは

Lengthプロパティは、Unicode文字の数ではなく、このインスタンスのCharオブジェクトの数を返します。

ただし、本当に知りたいのが「Char」オブジェクトの数ではなく「テキスト要素」の数である場合は、 StringInfo クラスを使用できます。

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

このように各テキスト要素を列挙することもできます

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

文字列でforeachを使用すると、中央の「文字」が2つのcharオブジェクトに分割され、印刷結果が文字列に対応しなくなります。

23
dee-see

これは、 Length プロパティが、Unicode文字の数ではなく、charオブジェクトの数を返すためです。あなたの場合、Unicode文字の1つは複数のcharオブジェクト(SurrogatePair)で表されます。

Lengthプロパティは、Unicode文字の数ではなく、このインスタンスのCharオブジェクトの数を返します。理由は、Unicode文字が複数のCharで表される可能性があるためです。 System.Globalization.StringInfoクラスを使用して、各Charではなく各Unicode文字を操作します。

20
Yuval Itzchakov

他の人が言ったように、それは文字列の文字数ではなく、Charオブジェクトの数です。キャラクター ????コードポイントU + 20213です。値は16ビットchar型の範囲外であるため、サロゲートペアD840 DE13としてUTF-16でエンコードされます。

文字の長さを取得する方法は、他の回答で言及されました。ただし、Unicodeで文字を表現するには多くの方法があるため、注意して使用する必要があります。 「à」は、1文字の合成文字または2文字(発音区別記号)の場合があります。 Twitter の場合のように正規化が必要になる場合があります。

これを読んでください
すべてのソフトウェア開発者は、ユニコードと文字セットについて絶対的かつ積極的に知っておくべき絶対最小値(言い訳なし!)

10
phuclv

さて、.NetとC#では、すべての文字列は TF-16LE としてエンコードされます。 string は一連の文字として格納されます。各 char は、2バイトまたは16ビットのストレージをカプセル化します。

「紙または画面上」で単一の文字、文字、グリフ、シンボル、または句読点として表示されるものは、単一のテキスト要素と考えることができます。 nicode Standard Annex#29 UNICODE TEXT SEGMENTATION で説明されているように、各テキスト要素は1つ以上のコードポイントで表されます。コードの完全なリストは、 ここにあります です。

各コードポイントは、コンピューターによる内部表現のためにバイナリにエンコードする必要があります。前述のように、各 char は2バイトを格納します。 U+FFFF以下のコードポイントは、単一の char に格納できます。 U+FFFFより上のコードポイントはサロゲートペアとして保存され、2つの文字を使用して単一のコードポイントを表します。

推測できることがわかっているので、テキスト要素は1つ char として、2つの文字のサロゲートペアとして、またはテキスト要素が複数のコードポイントで単一の文字の組み合わせで表される場合に格納できますおよびサロゲートペア。それが十分に複雑ではなかったかのように、一部のテキスト要素は、説明されているようにコードポイントの異なる組み合わせで表すことができます in、Unicode Standard Annex#15、UNICODE NORMALIZATION FORMS


インタールード

したがって、レンダリング時に同じように見える文字列は、実際には異なる文字の組み合わせで構成できます。このような2つの文字列の序数(バイトごと)の比較は違いを検出しますが、これは予期しないまたは望ましくない可能性があります。

.Net文字列を再エンコードできます。同じ正規化フォームを使用するようにします。正規化されると、同じテキスト要素を持つ2つの文字列が同じ方法でエンコードされます。これを行うには、 string.Normalize 関数を使用します。ただし、いくつかの異なるテキスト要素は互いに似ていることを覚えておいてください。 :-s


それでは、質問に関連してこれはすべてどういう意味ですか?テキスト要素'????'は、単一のコードポイントU + 20213cjk統合表意文字拡張bで表されます。つまり、単一の char としてエンコードすることはできず、2つの文字を使用してサロゲートペアとしてエンコードする必要があります。これが、string bが1つ charstring aより長い理由です。

string内のテキスト要素の数を確実にカウントする必要がある場合(注意を参照)、このような System.Globalization.StringInfo クラスを使用する必要があります。

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

出力を与える、

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

予想通り。


警告

StringInfo および TextElementEnumerator クラスでのUnicodeテキストセグメンテーションの.Net実装は、一般に有用であり、ほとんどの場合、呼び出し元が期待する応答を生成します。ただし、 nicode Standard Annex#29、「テキストだけでは境界を明確に決定するのに十分な情報が常に含まれているとは限らないため、ユーザーの認識を一致させるという目標を常に満たすことはできません」

6
Jodrell

これは、length()U+FFFF以下のUnicodeコードポイントでのみ機能するためです。このコードポイントのセットは Basic Multilingual Plane (BMP)と呼ばれ、2バイトのみを使用します。

BMPの外側のUnicodeコードポイントは、4バイトのサロゲートペアを使用してUTF-16で表されます。

文字数(3)を正しくカウントするには、StringInfoを使用します

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