Por que o comparador de cadeias padrão falha em manter a consistência transitiva?
Eu conheço esse problemafoi observado antes, de forma mais ou menos concisa, mas ainda assim crio esse novo thread porque me deparei com o problema novamente ao escrever um teste de unidade.
A comparação de cadeias padrão (que é a comparação sensível a maiúsculas e minúsculas dependente da cultura que obtemos comstring.CompareTo(string)
, Comparer<string>.Default
, StringComparer.CurrentCulture
, string.Compare(string, string)
e outros) viola a transitividade quando as seqüências contêm hífens (ou sinais de menos, estou falando de caracteres simples U + 002D).
Aqui está uma simples reprodução:
static void Main()
{
const string a = "fk-";
const string b = "-fk";
const string c = "Fk";
Console.WriteLine(a.CompareTo(b)); // "-1"
Console.WriteLine(b.CompareTo(c)); // "-1"
Console.WriteLine(a.CompareTo(c)); // "1"
var listX = new List<string> { a, b, c, };
var listY = new List<string> { c, a, b, };
var listZ = new List<string> { b, c, a, };
listX.Sort();
listY.Sort();
listZ.Sort();
Console.WriteLine(listX.SequenceEqual(listY)); // "False"
Console.WriteLine(listY.SequenceEqual(listZ)); // "False"
Console.WriteLine(listX.SequenceEqual(listZ)); // "False"
}
Na parte superior, vemos como a transitividade falha.a
é menos do queb
eb
é menos do quec
, aindaa
falha em ser menor quec
.
Isso vai contra ocomportamento documentado do agrupamento Unicode que afirma que:
... para quaisquer cadeias A, B e C, se A <B e B <C, então A <C.
Agora classificando uma lista coma
, b
ec
é exatamente como tentar classificar as mãos de"Pedra", "Papel" e "Tesoura" no conhecido jogo intransitivo. Uma tarefa impossível.
A última parte do meu exemplo de código acima mostra que o resultado da classificação depende da ordem inicial dos elementos (e não há dois elementos na lista que comparem "igual" (0
)).
Linq'slistX.OrderBy(x => x)
também é afetado, é claro. Essa classificação deve ser estável, mas você obtém resultados estranhos ao solicitar uma coleção que contenhaa
, b
ec
junto com outras strings.
Eu tentei isso comtudo aCultureInfo
s na minha máquina (já que esse é um tipo dependente da cultura), incluindo a "cultura invariável", e cada um tem o mesmo problema. Eu tentei isso com o tempo de execução do .NET 4.5.1, mas acredito que as versões mais antigas têm o mesmo bug.
Conclusão: Ao classificar cadeias no .NET com o comparador padrão, os resultados são imprevisíveis se algumas cadeias contiverem hífens.
Quais alterações foram introduzidas no .NET 4.0 que causaram esse comportamento?
Já foi observado que esse comportamento é inconsistente nas diferentes versões da plataforma: no .NET 3.5, as seqüências de caracteres com hífens podem ser classificadas de maneira confiável. Em todas as versões da estrutura, chamarSystem.Globalization.CultureInfo.CurrentCulture.CompareInfo.GetSortKey
fornece únicoDeyData
para essas strings, então por que elas não são classificadas corretamente?