Я недавно столкнулся с той же проблемой: мне нужен IEqualityComparer<string>
, который ведет себя в стиле SQL. Я пробовал CollationInfo
и его EqualityComparer
. Если ваша БД всегда _AS (чувствительна к акценту), тогда ваше решение будет работать, но в случае, если вы измените параметры сортировки, которые AI или WI или что-то еще " нечувствителен, иначе хеширование сломается.
Зачем? Если вы декомпилируете Microsoft.SqlServer.Management.SqlParser.dll и загляните внутрь, вы обнаружите, что CollationInfo
внутренне использует CultureAwareComparer.GetHashCode
(это внутренний класс mscorlib.dll) и, наконец, выполняет следующее :
public override int GetHashCode(string obj)
{
if (obj == null)
throw new ArgumentNullException("obj");
CompareOptions options = CompareOptions.None;
if (this._ignoreCase)
options |= CompareOptions.IgnoreCase;
return this._compareInfo.GetHashCodeOfString(obj, options);
}
Как вы можете видеть, он может создавать один и тот же хеш-код для "aa" и "AA", но не для "aa" и "aa" (которые одинаковы, если вы игнорируете диакритические знаки (AI) в большинстве культур, поэтому они должны иметь одинаковый хэш-код). Я не знаю, почему .NET API ограничивается этим, но вы должны понимать, откуда может возникнуть проблема.
Чтобы получить тот же хеш-код для строк с диакритическими знаками, вы можете сделать следующее: создать реализацию из IEqualityComparer<T>
, реализующую GetHashCode
, которая вызовет соответствующий CompareInfo
объект GetHashCodeOfString
через отражение, потому что этот метод является внутренним и не может использоваться напрямую. Но вызов его напрямую с правильным CompareOptions
даст желаемый результат:
Смотрите этот пример:
static void Main(string[] args)
{
const string outputPath = "output.txt";
const string latin1GeneralCiAiKsWs = "Latin1_General_100_CI_AI_KS_WS";
using (FileStream fileStream = File.Open(outputPath, FileMode.Create, FileAccess.Write))
{
using (var streamWriter = new StreamWriter(fileStream, Encoding.UTF8))
{
string[] strings = { "aa", "AA", "äå", "ÄÅ" };
CompareInfo compareInfo = CultureInfo.GetCultureInfo(1033).CompareInfo;
MethodInfo GetHashCodeOfString = compareInfo.GetType()
.GetMethod("GetHashCodeOfString",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(string), typeof(CompareOptions), typeof(bool), typeof(long) },
null);
Func<string, int> correctHackGetHashCode = s => (int)GetHashCodeOfString.Invoke(compareInfo,
new object[] { s, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace, false, 0L });
Func<string, int> incorrectCollationInfoGetHashCode =
s => CollationInfo.GetCollationInfo(latin1GeneralCiAiKsWs).EqualityComparer.GetHashCode(s);
PrintHashCodes(latin1GeneralCiAiKsWs, incorrectCollationInfoGetHashCode, streamWriter, strings);
PrintHashCodes("----", correctHackGetHashCode, streamWriter, strings);
}
}
Process.Start(outputPath);
}
private static void PrintHashCodes(string collation, Func<string, int> getHashCode, TextWriter writer, params string[] strings)
{
writer.WriteLine(Environment.NewLine + "Used collation: {0}", collation + Environment.NewLine);
foreach (string s in strings)
{
WriteStringHashcode(writer, s, getHashCode(s));
}
}
Вывод:
Used collation: Latin1_General_100_CI_AI_KS_WS
aa, hashcode: 2053722942
AA, hashcode: 2053722942
äå, hashcode: -266555795
ÄÅ, hashcode: -266555795
Used collation: ----
aa, hashcode: 2053722942
AA, hashcode: 2053722942
äå, hashcode: 2053722942
ÄÅ, hashcode: 2053722942
Я знаю, что это похоже на взлом, но после проверки декомпилированного кода .NET я не уверен, есть ли какая-либо другая опция в случае, если необходимы общие функциональные возможности.
Поэтому убедитесь, что вы не попадете в ловушку с помощью этого не совсем корректного API.
UPDATE:
Я также создал сущность с потенциальной реализацией "SQL-подобного компаратора" с использованием CollationInfo
.
Также следует уделить достаточно внимания , где искать «строковые ловушки» в вашей кодовой базе, поэтому, если сравнение строк, хэш-код, равенство должны быть изменены на «SQL-подобный сопоставлению», эти места равны 100% будет сломан, так что вам придется выяснить и осмотреть все места, которые могут быть сломаны.
ОБНОВЛЕНИЕ № 2:
Есть лучший и более чистый способ заставить GetHashCode () обрабатывать CompareOptions. Существует класс SortKey , который корректно работает с CompareOptions, и его можно получить с помощью
CompareInfo.GetSortKey (yourString, yourCompareOptions) .GetHashCode ()
Вот ссылка на исходный код .NET и его реализацию.