Добавлен , опубликован
Раздел:
Если вы находитесь на стадии обучения языку C# - данная статья как раз для вас! Она расскажет вам о том, как использовать одну из синтаксических фич языка - методы расширений.
Итак, для чего нужны методы расширений?
Что, ж, я рассмотрю использование подобной конструкции на конкретном примере, правда не самом правильном - этот пример выбран специально и устроен так, чтобы не задевать другие темы программирования и не мешать в голове понятия.

Пример

Так случилось, что в вашем проекте постоянно приходится доставать последний элемент из массивов типа int.
Чтобы доставать последний элемент массива array вам приходится каждый раз писать код на подобии следующего:
var last = 0;
if (array != null && array.Count != 0) 
    last = array[array.Length - 1];
В результате в переменной last хранится либо последний элемент массива, либо, если массив оказался пустым - хранится 0, как значение по умолчанию.
Вы долго и муторно вбиваете этот код при каждом использовании, пока однажды к вам в голову не приходит идея - "а почему бы мне не выделить написанное в отдельный метод"?
И вы создаете метод на подобии вот такого:
public static class IntArrayUtils 
{
    public static int GetLast(int[] array) 
    {
        var last = 0;
        if (array != null && array.Length != 0)
             last = array[array.Length - 1];
        return last;
    }
}
Который вызывается в коде вот так:
var last = IntArrayUtils.GetLast(array);
Согласитесь, уже короче? Но чего -то не хватает. Может, красоты?
Вот тут мы и подошли к методам расширений. Согласитесь, что вот так было бы лучше:
var last = array.GetLast();
Нам бы не пришлось указывать странный метод IntArrayUtils и данный метод выглядел бы в точь-в-точь как родной метод у int[], который, к сожалению мы добавить не можем.

Решение

Что же нужно изменить чтобы достичь такого результата? Все очень просто, к существующему методу мы добавим всего одно слово this перед первым параметром:
public static class IntArrayUtils 
{
    public static int GetLast(this int[] array) 
    {
        var last = 0;
        if (array != null && array.Length != 0)
             last = array[array.Length - 1];
        return last;
    }
}
В результате все экземпляры int[] в нашем коде обзавелись дополнительным методом GetLast().
Ваш код после такой простой манипуляции может вызываться двумя разными способами, которые я уже указывал выше.
Как стандартным, через статический класс:
var last = IntArrayUtils.GetLast(array);
Так и через экземпляр:
var last = array.GetLast();

Заметки

Немного тонкостей о методах расширений:
  • Метод расширения обязан быть частью static класса
  • Несмотря на то, что метод выглядит как метод экземпляра, он является статичным и при компиляции подменяется. Это обязывает вас делать проверку на null, чтобы не допустить ошибок.
  • Метод может иметь собственные Generic типы, как и любой другой метод. Это позволит вам сделать код более универсальным.
  • Увы, вы не сможете расширить этим статические классы, например добавить новый статический метод в класс Math. Только экземпляры, только хардкор.

Примеры использования

Несмотря на все удобство, методы расширений не стоит применять без повода - чрезмерная перенасыщенность ваших классов может плохо сказываться на читаемости и поддерживании вашего кода. Потому настоятельно советую использовать данный "сахар" с умом.
Код желательно обустраивать так, чтобы его было легко читать. Например:
text.SaveAsFile(file);
Среди стандартных библиотек данный способ используется для всех методов у массивов и перечислителей - методы ForEach, Select, Any, Where, Contains и так далее.
Очевидно, что подобное решение отлично подходит для случаев, в которые просто так метод не добавишь, либо этот метод весьма специфичен и требует возможностей static классов. Например, к таким относится удаление объекта, где обычный метод экземпляра не может обнулить экземпляр, через this = null потому что сам является его частью.
Несмотря на плюсы, не стоит использовать методы расширений если:
  • Ваш метод лучше укладывается в отдельном классе с другими тематичными методами и ситуация использования крайне специфична или просто редка для этого типа в целом.
  • Ваш метод перекликается с названием существующих методов и порождает путаницу.
  • Ваш метод выглядит читабельней, не используя данного синтаксиса.
