Что происходит в C #, когда вы вызываете метод расширения для нулевого объекта?

329

Метод вызывается с нулевым значением или он дает исключение нулевой ссылки?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

Если это так, мне никогда не нужно будет проверять мой параметр this на null?

TPower
источник
Если, конечно, вы имеете дело с ASP.NET MVC, который выдаст эту ошибку Cannot perform runtime binding on a null reference.
Mrchief

Ответы:

387

Это будет работать нормально (без исключения). Методы расширения не используют виртуальные вызовы (т. Е. Он использует инструкцию il «call», а не «callvirt»), поэтому проверка на ноль не выполняется, если вы не пишете ее самостоятельно в методе расширения. Это на самом деле полезно в нескольких случаях:

public static bool IsNullOrEmpty(this string value)
{
    return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull<T>(this T obj, string parameterName)
        where T : class
{
    if(obj == null) throw new ArgumentNullException(parameterName);
}

и т.д

По сути, вызовы статических вызовов очень буквальны - т.е.

string s = ...
if(s.IsNullOrEmpty()) {...}

будет выглядеть так:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

где явно нет нулевой проверки.

Марк Гравелл
источник
1
Марк, вы говорите о «виртуальных» вызовах - но то же самое относится и к невиртуальным вызовам в методах экземпляра. Я думаю, что слово «виртуальный» здесь неуместно.
Конрад Рудольф
6
@ Конрад: это зависит от контекста. Компилятор C # обычно использует callvirt даже для не виртуальных методов, именно для получения нулевой проверки.
Джон Скит
Я имел в виду разницу между звонком и звонком иль инструкций. В одном редакторе я попытался создать две страницы с кодами операций, но редактор сорвался по ссылкам ...
Марк Гравелл
2
Я не понимаю, как такое использование методов расширения может быть полезным. То, что это можно сделать, не означает, что это правильно, и, как упоминалось ниже, Binary Worrier, для меня это выглядит как отклонение, если не сказать больше.
Ловушка
3
@Trap: эта функция хороша, если вы увлекаетесь программированием в функциональном стиле.
Рой Тинкер
50

Дополнение к правильному ответу от Марка Гравелла.

Вы можете получить предупреждение от компилятора, если очевидно, что аргумент this равен нулю:

default(string).MyExtension();

Хорошо работает во время выполнения, но выдает предупреждение "Expression will always cause a System.NullReferenceException, because the default value of string is null".

Стефан Штайнеггер
источник
32
Почему бы это предупредить "всегда вызывать исключение System.NullReferenceException". Когда на самом деле этого никогда не будет?
tpower
46
К счастью, мы, программисты, заботимся только об ошибках, а не о предупреждениях: p
JulianR
7
@JulianR: Да, некоторые делают, некоторые нет. В нашей конфигурации сборки выпуска мы рассматриваем предупреждения как ошибки. Так что это просто не работает.
Стефан Штайнеггер
8
Спасибо за примечание; Я получу это в базе данных ошибок, и мы посмотрим, сможем ли мы это исправить для C # 4.0. (Никаких обещаний - поскольку это нереалистичный угловой случай и просто предупреждение, мы могли бы попытаться его исправить.)
Эрик Липперт
3
@Stefan: Так как это ошибка, а не «истинное» предупреждение, вы можете использовать инструкцию #pragma, чтобы подавить предупреждение, чтобы код прошел вашу сборку релиза.
Мартин Р.Л.
25

Как вы уже обнаружили, поскольку методы расширения - это просто прославленные статические методы, они будут вызываться с nullпередаваемыми ссылками без NullReferenceExceptionвыброса. Но, поскольку они выглядят как методы экземпляра для вызывающей стороны, они также должны вести себя как таковые. Затем вы должны в большинстве случаев проверять thisпараметр и выдавать исключение, если оно есть null. Это нормально, если метод явно заботится о nullзначениях, и его имя указывает на это должным образом, как в примерах ниже:

public static class StringNullExtensions { 
  public static bool IsNullOrEmpty(this string s) { 
    return string.IsNullOrEmpty(s); 
  } 
  public static bool IsNullOrBlank(this string s) { 
    return s == null || s.Trim().Length == 0; 
  } 
}

Я также написал пост в блоге об этом некоторое время назад.

Jordão
источник
3
Проголосовал за это, потому что это правильно и имеет смысл для меня (и хорошо написано), а я также предпочитаю использование, описанное в ответе @Marc Gravell.
qxotk
17

Нуль будет передан в метод расширения.

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

Парень здесь написал методы расширения "IsNull" и "IsNotNull", которые проверяют, передана ли ссылка на ноль или нет. Лично я думаю, что это заблуждение и не должно было светить днем, но это совершенно справедливо для C #.

Двоичный Беспорядок
источник
18
Действительно, для меня это все равно, что спросить труп «Ты жив» и получить ответ «нет». Труп не может ответить ни на один вопрос, также вы не должны быть в состоянии «вызвать» метод для нулевого объекта.
бинарный беспорядок
14
Я не согласен с логикой Binary Worrier, так как это удобно для возможности вызывать расширения, не беспокоясь о нулевых ссылках, но +1 для значения комедии аналогии :-)
Тим Абелл
10
На самом деле, иногда вы не знаете, умер ли кто-то, поэтому вы все еще спрашиваете, и этот человек может ответить: «Нет, просто
отдыхаю
7
Когда вам нужно объединить несколько операций (скажем, 3+), вы можете (при условии отсутствия побочных эффектов) превратить несколько строк скучного стандартного кода проверки на нуль в элегантно соединенную строку с «нулевыми» методами расширения. (По аналогии с предложенным оператором «.?», Но, по общему признанию, не так элегантно.) Если не очевидно, что расширение является «нулевым», я обычно добавляю методу «Safe», поэтому, если, например, это копия Метод, его имя может быть «SafeCopy», и он будет возвращать нуль, если аргумент был ноль.
AnorZaken
3
Я так сильно смеялся над ответом @BinaryWorrier, хахахаха, я видел, как пинал тело, чтобы проверить, мертв ли ​​он или нет. Чек был во мне, активно пинал его, чтобы увидеть, если он движется. Итак, тело не знает, мёртв он или нет, ВОЗ проверяет, знает, теперь вы можете утверждать, что вы могли бы «подключить» к телу способ, чтобы он мог сказать вам, мёртв он или нет, и что, по моему мнению, это то, что Расширение для.
Зоркинд
7

