Meshbeyn / C#

Дополнение типа в C# (C# Language Extension)

Сегодня мы с Вами рассмотрим, как в C# можно присобачить к телеге пятую ногу. Эта очень интересная и полезная возможность появилась в третьей версии, но до сих пор известна немногим. Тому есть две причины: во-первых, в третьей версии появилось просто гигантское количество новых технологий и фич, причем многие довольно сомнительной полезности для большинства программистов, ну а во-вторых - крайне неудачное название. Когда видишь сочетание слов "C# Language Extension" - вообще не возможно понять его смысл. По-русски самым адекватным названием можно считать "дополнение типа".

Итак, что же это такое и зачем оно?

Допустим, мы хотим много и плодотворно работать со строками: вытаскивать куски строк, склеивать их обратно. Для более-менее удобной работы с префиксами и суффиксами в языках типа Бэйсика есть операторы Left, Right и Mid. Left отрезает начальные n символов из строки, Right – последние, а Mid вырезает любую часть. Ясно, что можно обойтись одним только Mid, но Left и Right для своих целей удобнее.

В классе String также есть два метода для такой обработки:

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

Итак, нам надо много работать с префиксами и суффиксами, поэтому мы хотим иметь методы Left и Right (без Mid обойдемся — его аналог и так уже есть). Нет ничего проще — наследуем от String и добавляем свои методы:

class MyString : String
{
    public String Left(int Len)
    {
        return Substring(0, Len);
    }

    public String Right(int Len)
    {
        return Substring(Length - Len);
    }
}

И тут компилятор нам и говорит человеческим голосом: "Ты что, родной! Класс String запечатан, так что отдохни". Остается нам только поматериться в сторону подлого Гейтса, подготовившего такую подлянку.

Какие еще у нас остаются варианты? Мы можем добавить в свой обрабатывающий класс эти методы и передавать им еще и строку. Но тогда ими очень неудобно будет пользоваться из других классов. Чтобы все классы смогли ими пользоваться однообразно, мы сделаем для них отдельный класс, где они будут публичными и статическими.

public static class StringUtil
{
    public static String Left(String Src, int Len)
    {
        return Src.Substring(0, Len);
    }

    public static String Right(String Src, int Len)
    {
        return Src.Substring(Src.Length - Len);
    }
}

Но это тоже неудобно: приходится везде писать имя класса. А при обработке строк их надо часто по 3-5 штук на сборку одной строки. Да и через неделю придется вспоминать, где эти методы живут и как их зовут.

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

public static class StringUtil
{
    public static String Left(this String Src, int Len)
    {
        return Src.Substring(0, Len);
    }

    public static String Right(this String Src, int Len)
    {
        return Src.Substring(Src.Length - Len);
    }
}

И все, теперь у строк появились новые методы, о чем IntelliSense нам любезно и сообщает:

Обратите внимание, что иконки у Left и многих других "новых" методов со специальным значком. Они также не являются собственными методами строки. Более того, они даже не расширяют строки: они расширяют соответствующие интерфейсы коллекций и все коллекции автоматически получают готовый метод.

Каким образом компилятор MSIL и JIT-компилятор среды управления узнают, что добавлен новый метод? На заборе ведь тоже много чего можно написать, а пользовательские библиотеки могут находиться где угодно. Здесь вся нагрузка ложится на первый компилятор: он рассматривает все видимые "отсюдова" классы и ищет в них подходящую сигнатуру. А в MSIL уже подставляется вызов конкретного статического метода. Так что для среды выполнения вообще ничего не меняется: это просто очередной "синтаксический сахар". На самом деле никаких новых технологий не добавлено.

Об это поведение постоянно спотыкаются новички удаляя никому не нужную строку "using System.Linq;". После этого все новые удобные плюшки коллекций внезапно и бесследно исчезают, а бедный юный кодер в истерике бегает и кричит "был же сарайчик!!!" Просто в каких-то классах в этом пространстве имен определены все расширения для коллекций, и если эти классы видны, то видны и расширения. Точно также можно подключать свои.

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

StringUtil.Left("1234", 2);

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