Зачем проверять это!= 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? Я должен предположить, что есть цель, иначе это, вероятно, было бы поймано и удалено к настоящему времени.
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