Как отмечали другие, вызов метода расширения по нулевой ссылке приводит к тому, что аргумент this будет нулевым, и больше ничего особенного не произойдет. Это порождает идею использовать методы расширения для написания охранных предложений.

Вы можете прочитать эту статью для примеров: Как уменьшить цикломатическую сложность: Guard Clause Короткая версия:

public static class StringExtensions
{
    public static void AssertNonEmpty(this string value, string paramName)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Value must be a non-empty string.", paramName);
    }
}

Это метод расширения класса строки, который можно вызвать по нулевой ссылке:

((string)null).AssertNonEmpty("null");

Вызов работает нормально только потому, что среда выполнения успешно вызовет метод расширения по нулевой ссылке. Затем вы можете использовать этот метод расширения для реализации охранных предложений без беспорядочного синтаксиса:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }
Зоран Хорват
источник
3

Метод расширения является статическим, поэтому, если вы ничего не делаете с этим MyObject, это не должно быть проблемой, быстрый тест должен это проверить :)

Фредрик Лейон
источник
-1

Есть несколько золотых правил, когда вы хотите, чтобы они были читабельными и вертикальными.

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

В вашем случае - DesignByContract не работает ... вы собираетесь выполнить некоторую логику для нулевого экземпляра.

ruslander
источник