NET 3.5: Практическое использование extension methods (исходники)Источник: GOT DOT NET
Что это такоеНаверняка все из нас сталкивались с желанием "дописать" что-либо к существующему классу. А если это класс библиотеки .NET, да к тому же ещё и sealed? Приходиться использовать классы типа "helper" ("помощник"), единственное назначение которых реализовать специальные методы необходимые для облегчения работы в какой-либо области. Любой более-менее серьёзный проект содержит такие классы. Очень часто такие классы обрастают новыми методами и кочуют из проекта в проект. Так было раньше. Посмотрим, что мы можем теперь. В качестве примера возьмём класс System.String. Посмотрите на следующий код:
Это старый вариант. Для подсчёта слов в строке мы используем метод WordCount() класса StringHelper. Теперь та же функциональность, но с использованием "методов-расширителей":
Если Вы ещё не сталкивались с этой новой возможностью, данный код Вас должен слегка обескуражить. И действительно, откуда у System.String появился метод WordCount()?! Ну а теперь обратим внимание на StringExtension. Объявление метода WordCount() слегка изменилось. Перед параметром str добавилось ключевое слово this. Это всё, что нужно чтобы наш метод стал "методом-расширителем". Теперь в любом месте кода, из которого видим класс StringExtension, мы можем использовать быстрый вариант вызова метода подсчёта строк. Казалось бы, всё чего мы добились, это более короткого способа вызова нашего метода. Это конечно так, но неужели этого мало? В конечном счёте, чем меньше нам (программистам) необходимо написать в коде, тем быстрее мы работаем. В общем, на этом можно было бы поставить точку и закончить статью. Но область применения этой технологии намного шире, что я и постараюсь показать в дальнейшем. А пока, несколько условий, ограничивающих применение "методов-расширителей".
Следует помнить, что значение экземпляра типа может быть равно "null". И соответственно, правило: В методах-расширителях не стоит забывать про проверку на "null". Ну и наконец, одна приятная "мелочь" - методы-расширения могут быть применены как к классам (class), так и к интерфейсам (interface) и перечислениям (enum). Практическое применениеС основами разобрались. Теперь посмотрим как это можно применить. Библиотеки расширенийПрежде всего, хочется обратить внимание на то, что данная возможность открывает возможность для создания принципиально новых библиотек - расширений существующих классов. Пока таких библиотек не много, но надеюсь эта ситуация временная. Пока же советую обратить внимание на UberUtils. Применение к интерфейсамРеализация LINQ в .NET 3.5 базируется именно на методах-расширителях. Причём применительно к интерфейсам. А применение методов-расширителей к интерфейсам даёт нам ещё один бонус. Часть общих методов можно вынести за пределы интерфейса и реализовать через методы-расширители. В результате реализация интерфейса будет более простой и понятной. К сожалению, в качестве действительно удачного примера в данном случае подойдёт только большой (для статьи) проект с несколькими реализациями интерфейса. Например, реализация LINQ :) Параметр - указатель типа == nullТ.к. ссылка на экземпляр типа передаётся как параметр, мы можем использовать методы-расширители даже если текущее значение экземпляра равно "null". Что это нам даёт? Вот пример:
Более короткий вариант написания, и соответственно, наш код пишется чуть быстрее. Параметр - указатель типа - константаМетоды-расширения можно использовать для константных значений. Область применения этого факта просто фантастическая. Например, как вам такой код:
Как несложно догадаться, реализуется это использованием расширения для System.Integer. О деталях реализации советую подумать самому, ну а для особо нетерпеливых укажу, что полный код расширения можно найти в статье "C# 3.0 - Hair extensions for wanna-be rubyists". Универсальный вызов обработчиков событийДовольно интересных результатов можно добиться, комбинируя generics методы и методы-расширители. Следующий пример представляет собой универсальное расширение для вызова обработчика событий (EventHandler) с проверкой на null.
Идея была опубликована в статье "Firing events with Extension Methods". Использование в качестве делегатовНебольшой, но приятный бонус - методы расширения можно использовать в качестве делегатов:
Локализация имён методовДовольно часто в бизнес-приложениях приходится использовать встроенную систему скриптов. Как известно, C# поддерживает Unicode имена для классов, методов и т.д. Ну а в условиях глобализации приложения иногда желательно поддерживать локализованные имена методов в скриптах. При использовании методов-расширений, это достигается довольно просто. Создаём сборку с локализованными именами необходимых методов. В скрипте прописываем строку вида:
и можем использовать локализованные имена. Конечно, этот метод не позволяет локализовать имена классов и свойств, но это всё же лучше чем ничего. Усложнение читабельности кодаКак ни странно, я ещё нигде не слышал о таком использовании методов-расширителей. Хотя одно из следствий их неправильного использования именно ухудшение читабельности кода. Кстати, это один из наиболее часто приводимых аргументов против их использования вообще. Моё личное мнение - плюсы применения очень перевешивают все минусы. Кроме того, возможность неправильного применения существует практически у всего. Однако это не причина для того, чтобы это "всё" запретить. Ну а теперь сама идея. В различных утилитах - обфускаторах, одна из наиболее востребованных функций - ухудшение читабельности кода. Методы-расширения дают более чем достаточные возможности для этого. Примеров не привожу специально, ибо о деталях область применения обязывает умолчать. Да и не входит это в рамки темы статьи. Несколько слов в заключение... или продолжениеКонечно, данная статья не может претендовать на всеобъемленность. Более того, в ней приведены только наиболее интересные (на мой взгляд) сценарии использования. А посему, буду рад новым идеям, комментариям и пожеланиям. И наиболее интересное из полученного, обещаю включить в новые версии статьи. Кстати, советую обратить внимание на комбинации с generic типами и константными указателями типа. |