Неожиданное поведение в С# generic method на .Equals
Почему метод Equals возвращает другой результат из общего метода? Я думаю, что здесь есть какой-то автоматический бокс, который я не понимаю.
Вот пример, который воспроизводит поведение с .net 3.5 или 4.0:
static void Main(string[] args)
{
TimeZoneInfo tzOne = TimeZoneInfo.Local;
TimeZoneInfo tzTwo = TimeZoneInfo.FindSystemTimeZoneById(tzOne.StandardName);
Console.WriteLine(Compare(tzOne, tzTwo));
Console.WriteLine(tzOne.Equals(tzTwo));
}
private static Boolean Compare<T>(T x, T y)
{
if (x != null)
{
return x.Equals(y);
}
return y == null;
}
Вывод:
False
True
Изменить: Этот код работает без каких-либо компромиссов:
private static Boolean Compare<T>(T x, T y)
{
if (x != null)
{
if (x is IEquatable<T>)
{
return (x as IEquatable<T>).Equals(y);
}
return x.Equals(y);
}
return y == null;
}
Followup: я подал ошибку через MS Connect и был исправлен как исправленный, поэтому это возможно будут исправлены в следующей версии .NET Framework. Если они станут доступными, я обновляю их подробнее.
PS: это, по-видимому, исправлено в .net 4.0 и более поздних версиях (смотря на разборку TimeZoneInfo в mscorlib).
Ответы
Ответ 1
TimeZoneInfo не переопределяет метод Object Equals, поэтому он вызывает значение по умолчанию Object Equals, которое, по-видимому, не работает должным образом. Я считаю это ошибкой в TimeZoneInfo. Это должно работать:
private static Boolean Compare<T>(T x, T y)
where T: IEquatable<T>
{
if (x != null)
{
return x.Equals(y);
}
return false;
}
Вышеупомянутое вызовет вызов Equals<T>
, который является методом, который вы вызывали выше (он неявно предпочел общий вызов, потому что он был более специфичен для типа параметра, чем Object Equals; однако внутри общего метода, он не мог быть уверенным, что такой общий Equals существует, поскольку не было никаких ограничений, гарантирующих это).
Ответ 2
FWIW, в mono 2.8+ оба возвращаемых значения False, выводя
False
False
Удивительно, csc.exe из VS2010 дает разные результаты, действительно выводящий:
False
True
Еще более интересно, проблема появляется не с созданным кодом IL, а с движком Framework/JIT;
- Выполнение MS-скомпилированного изображения с помощью Mono VM приводит к
False/False
, например, с помощью скомпилированной версии
- Выполнение Mono-скомпилированного изображения с помощью VM VM приводит к
False/True
, например, к скомпилированной версии MS
Для вашего интереса рассмотрим дизассемблирование компилятора Microsoft CSC.exe(csc.exe /optimize+ test.cs
):
.method private static hidebysig
default bool Compare<T> (!!T x, !!T y) cil managed
{
// Method begins at RVA 0x2087
// Code size 30 (0x1e)
.maxstack 8
IL_0000: ldarg.0
IL_0001: box !!0
IL_0006: brfalse.s IL_001c
IL_0008: ldarga.s 0
IL_000a: ldarg.1
IL_000b: box !!0
IL_0010: constrained. !!0
IL_0016: callvirt instance bool object::Equals(object)
IL_001b: ret
IL_001c: ldc.i4.0
IL_001d: ret
} // end of method Program::Compare
и компилятор Mono gmcs.exe(dmcs -optimize+ test.cs
):
.method private static hidebysig
default bool Compare<T> (!!T x, !!T y) cil managed
{
// Method begins at RVA 0x212c
// Code size 33 (0x21)
.maxstack 4
IL_0000: ldarg.0
IL_0001: box !!0
IL_0006: brfalse IL_001f
IL_000b: ldarga.s 0
IL_000d: ldarg.1
IL_000e: box !!0
IL_0013: constrained. !!0
IL_0019: callvirt instance bool object::Equals(object)
IL_001e: ret
IL_001f: ldc.i4.0
IL_0020: ret
} // end of method Program::Compare
Ответ 3
TimezoneInfo определяет его собственную перегрузку Equals (TimeZoneInfo). В методе сравнения используется объект equals (это вызов виртуального метода Object.Equals), тогда как в Console.WriteLine(tzOne.Equals(tzTwo)) вызывается перегруженный (новый) метод TimeZoneInfo.Equals.
TimeZoneInfo явно не переопределил метод Object.Equals правильно...