Как стать автором
Обновить

Комментарии 21

Хочу особо отметить, что методы расширений - это синтаксический сахар. Внешне эти методы в коде выглядят как методы экземпляров, но по природе остаются статическими. Из этого я вижу следующие важные следствия:

  1. Можно применить шаблон Fluent interface, так как внешне методы неотличимы.

  2. Для значений null у this-аргумента (первого) метод спокойно вызовется без исключений, т.к. природа его статична. Поэтому скорее всего потребуется проверка этого значения на null.

Спасибо. Действительно, важные следствия. Чуть позже дополню статью)

Эта статья должна иметь метку не .net 6, а .net fx 3.5. И выйти на 15 лет раньше.

Единственное существенное изменение, которое произошло за эти 15 лет - это то, что утиный GetEnumerator теперь можно иметь методом-расширением, но и это произошло не в .net6/C#10, а в C#9/.net5

Можно ли придумать еще более отвратительно-вырвиглазную кдпв?

Действительно, с картинкой я перемудрил из-за недостатка опыта в ремесле написания статей. Обязательно учту это в следующий раз

А можно ее прям щяс поменять?

В принципе да

Поменял. Надеюсь, угодил)

Думаю, что то, что выглядит как экземплярный метод, должно и вести себя как экземплярный метод. Если этот принцип мы хотим соблюсти, то метод расширения никогда не должен вызываться для нулевой ссылки. При вызове "настоящего" экземплярного метода для нулевой ссылки генерируется исключение. Значит также должен вести себя и метод расширения.

Для обеспечения этого поведения, обычно (на самом деле всегда) this-аргумент
проверяют на null и в случае необходимости генерят ArgumentNullException.

ИМХО:
Конечно, есть любители, пишущие методы расширения, допускающие вызов для нуловой ссылки. Код с использованием таких методов выглядит ужасно (напрягает,
противоречит привычкам, обманывает).

обычно (на самом деле всегда)

Немного позанудствую, но вот исключения из правил:
- Валидация. Например, Fluent Assertions с конструкцией вида x.Should().BeNull/NotBeNull().
- Методы, которые явно подчеркивают, что принимают null: string.IsNullOrEmpty(s) -> s.IsNullOrEmpty().

Также с появлением nullable reference, можно в методе-расширении явно указать, ожидается ли там null или нет. Это очень удобно

Когда я вижу, что для переменной, которая может содержать null, безусловно вызывается какой-нибудь метод, в моей голове включается сирена. Приходится разбираться, что это за метод (настоящий или расширение), лезть в код метода расширения, чтобы убедиться, что всё будет в порядке. Только тогда сирена выключается...

