Що в C #, що відбувається, коли ви викликаєте метод розширення на нульовий об'єкт?


328

Чи називається метод з нульовим значенням або він дає нульове посилання виключення?

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

У такому випадку мені ніколи не доведеться перевіряти параметр 'this' на null?


Якщо, звичайно, ви маєте справу з ASP.NET MVC, який видасть цю помилку Cannot perform runtime binding on a null reference.
Mrchief

Відповіді:


386

Це буде добре (не виняток). Методи розширення не використовують віртуальні виклики (тобто він використовує інструкцію "call" il, а не "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
@Konrad: Це залежить від контексту. Компілятор C # зазвичай використовує callvirt навіть для невіртуальних методів, саме для отримання нульової перевірки.
Джон Скіт

Я мав на увазі різницю між інструкціями для виклику та callvirt il. В одній редакції я насправді спробував зафіксувати дві сторінки "Опкоди", але редактор заграв у посиланнях ...
Марк Гравелл

2
Я не бачу, як таке використання методів розширення може бути корисним. Тільки тому, що це можна зробити, це не означає, що це правильно, і, як Бінарний Вор'єр згадувався нижче, мені це виглядає, як мінімум, відхиленням.
Пастка

3
@Trap: ця функція чудова, якщо ви займаєтесь програмуванням у функціональному стилі.
Рой Тінкер

50

Доповнення до правильної відповіді від Марка Гравелла.

Ви можете отримати попередження від компілятора, якщо очевидно, що цей аргумент є нульовим:

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, щоб придушити попередження, щоб змусити код пройти збірку релізу.
Мартін Р.Л.

24

Як ви вже виявили, оскільки методи розширення є просто прославленими статичними методами, вони будуть викликатися з 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; 
  } 
}

Я також писав про це в блозі деякий час тому.


3
Проголосував це за те, що це правильно і має сенс для мене (і це добре написано), тоді як я також віддаю перевагу використанню, описаному у відповіді від @Marc Gravell.
qxotk

17

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

Якщо метод намагається отримати доступ до об'єкта без перевірки, чи недійсний він, то так, він викине виняток.

Хлопець тут написав "IsNull" та "IsNotNull" методи розширення, які перевіряють, чи посилання передано нульовим чи ні. Особисто я думаю, що це відхилення, і я не повинен був бачити світло дня, але це абсолютно дійсно c #.


18
Дійсно, для мене це як би запитати труп "Ти живий" і отримати відповідь "ні". Труп не може відповісти на жодне запитання, а також не можна мати можливість "викликати" метод на нульовому об'єкті.
Бінарний занепокоєння

14
Я не погоджуюся з логікою Бінарного Вор'є, оскільки це зручно в змозі викликати розширення, не турбуючись про нульові коефіцієнти, але +1 за значення комедії за аналогією :-)
Тім Абел

10
Насправді іноді ви не знаєте, чи хтось загинув, тому ви все ще запитуєте, і людина може відповісти: «ні, просто відпочиваючи з закритими очима»
nurchi

7
Коли вам потрібно зв'язати декілька операцій (скажімо, 3+), ви можете (припускаючи відсутність побічних ефектів) перетворити кілька рядків нудного кодового коду перевірки нуля на елегантний ланцюжок з однорядним вкладишем із "нульовими" методами розширення. (Подібно до запропонованого "?" - оператора, але, правда, не настільки елегантного.) Якщо це не очевидно, що розширення є "недійсним", я зазвичай префіксую метод "Safe", так що, наприклад, це копія- методу, його ім'я могло бути "SafeCopy", і воно поверне null, якщо аргумент буде null.
AnorZaken

3
Я так важко сміявся з відповіддю @BinaryWorrier хахахаха, я бачив, як я штовхаю тіло, щоб перевірити, чи він мертвий чи ні ха-ха. Так у моїй уяві хто перевіряв, чи тіло було мертве чи не я, а не саме тіло, реалізація перевірка була в мені, активно натискаючи на неї, щоб побачити, чи рухається вона. Тож організм не знає, мертвий він чи ні, ВООЗ перевіряє, знає, тепер ви можете стверджувати, що ви можете "підключити" до тіла спосіб, щоб він сказав вам, чи він мертвий чи ні, і що, на мою думку, це те, що розширення для.
Зоркінд

7

Як зазначали інші, виклик методу розширення на нульовий довідник призводить до того, що цей аргумент є нульовим, і нічого іншого особливого не відбудеться. Це породжує ідею використовувати методи розширення для написання захисних пропозицій.

Ви можете прочитати цю статтю для прикладів: Як зменшити цикломатичну складність: Застереження Застереження Коротка версія:

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

Метод Extension є статичним, тому якщо ви нічого не зробите з цим MyObject, це не повинно бути проблемою, швидкий тест повинен перевірити його :)


-1

Є кілька золотих правил, коли ви хочете, щоб ваші були читабельні та вертикальні.

  • Варто сказати від Ейфеля, що конкретний код, інкапсульований у метод, повинен працювати проти деякого введення, що код є працездатним, якщо дотримуються певних передумов і забезпечують очікуваний вихід

У вашому випадку - DesignByContract порушено ... ви будете виконувати певну логіку в нульовому екземплярі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.