2016-01-12 18 views
21

私はこの奇妙な問題に気づいた。 間違っ結果のように私には思えるString.Startsアジア言語で作業していない?

string line = "Mìng-dĕ̤ng-ngṳ̄"; 
string sub = "Mìng-dĕ̤ng-ngṳ"; 
line.Length 
15 
sub.Length 
14 
line.StartsWith(sub) 
false 

:このベトナム(Google Translateによる)の文字列をチェックしてください。だから私は文字列char-by-charを比較する私のカスタムStartWith関数を実装しています。

public bool CustomStartWith(string parent, string child) 
{ 
    for (int i = 0; i < child.Length; i++) 
    { 
     if (parent[i] != child[i]) 
      return false; 
    } 
    return true; 
} 

そして、私は推測するように、ここで何が起こっている

CustomStartWith("Mìng-dĕ̤ng-ngṳ̄", "Mìng-dĕ̤ng-ngṳ") 
true 

この機能を実行した結果!これはどのように可能ですか?

+3

は、インバリアントカルチャを使用してください。例えば、http://stackoverflow.com/q/492799/1364007を参照してください。 –

+6

私はベトナム語を知らない。最後の 'u'の上に線があります。それはそれを別の手紙にしませんか? *編集:*私は長さを印刷していることを忘れていました。これは、その行が別の*文字と見なされているようです。 –

+4

'StartsWith()'がfalseを返すべきであると私には思われます。(Jonathonが指摘するように) 'line'は実際に' sub'で始まらないからです。 –

答えて

37

StartsWithによって返された結果は正しいです。デフォルトでは、ほとんどの文字列比較メソッドは、プレーンなバイトシーケンスではなく、現在のカルチャを使用してカルチャに敏感な比較を実行します。 lineは、subと同じバイトシーケンスで始まりますが、それが表す部分文字列はほとんどの(またはすべての)カルチャでは同等ではありません。

あなたは本当にオーバーロードを使用、プレーンなバイト列として文字列を扱うの比較をしたい場合:あなたは比較は大文字と小文字を区別しないようにしたい場合は

line.StartsWith(sub, StringComparison.Ordinal);      // true 

を:

line.StartsWith(sub, StringComparison.OrdinalIgnoreCase);    // true 

はここよりますおなじみの例:

var line1 = "café"; // 63 61 66 E9  – precomposed character 'é' (U+00E9) 
var line2 = "café"; // 63 61 66 65 301 – base letter e (U+0065) and 
         //     combining acute accent (U+0301) 
var sub = "cafe"; // 63 61 66 65 
Console.WriteLine(line1.StartsWith(sub));        // false 
Console.WriteLine(line2.StartsWith(sub));        // false 
Console.WriteLine(line1.StartsWith(sub, StringComparison.Ordinal)); // false 
Console.WriteLine(line2.StartsWith(sub, StringComparison.Ordinal)); // true 

上記の例では、line2subと同じバイトシーケンスで始まり、最後にに適用される鋭いアクセント(U + 0301)の結合が続きます。 line1é(U + 00E9)にprecomposed characterを使用しているため、そのバイトシーケンスはsubのバイトシーケンスと一致しません。

実際には、cafecaféの部分文字列ではないと見なされます。 eおよびは異なる文字として扱われます。そのは、結果に影響を及ぼさないエンコードスキーム(Unicode)の内部実装の詳細であるeで始まる一対の文字として表されます。これは、上記の例では、caféおよびcaféを対照として示されています。特に序数(バイト単位)の比較を意図しない限り、異なる結果を期待することはありません。あなたの例には、この説明を適応

string line = "Mìng-dĕ̤ng-ngṳ̄"; // 4D EC 6E 67 2D 64 115 324 6E 67 2D 6E 67 1E73 304 
string sub = "Mìng-dĕ̤ng-ngṳ"; // 4D EC 6E 67 2D 64 115 324 6E 67 2D 6E 67 1E73 

各NETの文字は、その値が上記のコメントに示されているUTF-16コード単位を表します。最初の14個のコード単位は同一であるため、char-by-charの比較はtrueと評価されます(ちょうどStringComparison.Ordinalのように)。しかし、lineの15番目のコードユニットは、前のU+1E73)と組み合わされてṳ̄となる結合マクロン、◌̄(U+0304)です。

+5

しかし、StartsWithのデフォルト比較者は何ですか?また、IgnoreCaseはなぜOrdinalだけでなく使用するのですか?著者は大文字と小文字を無視せずに序数比較を使用します。 –

+3

@ VadimMartynov:「Ordinal」を使用するようにサンプルを更新しました。拡大説明。 – Douglas

+1

'line1.StartsWith(line2)'がtrueであることにも注目してください。また、 'line1.Equals(line2、StringComparison.InvariantCulture)' [CurrentCultureは最も合理的な設定です...単一引数EqualsはOrdinalを使用しているようです。 – Random832

9

これはバグではありません。実際にはString.StartsWithは、あなたの2つの文字列の文字単位のチェックよりもはるかにスマートです。あなたの現在の文化(言語設定など)を考慮し、収縮と特殊文字を考慮に入れます。 (あなたはṳ̄で終わるには2文字が必要であるとは気にしません。

これは、これらのカルチャ固有の設定をすべて取り入れずに、序数比較を使用してチェックしたい場合は、比較者にそれを伝える必要があることを意味します。

これは(Douglasが行ったように、ケースを無視していない!)それを行うための正しい方法である:

line.StartsWith(sub, StringComparison.Ordinal); 
+1

しかし、これらのカルチャー固有の設定を考慮に入れないのはなぜですか?私はベトナム語を話しません、これらの2つの文字列は接頭辞と等しいかどうかは分かりませんか? – Thilo

+0

それはOPになります。私はVietnamesのどちらかを知らないが、時には一方向と次回とを比較したいと思うことは理解できる。それで、あなたは 'Ordinal'を使って比較に影響を与えることができます。 –

+0

私は理解します。私が言っていることは、それが実際に行く方法であるかどうかを慎重に検討しなければならないということです。 – Thilo

関連する問題