IsNullOrEmpty(this string.. - постоянно вижу во многих проектах такой самописный метод расширения.

А вы не задумывались, почему разработчики Miсrosoft за столько лет не написали такой метод расширения - IsNullOrEmpty? или подобный... "Ума им не хватило" что ли?

Нет. Не написали, потому что это было бы неправильно. И вы не пишите.

Во-первых, в VS точно можно было отдельным цветом выделять методы-расширения. Если у Вас часто встречается такая проблема - это Вам может помочь.

Во-вторых, также всё-таки советую начать подключать nullable reference (если это возможно в проекте, конечно), чтобы процесс nullable/не-nullable был более контролируемым и очевидным.

Ну и в-третьих, последнее сообщение - откровенная грубость. При чём с аргументацией в стиле "не думай, что ты умнее разработчиков из Microsoft". Это прямо фу-фу-фу, не надо так.

В чём принципиальное отличие метода расширения IsNullOrEmpty(this string ...) от написанного самим Microsoft статического метода String.IsNullOrEmpty(string ...)?

Будет это вызвано как s.IsNullOrEmpty() или string.IsNullOrEmpty(s) совершенно непринципиально и работает одинаково (причём так и было задумано), тем более в данном конкректном случае, благодаря правильно выбранному имени, очевидно что именно произойдёт.

Если в языке есть какая-то фича - значит это кому-то нужно и её можно применять, иначе бы её там не было. Да, в отдельных командах или у отдельных разработчиков есть свои предпочтения и правила написания кода, но это всегда локальные предпочтения и правила.

Мне лично как раз больше нравится с точки зрения восприятия именно s.IsNullOrEmpty(), не говоря уже о том что это ощутимо короче и приятней на вид чем String.IsNullOrEmpty(s), а компилятору вообще всё равно.

И как уже отметили чуть раньше, давайте не будем про безграничную мудрость разработчиков Microsoft - по такой логике никакие сторонние библиотеки вообще не нужны были бы потому что "если этого не сделали в Microsoft то это неправильно", а если заглянуть внутрь .net Framework то вы увидите целую кучу неоптимальных и плохо продуманных решений.

И до unified function call остаётся один шаг.

Честно говоря, в моём коде есть метод расширения (для linq-а) который специально устроен так, что принимает null. Называется он NeverNull и конвертирует null в пустой массив - для единообразия linq-обработки.

Это особенно полезно например когда у тебя есть какие-то navigational параметры (EF Core), но ты заранее не знаешь подтянули они что-то или там так null и остался, да ещё если и по цепочке, можно просто их опрашивать как param1?.param2?.param3?.NeverNull(). Чем городить кучу проверок на null, просто один удобный метод расширения.

Для конвертации нула во что либо хорошо подходит оператор ?? (операция дефолтного значения). Очень ясно и стандартно выражает мысль: "если переменная случится нуловая, надо заменить её значение на вот такое значение". Это стандартное средство языка, любой программист поймёт такой код однозначно и без проблем.

?? хорош, кто ж спорит, но в данном конкретном кейсе он слишком тяжеловесен. Писать каждый раз ?? Array.Empty<тип>() куда многословней чем .NeverNull(), который за тебя ещё и тип выведет как надо.

Да. Многословнее. А главное придётся явно указать тип. В Вашем варианте этого удаётся избежать.

Надеюсь, что когда-нибудь нам позволят писать просто new[0], без указания типа (как это уже сейчас можно делать в некоторых других случаях).

Меня, конечно попинают, но выскажусь.

У расширений есть свойство, которое по моему мнению, является очень большым недостатоком, который, во многом перекрывает достоинства: поведение начинает зависить от интрукций импорта.

Т.е. я реально работал с одним человеком, которому чем-то не угодил LINQ и он написал в проекте свои методы расширения Where, Count и т.д. к Enumerable. И сколько я словил неприятных часов только из-за того что линтер не тот using подставлял!
И в будете правы, когда скажите, что не надо писть свой линкью (и тем более использовать его параллельно с оргинальным!). Но это уже крайний случай, который подчёркивает общую тенденцию — расширения создают неочевидные зависимости между проектами, модулями (файлами). Раздувают и запутывают раздел импорта, т.к. вместо импорта одного класса, приходится импортировать класс и все его расширения — почти во всех файлах проекта! И это я ещё про рефлексию не говорю (и упомянутые выше методы над null)

Так что без особой необходимости лучше их не использовать. В подавляющем большинстве случаев, что я видел, расширения надо было заменять либо partial классами, либо обычными статическими методами класса, как микрософтовсий IsNullOrEmpty

А необходимость в расширениях реально есть вразве что в фреймворках со сложной структурой, когда возникает ситуация, что вот в этом проекте/классе сложный и запутанный функционал А нужен, а также запутанный и сложный функционал Б — не нужен и будет только мешаться. Ну примерно как тот же LINQ — его не редко и не нужно подключать, но если нужен — легко подключается. И расширения тут позволяют избежать лишних уровней абстракций классов.
Но писать расширения просто чтобы строки парсить — это, я считаю, методологически некорректно.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории