25 августа 2014 г.

Частая ошибка №6: Путаница из-за методов расширения. Перевод.

Оглавление

Ранее я упомянул что выражения LINQ работают с любыми объектами, реализующими IEnumerable.
Например следующий простой пример суммирует баланс любой коллекции счетов:


В приведенном коде, тип параметра myAccounts определен как IEnumerable<Account>. Поскольку myAccounts ссылается на метод Sum (C# использует знакомую всем "точечную нотацию" для ссылки на метод класса или интерфейса), мы ожидаем увидеть метод Sum() в определении интерфейса IEnumerable<T>. Однако, определение IEnumerable<T> не имеет никаких методов Sum и и просто выглядит вот так:


Так где же определен метод Sum()? C# - строго типизированный язык, так что если ссылка на метод Sum была бы недействительной, компилятор C# сразу пометил бы это как ошибку. Поэтому мы знаем что этот метод должен существовать, но где? Более того, где определения всех остальных методов, которые предоставляет LINQ для запросов и агрегации коллекций?
Ответ заключается в том, что Sum() - это не метод определенный в интерфейсе IEnumerable. Это статический метод (называемый "метод расширения") , который определен в классе System.Linq.Enumerable:


Что же делает методы расширения отличными от других статических методов и что позволяет нам получать доступ к ним из других классов?
Отличительной особенностью методов расширения является модификатор this у первого параметра. Это то "волшебство", которое сообщает компилятору что это метод расширения. Тип параметра, к которому этот модификатор относится (в данном случае IEnumerable<TSource>) указывает на класс или интерфейс, в котором появится этот метод.
(Нет ничего "магического" в сходстве имени интерфейса IEnumerable и имени класса Enumerable, в котором определены методы расширения. Эта сходство - просто случайный стилистический выбор).

Теперь, вы можете так же видеть что функция sumAccounts, которую мы описали выше, может быть так же реализована следующим образом:


Тот факт, что мы можем сделать такую реализацию, поднимает вопрос о том зачем вообще нужны методы расширения? Методы расширения, по существу - удобство языка C#, которое позволяет вам "добавлять" методы к существующим типам без создания нового типа-наследника, перекомпиляции или других изменений исходного типа.

Методы расширения вводятся в контекст путем включения оператора using [namespace]; в начале файла. Вам необходимо знать, какое пространство имен содержит методы расширения которые вы ищите.
Когда компилятор C# встречает вызов метода в экземпляре объекта, и не находит определение этого метода в классе объекта, он смотрит на все методы расширения в текущем контексте чтобы попытаться найти тот, сигнатура и класс которого будут подходящими. Если такой метод найден, компилятор передаст ссылку на экземпляр объекта как первый аргумент метода расширения, а затем все оставшиеся, если они есть, будут переданы как последующие аргументы метода расширения. (Если компилятор C# не найдет подходящего метода в контексте, он выбросит исключение).

Методы расширения - пример "синтаксического сахара", со стороны компилятора C#, который позволяет нам писать код, который (обычно) более чистый и более сопровождаемый. В том случае если вы знаете о его использовании, в противном случае это может казаться немного запутанным, особенно поначалу.

Хотя у использования методов-расширения есть свои преимущества, они могут вызвать головные боли и трату времени для тех разработчиков, которые не правильно используют или не правильно понимают их. Это особенно верно если вы смотрите на примеры кода в сети или любой другой готовый код. Когда такой код порождает ошибки компиляции (потому что вызываются методы, которые не определены в классах, в которых они вызываются) существует тенденция думать что этот код применяется к другой версии библиотеки или вообще другой библиотеке. Много времени можно потратить на поиски новой версии или фантомной "потерявшейся библиотеки", которой не существует.
Даже разработчики знакомые с методами расширения могут иногда попадаться, когда есть метод с таким же названием в объекте, но немного отличающейся сигнатурой от метода расширения. Много времени может быть потрачено на поиск опечатки или ошибки, которой не существует.

Использование методов расширения в C# становится все более распространенным. В дополнение к LINQ, Unity Application Block и Web API framework - примеры двух, широко распространенных, современных библиотек от Microsoft, которые также используют методы расширения. И таких библиотек еще много. И чем современнее будет framework, тем больше вероятность что он будет включать в себя методы расширения.

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

Комментариев нет:

Отправить комментарий