Ответ 1
Я предполагаю, что вы рассматривали реализацию .NET 3.5? Я считаю, что реализация .NET 4 немного отличается.
Однако у меня есть подозрительное подозрение, что это связано с тем, что можно вызвать даже методы виртуального экземпляра практически без нулевой ссылки. Возможно в IL, то есть. Я посмотрю, могу ли я создать IL, который будет называть null.Equals(null)
.
EDIT: Хорошо, вот какой-то интересный код:
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 17 (0x11)
.maxstack 2
.locals init (string V_0)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldnull
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
IL_000a: call void [mscorlib]System.Console::WriteLine(bool)
IL_000f: nop
IL_0010: ret
} // end of method Test::Main
Я получил это, скомпилировав следующий код С#:
using System;
class Test
{
static void Main()
{
string x = null;
Console.WriteLine(x.Equals(null));
}
}
... и затем разборки с помощью ildasm
и редактирования. Обратите внимание на эту строку:
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
Первоначально это был callvirt
вместо call
.
Итак, что происходит, когда мы его собираем? Ну, с .NET 4.0 мы получаем следующее:
Unhandled Exception: System.NullReferenceException: Object
reference not set to an instance of an object.
at Test.Main()
Хм. Что с .NET 2.0?
Unhandled Exception: System.NullReferenceException: Object reference
not set to an instance of an object.
at System.String.EqualsHelper(String strA, String strB)
at Test.Main()
Теперь, что более интересно... нам явно удалось попасть в EqualsHelper
, чего мы обычно не ожидали.
Достаточно строки... попробуйте сами реализовать ссылочное равенство и посмотрим, можем ли мы получить null.Equals(null)
для возврата true:
using System;
class Test
{
static void Main()
{
Test x = null;
Console.WriteLine(x.Equals(null));
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object other)
{
return other == this;
}
}
Те же процедуры, что и раньше, - разобрать, изменить callvirt
на call
, собрать и посмотреть его печать true
...
Обратите внимание, что, хотя другой отвечает на этот вопрос на С++, мы здесь еще более коварны... потому что мы вызываем виртуальный метод практически. Обычно даже компилятор С++/CLI будет использовать callvirt
для виртуального метода. Другими словами, я думаю, что в этом конкретном случае единственным способом для this
быть null является запись IL вручную.
EDIT: Я только что заметил кое-что... На самом деле я не называл правильный метод ни в одной из наших маленьких программ-примеров. Здесь вызов в первом случае:
IL_0005: call instance bool [mscorlib]System.String::Equals(string)
здесь вызов во втором:
IL_0005: call instance bool [mscorlib]System.Object::Equals(object)
В первом случае я назвал System.String::Equals(object)
, а во втором - называть Test::Equals(object)
. Из этого мы можем видеть три вещи:
- Вам нужно быть осторожным с перегрузкой.
- Компилятор С# испускает вызовы декларатору виртуального метода - не самое конкретное переопределение виртуального метода. IIRC, VB работает обратным образом.
-
object.Equals(object)
рад сравнить нулевую ссылку "this"
Если вы добавите немного консольного вывода в переопределение С#, вы увидите разницу - он не будет вызываться, если вы не измените IL, чтобы вызвать его явно, например:
IL_0005: call instance bool Test::Equals(object)
Итак, вот и мы. Развлечения и злоупотребления методами экземпляра на нулевых ссылках.
Если вы сделали это так далеко, вам также может понравиться мое сообщение в блоге о как типы значений могут объявлять конструкторы без параметров... в ИЛ.