Зачем проверять это!= null?



иногда мне нравится тратить некоторое время на просмотр кода .NET, чтобы увидеть, как все реализуется за кулисами. Я наткнулся на этот камень, глядя на String.Equals метод через рефлектор.



C#



[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(object obj)
{
string strB = obj as string;
if ((strB == null) && (this != null))
{
return false;
}
return EqualsHelper(this, strB);
}


IL



.method public hidebysig virtual instance bool Equals(object obj) cil managed
{
.custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency, valuetype System.Runtime.ConstrainedExecution.Cer) = { int32(3) int32(1) }
.maxstack 2
.locals init (
[0] string str)
L_0000: ldarg.1
L_0001: isinst string
L_0006: stloc.0
L_0007: ldloc.0
L_0008: brtrue.s L_000f
L_000a: ldarg.0
L_000b: brfalse.s L_000f
L_000d: ldc.i4.0
L_000e: ret
L_000f: ldarg.0
L_0010: ldloc.0
L_0011: call bool System.String::EqualsHelper(string, string)
L_0016: ret
}


каково обоснование для проверки this против null? Я должен предположить, что есть цель, иначе это, вероятно, было бы поймано и удалено к настоящему времени.

565   6  

6 ответов:

я предполагаю, что вы смотрели на реализацию .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

я получил это путем компиляции следующего кода C#:

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...

обратите внимание, что хотя другой отвечает ссылки это C++ вопрос, мы здесь еще более коварны... потому что мы называем виртуальный метод не практически. Обычно даже компилятор C++/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). Из этого мы можем видеть три вещи:

  • вы должны быть осторожны с перегрузкой.
  • компилятор C# вызывает декларантом из виртуального метода-не самый специфический переопределить виртуальный метод. IIRC, VB работает наоборот путь
  • object.Equals(object) рад сравнить нулевую" эту " ссылку

если вы добавите немного консольного вывода в переопределение C#, вы можете увидеть разницу - он не будет вызван, если вы не измените IL, чтобы вызвать его явно, например:

IL_0005:  call   instance bool Test::Equals(object)

Итак, мы здесь. Забава и злоупотребление методами экземпляра на пустых ссылках.

если вы сделали это так далеко, вы также можете посмотреть на мой блог о как типы значений можете объявить конструкторы без параметров... в ил.

причина в том, что это действительно возможно для this на null. Существует 2 кода Il op, которые можно использовать для вызова функции: call и callvirt. Функция callvirt заставляет среду CLR выполнять проверку null при вызове метода. Инструкция вызова не делает и, следовательно, позволяет ввести метод с this будучи null.

звучит страшно? Действительно, это немного. Однако большинство компиляторов гарантируют, что этого никогда не произойдет. Этот.вызов инструкции является только когда выводится null это не возможность (я уверен, что C# всегда использует callvirt).

это не верно для всех языков, хотя и по причинам, которые я точно не знаю, команда BCL решила еще больше укрепить System.String класс В данном случае.

еще один случай, когда это может всплывающее окно находится в обратном pinvoke вызовов.

короткий ответ заключается в том, что такие языки, как C#, заставляют вас создавать экземпляр этого класса перед вызовом метода, но сама структура этого не делает. В CIL есть два разных способа вызова функции:call и callvirt.... Вообще говоря, C# всегда будет излучать callvirt, который требует this чтобы не быть null. Но другие языки (C++ / CLI приходит на ум) могут испускать call, который не имеет этой надежды.

(1okay, это больше похоже на пять, если вы графа Калли, newobj и т. д., Но давайте держать его простым)

The исходный код Это комментарий:

Это необходимо для защиты от обратного-pinvokes и других абонентов кто не использует инструкцию callvirt

давайте посмотрим... this Это первая строка, которую вы сравниваете. obj второй объект. Так что, похоже, это своего рода оптимизация. Это первый кастинг obj строкового типа. И если это не удастся, то strB имеет значение null. А если strB равно нулю, а this нет, тогда они точно не равны и

Если аргумент (obj) не приводит к строке, то strB будет null и результат должен быть false. Пример:

    int[] list = {1,2,3};
    Console.WriteLine("a string".Equals(list));

пишет false.

запомнить эту строку.Метод Equals () вызывается для любого типа аргумента, а не только для других строк.

Comments

    Ничего не найдено.