This == null внутри метода экземпляра .NET - почему это возможно?
Я всегда думал, что невозможно, чтобы this
был пустым внутри тела метода экземпляра. Следуя простой программе, это доказывает, что это возможно. Это какое-то документальное поведение?
class Foo
{
public void Bar()
{
Debug.Assert(this == null);
}
}
public static void Test()
{
var action = (Action)Delegate.CreateDelegate(typeof (Action), null, typeof(Foo).GetMethod("Bar"));
action();
}
ОБНОВЛЕНИЕ
Я согласен с ответами, в которых говорится, что этот метод документирован. Однако я не понимаю этого поведения. Тем более, что это не так, как С#.
Мы получили отчет от кого-то (вероятно, одна из групп .NET используя С# (думал, что в то время он еще не был назван С#), который написанный код, который вызвал метод на нулевом указателе, но они didnt получить исключение, потому что метод не имел доступа к каким-либо полям (т.е. "this" был нулевым, но ничто в используемом методе). Тогда этот метод вызвал другой метод, который использовал эту точку, и бросил исключение, и последовало немного царапин на голове. После того, как они это поняли они отправили нам записку об этом. Мы думали, что возможность вызова метода в нулевом экземпляре бит странный. Питер Голде сделал несколько тестов, чтобы понять, всегда использовал callvirt, и он был достаточно мал, чтобы мы решили чтобы внести изменения.
http://blogs.msdn.com/b/ericgu/archive/2008/07/02/why-does-c-always-use-callvirt.aspx
Ответы
Ответ 1
Поскольку вы передаете null
в firstArgument
из Delegate.CreateDelegate
Итак, вы вызываете метод экземпляра для нулевого объекта.
http://msdn.microsoft.com/en-us/library/74x8f551.aspx
Если firstArgument является пустой ссылкой и метод является методом экземпляра, результат зависит от подписей типа типа делегата и Метод:
Если сигнатура типа явно включает скрытый первый параметр метода, делегат, как говорят, представляет собой открытый метод экземпляра. При вызове делегата первый аргумент в список аргументов передается параметру скрытого экземпляра Метод.
Если подписи метода и типа совпадают (т.е. все параметры типы совместимы), то делегат, как говорят, закрыт над null ссылка. Вызов делегата - это вызов вызова экземпляра метод на нулевом экземпляре, что не особенно полезно для сделать.
Ответ 2
Конечно, вы можете вызвать метод, если вы используете инструкцию IL вызова или подход делегата. Если вы попытаетесь получить доступ к полям-членам, вы получите эту нулевую ловушку, которая даст вам исключение NullReferenceException, которое вы искали.
попробовать
int x;
public void Bar()
{
x = 1; // NullRefException
Debug.Assert(this == null);
}
BCL даже содержит явно эти == null проверки, чтобы помочь отладки для языков, которые не используют callvirt (например, С#) все время. Подробнее см. question.
Класс String, например, имеет такие проверки. В них нет ничего загадочного, кроме того, что вы не увидите необходимости в таких языках, как С#.
// Determines whether two strings match.
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(Object obj)
{
//this is necessary to guard against reverse-pinvokes and
//other callers who do not use the callvirt instruction
if (this == null)
throw new NullReferenceException();
String str = obj as String;
if (str == null)
return false;
if (Object.ReferenceEquals(this, obj))
return true;
return EqualsHelper(this, str);
}
Ответ 3
Попробуйте выполнить документацию для Delegate.CreateDelegate()
в msdn.
Вы "вручную" вызываете все, и вместо этого вместо передачи экземпляра для указателя this
вы передаете значение null. Так что это может произойти, но вам нужно очень тяжело попробовать.
Ответ 4
this
является ссылкой, поэтому нет проблемы с его существованием null
с точки зрения системы типов.
Вы можете спросить, почему NullReferenceException
не было выбрано. Полный список обстоятельств, когда CLR выбрасывает это исключение документировано. Ваше дело не указано. Да, это callvirt
, но Delegate.Invoke
(см. Здесь), а не Bar
, и поэтому ссылка this
-полный делегат!
Поведение, которое вы видите, имеет интересное следствие для CLR. Делегат имеет свойство Target
(соответствует вашей ссылке this
), что довольно часто бывает null
, а именно, когда делегат статичен (представьте Bar
быть статическим). Теперь, естественно, есть частное поле поддержки для свойства, называемое _target
. Имеет ли _target
значение null для статического делегата? Нет, это не так. В нем содержится ссылка на сам делегат. Почему не null? Поскольку нуль является законной целью делегата, как показывает ваш пример, и CLR не имеет двух вариантов указателя null
, чтобы каким-то образом отличить статический делегат.
Этот бит тривиума демонстрирует, что с делегатами нулевые цели методов экземпляра не задумываются. Вы все еще можете задать конечный вопрос: но почему их нужно поддерживать?
У ранней CLR был амбициозный план стать, среди прочего, платформой выбора даже для присяжных разработчиков на С++, цель которой была достигнута сначала с Managed С++, а затем с С++/CLI. Некоторые слишком сложные функции языка были пропущены, но не было ничего сложного в том, что поддержка экземпляров экземпляров выполняется без экземпляра, что совершенно нормально в С++. Включая поддержку делегатов.
Конечным ответом является: потому что С# и CLR - это два разных мира.
Более хорошее чтение и еще лучше читать чтобы показать дизайн, позволяющий пустым экземплярам показывать его трассировки даже в очень естественных синтаксических контекстах С#.
Ответ 5
это ссылка на чтение в классах С#. Соответственно и, как и ожидалось, это можно использовать как любые другие ссылки (в режиме только для чтения)...
this == null // readonly - possible
this = new this() // write - not possible