Общепринято складывать методы расширений в отдельный класс (без обычных статических методов) и дописывать данному классу слово Extensions. Например, класс, содержащий методы расширений для типа List обычно называют ExtensionsList или ListExtensions. Это не обязывает вас делать тоже самое, но насколько я видел в проектах всегда есть отдельная папочка для методов расширений с вот такими классами.
Вот наверное и вся основная информация по методам расширений. Спасибо за внимание.
`
ОЖИДАНИЕ РЕКЛАМЫ...
24
Автор, прежде чем учить других, сам хотя бы немного изучи язык :facepalm:
public static IntArrayUtils 
{
    public static int GetLast(this int[] array) 
    {
        var last = 0;
        if (array != null && array.Count != 0)
             last = array[array.Length];
        return last;
    }
}
Две ошибки в примитивном примере. Я уже не говорю про оптимальность.
Несмотря на плюсы, не стоит использовать методы расширений если
Вместо этих если, нужно было сказать, что эти методы расширения допустимо использовать только в том случае, если у вас нету доступа к объекту, который нужно расширить.
И да:
Is Extension method thread safe?
You found an interesting loop hole, it tripped everybody up. No, it is not thread-safe.
While it looks like the EventHandler
reference is copied through the method argument, this is not what happens at runtime. Extension methods are subject to being inlined, just like a regular instance method. In fact, it is extremely likely to get inlined since it is so small. There's no copy, you have to make one yourself.
27
Две ошибки в примитивном примере.
Поправил, писал код в странице, не заметил
Автор, прежде чем учить других, сам хотя бы немного изучи язык :
Прежде чем делать такие заявления, подумай, реально ли списать ошибку на человеческий фактор невнимательности. Если да - выбирай выражения.
Faion:
Я уже не говорю про оптимальность.
Читай статью внимательно. Эта "неоптимальность" сделана умышлено. Когда речь идет о том чтобы объяснить новичку как устроены методы расширения, лучше имхо избавить его от необходимости смотреть "а почему код в функции поменялся", если это не касается темы. Я намеренно не использовал даже простейший оператор ?: только по этой причине, как и оставил объявление переменной (хотя можно было сразу ретурнить) чтобы читающий обращал внимание на изменения способа, а не на код-рыбу, который я использую.
Faion:
Вместо этих если, нужно было сказать, что эти методы расширения допустимо использовать только в том случае, если у вас нету доступа к объекту, который нужно расширить.
Это было написано двумя предложениями ранее.
Faion:
И да
Потокобезопасность - это то что нужно в статье для новичка по методам расширениям. Грац!
Снобам к ознакомлению
Критика к статье приветствуется, но, пожалуйста, не нужно выставлять это в виде собственного превосходства, посматривая как на гавно и убегая в крайности на любую брешь, перестаньте мудачить.
29
Faion, в книгах тоже бывают опечатки, по твоему тогда и авторы тоже не знаю языка? А так попрошу не горячиться и помочь с написанием статей для новичков, раз ты такой умный
2
этот пример выбран специально и устроен так, чтобы не задевать другие темы программирования и не мешать в голове понятия.
Это понятно, но метод, который возвращает значение несуществующего элемента, особенно для несуществующего объекта, выглядит опасно, а если еще кто-то скопирует это себе...
Я уже подзабыл: разве индексирование массивов в шарпе начинается с 1?
29
bea_mind, я думаю, если еще и юзать nullable тип для возвращения, новичкам будет не легко
2
alexprey, никаких nullable здесь быть и не должно. По-моему правильный вариант - если метод будет генерировать исключения и/или иметь вид
public static bool GetLast(this int[] array, out int value).
27
bea_mind:
public static bool GetLast(this int[] array, out int value)
Нет уверенности что потенциальный читатель уже знает про out-параметры и про исключения. Откуда такое стремление усложнить подачу материала в целях того чтобы профи устроила правильность? Статья рассчитана на новичка, если освещаются методы расширений, то не надо приплетать еще 30 понятий. В этом и та беда почему по книгам сложно обучаться с нуля - они тут же начинают говорить про кучи, исключения, потокобезопасность и еще 40 не разъясненных понятий, при том не объясняя нифига, а потом думают что доступно изложили материал.
При условии, что в статье предупреждается, что мы экспериментируем не с самым правильным примером кода я думаю это допустимая погрешность.
29
bea_mind, имхо, с наллеблом было бы намного лучше, ну а вообще, да, согласен с экстравертом, зачем все усложнять?
Этот комментарий удален
20
Откуда такое стремление
Переучивать сложнее, чем учить с нуля.
Это я не критикую статью, просто аргумент.
Этот комментарий удален
29
nvc123, тоесть предлагаешь бедному новичку сразу дать не объясняя овер дофига тем, чтобы он тупо это все скопипастил не понимая?
Этот комментарий удален
24
Extravert, лично я шел в школу с твердой уверенностью что на ноль делить можно и это доставило мне в свое время немало проблем, не говоря уже об уверенности что если из единицы вычесть два, то получится не ноль, а минус единица.
К чему это я? Наверно к тому, что осваивать программирование, как правило, начинают все-же не в первом классе, а уже более зрелые личности, способные к мыслительной деятельности повыше уровнем чем "добавим пять слонов к девяти бананам". Да и синтаксический сахар вроде методов расширения это явно не первое, чему должен учиться человек, начинающий изучать программирование, а значит и статья на эту тему не обязательно должна быть написана в столь аскетичной манере.
Не говоря уже о том, что конкретно методы расширения есть зло, нарушающее принципы ООП и взрывающее мозг почитателям более-менее грамотной архитектуры приложения, а также поборникам чистоты, читаемости и самодостаточности кода. И да, вот еще что, имена методов с большой буквы - ааааа моооииии глаааазааааа!
P.S. так-то статья составлена достаточно грамотно, ничего личного.
29
имена методов с большой буквы - ааааа моооииии глаааазааааа!
вот только не надо тут холиваров по стилю написания кода.
prog, Mihahail, nvc123, bea_mind, вот вы все молодцы, хоть бы одну статью написали. Человек старается и пытается развить сайт, а вы тут сидите и пинаете палочки. Говорил, и еще раз скажу, хотите показать свою крутоту всем? Так возьмите и напишите статью
prog:
Не говоря уже о том, что конкретно методы расширения есть зло
не понимаешь всю их прелесть, они упрощают твою жизнь.
3 комментария удалено
20
Методы расширения было бы лучше реализовывать переопределяя класс array его потомком(ой, ну да, этож c#) с заимплеменченным методом .last()
Тогда это не выглядело бы как костыль, на мой скромный взгляд.
alexprey, я вообще мимокрокодил. Но аргумент "сперва добейся" - не аргумент.
Extravert, не воспринимай близко к сердцу. Статья-то хорошая, её хоть сейчас помещай в учебник.
Мы тут просто начали оффтопить на близкую, но другую тему: как лучше эту фичу использовать в больших серьезных проектах. В статье настолько всё разжевано и понятно, что обсуждать приходится что угодно, кроме статьи.
2
Нет уверенности что потенциальный читатель уже знает про out-параметры и про исключения.
Так и нету уверенности, что он знает static, public, class и.т.д.
В этом и та беда почему по книгам сложно обучаться с нуля - они тут же начинают говорить про кучи, исключения, потокобезопасность и еще 40 не разъясненных понятий, при том не объясняя нифига, а потом думают что доступно изложили материал.
Вот про книги не надо. В большинстве из них оговаривается, что конструкции языка тесно связаны, и на ненужные просто не обращать внимания.
Скорее всего вам не повезло с хорошей литературой.
Вообще подобные статьи должны быть нацелены на тех, кто не совсем или в тяжелом случае совершенно ничего не понял, что "это" такое.
А вид иметь примерно такой:
  • полное описание в доступной форме(зачем? когда? как?).
  • реальные примеры и побольше.
  • различные тонкости применения и "подводные камни", опять же с обильным количеством примеров.
27
bea_mind:
Так и нету уверенности, что он знает static, public, class и.т.д.
Есть разница между тем, что ему придется узнать чтобы это осилить, и тем что он может узнать потом. Все в программировании очень сильно переплетается, но нельзя одним махом срубить все сразу.
24
alexprey, C# вызывает у меня дикий когнитивный диссонанс, как можно было понять из оффтопа в прошлом комментарии. А полезное для сайта я делать пытался, пока время позволяло - переводил статьи по JmonkeyEngine3 и еще вернусь к этому, даже не смотря на полное отсутствие востребованности этой темы.
Что касается методов расширения, расскажу одну маленькую историю из своей жизни: было дело, работал я на C# и выпало мне поддерживать библиотеку, написанную задолго до моего прихода в проект. Так получилось, что эта библиотека понадобилась одной из групп разработчиков на смежном проекте.
И вот, приходят ко мне с вопросом "как пользоваться методом Revitalize для объекта типа LostRequest? Мы его и так и эдак, а ему хоть бы хны - фиолетовый" имена методов и классов, а также пароли и явки взяты произвольно из соображений анонимности. Я то помню что такого метода в нашей библиотеке нет и быть не может, потому в первую очередь спрашиваю не используют ли они какую-то самопальную модификацию, но нет, ничего такого.
Потом вспоминаю про методы расширения и мягко уточняю не пользуются ли они чем-то таким, но и тут все чисто - не писали, не употребляли, не привлекались. В итоге мне пришлось скачать и развернуть у себя их проект, весивший, к слову, пару гигов со всеми необходимыми составляющими. И все только для того, чтобы в другой библиотеке обнаружить этот-самый метод-расширение, расширявший класс из нашей библиотеки.
Мораль - реализация этого расширения через фасад, адаптер, агрегацию или любым другим применимым в данном случае архитектурным паттерном значительно упростила бы жизнь, как минимум, пяти программистам ценой лишних десяти минут работы одного программиста.
29
prog, что это за программисты такие которые не могут отследить где метод расположен.... Студия такие методы выделяет и отправляет в нужный класс. Просто им было лень и скинули дело на тебя. Проблема фасада в том, что если добавят новйы метод в основной класс, тебе придется расширять новый и т.д. У нас в проекте с большим успехом юзают расширения для простоты написания кода и никто не жалуется.
Mihahail:
alexprey, я вообще мимокрокодил. Но аргумент "сперва добейся" - не аргумент.
Моя цель сподвигнуть других писать статьи) А вообще экстраверт тоже, сперва пришел и начал везде всех расскидывать такими же контр-комментариями, но он хотя бы доказал что он может не только писать заумные комментарии.
Я лично помогал в том году нашему преподавателю разрабатывать учебник для первого курса по плюсам и шарпам. Там было все так же разжованно, а в некоторых местах еще лучше. Большинство все равно так и не понимали как это делать, пока им устно не расскажешь, но в итоге, они все таки освоили язык. Поэтому давай те не будем тут разводить срачи и напишем каждый по несколько статей по основом любого языка.
Вот ты prog, например, ты отлично знаешь джаву, так почему бы не научить других? Я уверен, у тебя бы получились отличные статьи, наверняка.
И на последок, почему то мне кажется вы не дочитываете статью, или просто пробежались по коду и все. Потому что там было сказано, что их их надо использовть с умом, а не тыкать везде и всюду... вот теперь точно все
Чтобы оставить комментарий, пожалуйста, войдите на сайт.