Метод расширения и метод-член: почему каждый из них реализуется компиляторами по-разному (внутренне)?

Рассмотрим этот код:

 A a = null;
 a.f(); //Will it throw NullReferenceException?

Вызовет ли вышеприведенное NullReferenceException?

Ответ: это зависит от того, что такое f().

Это различие приводит к вопросу: как каждый тип метода реализуется и просматривается компиляторами C#? Кроме того, почему метод-член должен генерировать исключение, даже если он не обращается ни к каким данным-членам? Похоже, что компилятор C# заранее делает предположение, что метод-член будет обращаться к данным-членам, и поэтому выдает исключение, если объект имеет значение null, поскольку доступ к данным-членам с использованием нулевого объекта невозможен. Однако в случае метода расширения решение откладывается до тех пор, пока фактически не попытается получить доступ к данным участника, используя нулевую ссылку, только тогда выбрасывается исключение.

Насколько мое понимание верно? И если это так, то почему такая разница?

Да, я знаю, что если f() является методом расширения, то a.f() эквивалентно написанию AExt.f(a), поэтому последнее не должно генерировать исключение до тех пор, пока a не будет использоваться для доступа к элементу. Но мое внимание в основном сосредоточено на реализациях компилятора (которые могут одинаково реализовывать даже методы-члены).


person Nawaz    schedule 03.09.2011    source источник
comment
Как вы сказали, методы расширения - это просто синонимы.   -  person bevacqua    schedule 03.09.2011
comment
Как сказал Нико, это просто синтаксический сахар для обычных вызовов статических методов. Один пример, где это удобно: это позволяет определить метод .IsNullOrEmpty для строки, позволяя s.IsNullOrEmpty().   -  person Kirk Woll    schedule 03.09.2011
comment
@Kirk: s.IsNullOrEmpty() правильно? Разве это не должно быть string.IsNullOrEmpty(s)? Я думаю, что то, что вы написали, даже не скомпилируется!   -  person Nawaz    schedule 03.09.2011
comment
@Nawaz, будет, если вы определите свой собственный метод расширения IsNullOrEmpty(this string s).   -  person svick    schedule 03.09.2011
comment
@svick: И это мой вопрос. Почему это должно работать, и почему бы и нет, если это метод-член?   -  person Nawaz    schedule 03.09.2011
comment
@Nawaz, хотя он выглядит как метод экземпляра, реализован как статический метод. (просто посмотрите на сгенерированный IL - как будто концепция методов расширения даже не существует) Точечная нотация для элементов-экземпляров требует, чтобы фактический экземпляр члена имел какое-либо значение. Бессмысленно вызывать метод экземпляра без действительной ссылки this. То же самое совсем не верно для статического метода с нулевым первым аргументом. Синтаксис метода расширения перегружает значение точечной нотации, позволяя также связать специальные статические методы с типом.   -  person Kirk Woll    schedule 04.09.2011


Ответы (2)


Да, именно так ведет себя этот код (если метод расширения не проверяет наличие null и сам выдает исключение). Вы правы в том, что вызов невиртуального метода экземпляра для null может работать, если этот метод не обращается ни к каким полям экземпляра класса (прямо или косвенно).

Но язык дизайнеры посчитали, что это может сбить с толку, поэтому они удостоверились, что объект не null, с помощью инструкции callvirt IL.

С методами расширения это не так запутанно (никто не ожидает, что this будет null, но аргумент, объявленный this IEnumerable<TSource> source, должен быть проверен). И вы можете вызывать метод расширения как обычный статический метод, поэтому он все равно должен проверять null. Кроме того, оба способа вызова функции, вероятно, должны вести себя одинаково.

person svick    schedule 03.09.2011
comment
Мне бы очень хотелось, чтобы дизайнеры определили атрибут, чтобы сказать, что этот метод должен вызываться с нулевым значением this и делать что-то разумное; такой дизайн позволил бы неизменяемым типам классов, которые инкапсулируют значения, вести себя как типы значений [например. так что можно было бы сказать myString.IsNullOrEmpty, а не String.IsNullOrEmpty(myString)]. - person supercat; 07.12.2013
comment
@supercat Почему? Вы можете легко написать это как метод расширения. - person svick; 07.12.2013
comment
Методы расширения имеют странные правила области видимости и, особенно с точки зрения отражения, фактически существуют в отдельной вселенной от методов экземпляра. Если бы был способ объявить метод расширения внутри класса, это помогло бы, но, насколько мне известно, это не разрешено: методы расширения должны находиться в невложенном неуниверсальном статическом классе, поэтому любой тип или член типа, который используется методом расширения, должен быть видимым на этом уровне, независимо от того, будет ли метод публично раскрывать какие-либо детали типа или члена (или даже его существование). - person supercat; 07.12.2013

Методы экземпляра должны выдавать NullReferenceException, когда объект имеет значение null, потому что они по своей сути гарантируют, что объект доступен при выполнении этого метода. Думайте о методах экземпляра как о действии, которое выполняет экземпляр объекта, и когда сам объект не существует, то действие даже не может быть вызвано. Методы расширения — это просто синтаксический сахар, и компилятор сразу их транслирует, как вы упомянули выше AExt.f(a). Это всего лишь удобство, которое помогает лучше читать код.

Для случаев, когда вы хотите вызвать метод независимо от того, доступен экземпляр или нет, язык предоставляет статические методы в качестве функции. Как вы можете сказать, методы расширения являются статическими методами.

person Charles Prakash Dasari    schedule 03.09.2011