Могу ли я добавить методы расширения к существующему статическому классу?

Я поклонник методов расширения в C #, но мне не удалось добавить метод расширения к статическому классу, например Console.

Например, если я хочу добавить в консоль расширение под названием «WriteBlueLine», чтобы я мог:

Console.WriteBlueLine("This text is blue");

Я попробовал это, добавив локальный общедоступный статический метод с Console в качестве параметра this ... но без кубиков!

public static class Helpers {
    public static void WriteBlueLine(this Console c, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}

Это не добавило метод WriteBlueLine в консоль ... я делаю это неправильно? Или просить о невозможном?


person Leon Bambrick    schedule 30.10.2008    source источник
comment
Ну что ж. жаль, но я думаю, что я справлюсь. Я ВСЕ ЕЩЕ являюсь девственным методом расширения (во всяком случае в производственном коде). Может быть, однажды, если мне повезет.   -  person Andy McCluggage    schedule 21.11.2008
comment
Я написал несколько расширений HtmlHelper для ASP.NET MVC. Написал один для DateTime, чтобы дать мне конец заданной даты (23: 59.59). Полезно, когда вы просите пользователя указать дату окончания, но на самом деле хотите, чтобы это был конец этого дня.   -  person tvanfosson    schedule 21.11.2008
comment
В настоящее время нет возможности добавить их, потому что эта функция не существует в C #. Не потому, что это невозможно per se, а потому, что наблюдатели C # очень заняты, в основном интересовались методами расширения, чтобы заставить LINQ работать, и не видели достаточной выгоды в статических методах расширения, чтобы оправдать время, которое они взять к реализации. Эрик Липперт объясняет здесь.   -  person Jordan Gray    schedule 31.10.2012
comment
Просто позвоните Helpers.WriteBlueLine(null, "Hi"); :)   -  person Hüseyin Yağlı    schedule 24.07.2016


Ответы (15)


Нет. Методы расширения требуют наличия переменной экземпляра (значения) для объекта. Однако вы можете написать статическую оболочку для интерфейса ConfigurationManager. Если вы реализуете оболочку, вам не нужен метод расширения, поскольку вы можете просто добавить метод напрямую.

 public static class ConfigurationManagerWrapper
 {
      public static ConfigurationSection GetSection( string name )
      {
         return ConfigurationManager.GetSection( name );
      }

      .....

      public static ConfigurationSection GetWidgetSection()
      {
          return GetSection( "widgets" );
      }
 }
person tvanfosson    schedule 21.11.2008
comment
@Luis - в контексте идея заключалась бы в том, могу ли я добавить метод расширения к классу ConfigurationManager, чтобы получить конкретный раздел? Вы не можете добавить метод расширения к статическому классу, поскольку для него требуется экземпляр объекта, но вы можете написать класс-оболочку (или фасад), который реализует ту же сигнатуру и откладывает фактический вызов реального ConfigurationManager. Вы можете добавить любой метод к классу-оболочке, чтобы он не был расширением. - person tvanfosson; 18.02.2010
comment
Я считаю более полезным просто добавить статический метод к классу, реализующему ConfigurationSection. Итак, учитывая реализацию MyConfigurationSection, я бы вызвал MyConfigurationSection.GetSection (), который возвращает уже набранный раздел, или null, если он не существует. Конечный результат тот же, но без добавления класса. - person tap; 08.10.2010
comment
@tap - это всего лишь пример, и первое, что пришло в голову. Однако в игру вступает принцип единственной ответственности. Должен ли контейнер действительно отвечать за интерпретацию самого себя из файла конфигурации? Обычно у меня просто есть ConfigurationSectionHandler, и я передаю вывод ConfigurationManager соответствующему классу и не беспокоюсь об оболочке. - person tvanfosson; 08.10.2010
comment
Для внутреннего использования я начал создавать варианты 'X' статических классов и структур для добавления пользовательских расширений: 'ConsoleX' содержит новые статические методы для 'Console', 'MathX' содержит новые статические методы для 'Math', 'ColorX' расширяет методы Color и т. д. Не совсем то же самое, но легко запомнить и обнаружить в IntelliSense. - person user1689175; 26.09.2014
comment
Это одна из худших идей C #, которые я когда-либо слышал. Обертка должна будет объявлять каждый статический член, существующий в обернутом классе. Все накладные расходы на вызов методов увеличатся вдвое. Огромная реализация, низкая производительность, высокая стоимость обслуживания, неприемлемо. Не могу поверить, сколько лайков получил этот ответ. - person Xtro; 04.06.2017
comment
@Xtro Я согласен, что это ужасно, но не хуже, чем неспособность использовать тестовый дублер вместо него или, что еще хуже, отказаться от тестирования вашего кода, потому что статические классы делают это настолько сложным. Microsoft, кажется, согласна со мной, потому что они ввели классы HttpContextWrapper / HttpContextBase, чтобы обойти статический HttpContext.Current для MVC. - person tvanfosson; 05.06.2017
comment
Утверждение, что методы расширения требуют экземпляра объекта, неверно. Вы можете передать значение null, которое не является экземпляром объекта. Вот почему большинство методов расширения требуют нулевой проверки, а методы экземпляра никогда (за исключением очень редкого исключения для финализаторов). Хотя правда, что они не действуют по типу. - person Abel; 09.10.2017
comment
@Abel - Я не был точен ради краткости. Им требуется значение (обычно хранящееся в переменной), представляющее экземпляр объекта, который может быть null для ссылочного типа. - person tvanfosson; 09.10.2017
comment
Точно. В противном случае в вашем ответе нет ничего плохого, но я бы хотел быть точным, иначе очень легко запутаться;) - person Abel; 09.10.2017
comment
@Abel В контексте вопроса я чувствовал, что достаточно просто различать переменную экземпляра (значение) и ссылку на статический класс. - person tvanfosson; 09.10.2017

Можете ли вы добавить статические расширения к классам на C #? Нет, но вы можете это сделать:

public static class Extensions
{
    public static T Create<T>(this T @this)
        where T : class, new()
    {
        return Utility<T>.Create();
    }
}

public static class Utility<T>
    where T : class, new()
{
    static Utility()
    {
        Create = Expression.Lambda<Func<T>>(Expression.New(typeof(T).GetConstructor(Type.EmptyTypes))).Compile();
    }
    public static Func<T> Create { get; private set; }
}

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

Итак, вот как вы бы это использовали:

    var ds1 = (null as DataSet).Create(); // as oppose to DataSet.Create()
    // or
    DataSet ds2 = null;
    ds2 = ds2.Create();

    // using some of the techniques above you could have this:
    (null as Console).WriteBlueLine(...); // as oppose to Console.WriteBlueLine(...)

Теперь ПОЧЕМУ я выбрал вызов конструктора по умолчанию в качестве примера, и почему бы мне просто не вернуть new T () в первом фрагменте кода, не выполняя весь этот мусор Expression? Что ж, сегодня твой счастливый день, потому что ты получишь 2-е место. Как известно любому опытному разработчику .NET, new T () работает медленно, потому что он генерирует вызов System.Activator, который использует отражение для получения конструктора по умолчанию перед его вызовом. Черт побери, Microsoft! Однако мой код напрямую вызывает конструктор объекта по умолчанию.

Статические расширения были бы лучше, чем это, но отчаянные времена требуют отчаянных мер.

person Mr. Obnoxious    schedule 27.03.2011
comment
Я думаю, что для Dataset этот трюк сработает, но я сомневаюсь, что он работает для класса Console, поскольку Console является статическим классом, статические типы не могут использоваться в качестве аргументов :) - person ThomasBecker; 28.04.2015
comment
Да, я собирался сказать то же самое. Это псевдостатические методы расширения нестатического класса. OP был методом расширения статического класса. - person Mark A. Donohoe; 27.09.2015
comment
Гораздо лучше и проще просто иметь какое-то соглашение об именах для таких методов, как XConsole, ConsoleHelper и так далее. - person Alex Zhukovskiy; 11.01.2017
comment
Это увлекательный трюк, но результат неприятный. Вы создаете нулевой объект, а затем, кажется, вызываете для него метод - несмотря на годы, когда вам говорили, что вызов метода для нулевого объекта вызывает исключение. Он работает, но .. ох ... Это сбивает с толку тех, кто будет поддерживать его позже. Я не буду голосовать против, потому что вы добавили в пул информации о том, что возможно. Но я искренне надеюсь, что никто никогда не воспользуется этой техникой !! Дополнительная жалоба: не передавайте одно из них методу и ожидайте получения OO-подкласса: вызываемый метод будет иметь тип объявления параметра, а не тип параметра, переданного в. - person ToolmakerSteve; 24.01.2018
comment
Это сложно, но мне это нравится. Альтернативой (null as DataSet).Create(); может быть default(DataSet).Create();. - person Teroneko; 27.04.2018
comment
Мне нравится использовать var до такой степени, что он раздражает и делает var someObject = (SomeObject)null; или var someObject = default(SomeObject);, однако в этом случае действительно здорово, что вы можете сделать: SomeObject SomeObject = SomeObject.Create(); или если вы хотите, чтобы вас ненавидели SomeObject SomeObject = SomeObject.Create<SomeObject>(); ;-) - person Brent Rittenhouse; 23.06.2018
comment
Смысл метода расширения для статического класса состоит в том, чтобы согласовать выражение с тем, как мы обычно вызываем метод расширения, то есть статический класс должен быть вызывающим, как этот Helper.SomeMethod(), как мы обычно пишем, теперь нам нужен другой метод расширения SomeExtensionMethod, который мы должен уметь использовать его как Helper.SomeExtensionMethod(). Ваше решение - это просто еще один способ реализации оболочки и меняет способ вызова методов расширения, не больше и не меньше. - person Hopeless; 30.07.2020
comment
не понимаю, почему это могло получить до 93 голосов за? Из-за причудливого универсального кода, основанного на отражении, это не решает ничего, связанного с вопросом. - person Hopeless; 30.07.2020
comment
Хорошо сказано, @Hopeless, весь этот пост пронизан плохими аргументами в поддержку, большинство сторонников расширений статических классов прибегают к другим языкам или парадигмам разработки и поэтому просто не понимают, как выполнить свои требования родным способом. Но как этот пост получил столько голосов, когда он даже не решает проблему с OP, я никогда не пойму. - person Chris Schaller; 14.03.2021

Это невозможно.

И да, я думаю, что М.С. здесь ошибся.

Их решение не имеет смысла и заставляет программистов писать (как описано выше) бессмысленный класс-оболочку.

Вот хороший пример: Попытка расширить класс статического тестирования MS Unit Assert: мне нужен еще 1 метод Assert AreEqual(x1,x2).

Единственный способ сделать это - указать на разные классы или написать оболочку для сотен различных методов Assert. Почему !?

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

person Tom Deloford    schedule 12.01.2009
comment
Я также пытался расширить класс MS Unit Test Assert, чтобы добавить Assert.Throws и Assert.DoesNotThrow, и столкнулся с той же проблемой. - person Stefano Ricciardi; 24.02.2011
comment
Да, я тоже :( Я думал, что могу сделать Assert.Throws ответ stackoverflow.com/questions/113395/ - person CallMeLaNN; 14.07.2011
comment
Этот пост сегодня так же неактуален, как и более 10 лет назад, нет никакой чистой выгоды от расширения Static классов дополнительными методами. Поначалу это всегда кажется хорошей идеей, но на практике существует слишком много причин, по которым это антипаттерн. Нет вообще никакого бессмысленного класса-оболочки, вместо этого есть очень полезная и специально созданная служебная программа или вспомогательный класс, чтобы хранить всю вашу настраиваемую логику в одном месте. Не пытайтесь реплицировать все функции на Assert, только кодируйте свои пользовательские функции, разработчики вызывают вашу пользовательскую логику, когда им нужно, используйте Assert для остальных. - person Chris Schaller; 14.03.2021
comment
Ошибка - неправильное слово здесь. Помните бессмертные слова Эрика: Функции по умолчанию не реализованы; В C # нет функции, потому что никто никогда не проектировал, не специфицировал, не реализовывал, не тестировал, не документировал и не поставлял эту функцию. У каждой функции есть цена, и все дело в расстановке приоритетов. - person nawfal; 25.05.2021

Я наткнулся на эту ветку, пытаясь найти ответ на тот же вопрос, который был у OP. Я не нашел ответа, который хотел, но в итоге сделал это.

public static class MyConsole
{
    public static void WriteLine(this ConsoleColor Color, string Text)
    {
        Console.ForegroundColor = Color;
        Console.WriteLine(Text);   
    }
}

И я использую это так:

ConsoleColor.Cyan.WriteLine("voilà");
person Adel G.Eibesh    schedule 01.06.2017

Возможно, вы могли бы добавить статический класс с вашим пользовательским пространством имен и тем же именем класса:

using CLRConsole = System.Console;

namespace ExtensionMethodsDemo
{
    public static class Console
    {
        public static void WriteLine(string value)
        {
            CLRConsole.WriteLine(value);
        }

        public static void WriteBlueLine(string value)
        {
            System.ConsoleColor currentColor = CLRConsole.ForegroundColor;

            CLRConsole.ForegroundColor = System.ConsoleColor.Blue;
            CLRConsole.WriteLine(value);

            CLRConsole.ForegroundColor = currentColor;
        }

        public static System.ConsoleKeyInfo ReadKey(bool intercept)
        {
            return CLRConsole.ReadKey(intercept);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteBlueLine("This text is blue");   
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey(true);
        }
    }
}
person Pag Sun    schedule 07.01.2010
comment
Но это не решает проблему необходимости повторной реализации каждого отдельного метода исходного статического класса, который вы хотите сохранить в своей оболочке. Это по-прежнему оболочка, хотя она требует меньшего количества изменений в коде, который ее использует ... - person binki; 19.12.2014

В C # 7 это не поддерживается. Однако есть дискуссии об интеграции чего-либо как в C # 8 и предложениях, заслуживающих поддержки.

person mbx    schedule 28.09.2017

Неа. Для определений методов расширения требуется экземпляр расширяемого типа. Это прискорбно; Не знаю, зачем это нужно ...

person Community    schedule 21.11.2008
comment
Это потому, что метод расширения используется для расширения экземпляра объекта. Если бы они этого не сделали, это были бы обычные статические методы. - person Derek Ekins; 06.06.2009
comment
Было бы неплохо сделать и то, и другое, не так ли? - person ; 08.06.2009

К типу нельзя добавлять статические методы. Вы можете добавлять только (псевдо) методы экземпляра к экземпляру типа.

Смысл модификатора this - указать компилятору C # передать экземпляр слева от . в качестве первого параметра статического метода / метода расширения.

В случае добавления статических методов к типу для первого параметра нет экземпляра.

person Brannon    schedule 30.10.2008

Что касается методов расширения, сами методы расширения являются статическими; но они вызываются, как если бы они были методами экземпляра. Поскольку статический класс не может быть создан, у вас никогда не будет экземпляра класса для вызова метода расширения. По этой причине компилятор не позволяет определять методы расширения для статических классов.

Г-н Obnoxious писал: «Как знает любой продвинутый .NET-разработчик, new T () работает медленно, потому что он генерирует вызов System.Activator, который использует отражение для получения конструктора по умолчанию перед его вызовом».

New () компилируется в инструкцию IL "newobj", если тип известен во время компиляции. Newobj принимает конструктор для прямого вызова. Вызовы System.Activator.CreateInstance () компилируются с инструкцией «call» IL для вызова System.Activator.CreateInstance (). New () при использовании с универсальными типами приведет к вызову System.Activator.CreateInstance (). Сообщение г. Obnoxious было неясным по этому поводу ... и, в общем, неприятным.

Этот код:

System.Collections.ArrayList _al = new System.Collections.ArrayList();
System.Collections.ArrayList _al2 = (System.Collections.ArrayList)System.Activator.CreateInstance(typeof(System.Collections.ArrayList));

производит этот IL:

  .locals init ([0] class [mscorlib]System.Collections.ArrayList _al,
           [1] class [mscorlib]System.Collections.ArrayList _al2)
  IL_0001:  newobj     instance void [mscorlib]System.Collections.ArrayList::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldtoken    [mscorlib]System.Collections.ArrayList
  IL_000c:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_0011:  call       object [mscorlib]System.Activator::CreateInstance(class [mscorlib]System.Type)
  IL_0016:  castclass  [mscorlib]System.Collections.ArrayList
  IL_001b:  stloc.1
person Brian Griffin    schedule 11.01.2012

Я пытался сделать это с помощью System.Environment, когда изучал методы расширения, и безуспешно. Причина в том, как упоминают другие, потому что методы расширения требуют экземпляра класса.

person Robert S.    schedule 21.11.2008

Невозможно написать метод расширения, однако можно имитировать запрашиваемое поведение.

using FooConsole = System.Console;

public static class Console
{
    public static void WriteBlueLine(string text)
    {
        FooConsole.ForegroundColor = ConsoleColor.Blue;
        FooConsole.WriteLine(text);
        FooConsole.ResetColor();
    }
}

Это позволит вам вызывать Console.WriteBlueLine (fooText) в других классах. Если другие классы хотят получить доступ к другим статическим функциям консоли, на них нужно будет явно ссылаться через их пространство имен.

Вы всегда можете добавить все методы в замещающий класс, если хотите, чтобы все они были в одном месте.

Итак, у вас будет что-то вроде

using FooConsole = System.Console;

public static class Console
{
    public static void WriteBlueLine(string text)
    {
        FooConsole.ForegroundColor = ConsoleColor.Blue;
        FooConsole.WriteLine(text);
        FooConsole.ResetColor();
    }
    public static void WriteLine(string text)
    {
        FooConsole.WriteLine(text);
    }
...etc.
}

Это обеспечит то поведение, которое вы ищете.

* Примечание. Консоль должна быть добавлена ​​через пространство имен, в которое вы ее поместили.

person Douglas Potesta    schedule 12.08.2018

да, в ограниченном смысле.

public class DataSet : System.Data.DataSet
{
    public static void SpecialMethod() { }
}

Это работает, но консоль - нет, потому что она статична.

public static class Console
{       
    public static void WriteLine(String x)
    { System.Console.WriteLine(x); }

    public static void WriteBlueLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.Write(.x);           
    }
}

Это работает, потому что до тех пор, пока он не находится в том же пространстве имен. Проблема в том, что вам нужно написать статический метод прокси для каждого метода, который есть в System.Console. Это не обязательно плохо, так как вы можете добавить что-то вроде этого:

    public static void WriteLine(String x)
    { System.Console.WriteLine(x.Replace("Fck","****")); }

or

 public static void WriteLine(String x)
    {
        System.Console.ForegroundColor = ConsoleColor.Blue;
        System.Console.WriteLine(x); 
    }

Это работает так: вы подключаете что-то к стандартной WriteLine. Это может быть счетчик строк, фильтр плохих слов или что-то еще. Всякий раз, когда вы просто указываете Console в своем пространстве имен, скажите WebProject1 и импортируете пространство имен System, WebProject1.Console будет выбираться вместо System.Console по умолчанию для этих классов в пространстве имен WebProject1. Таким образом, этот код превратит все вызовы Console.WriteLine в синий цвет, поскольку вы никогда не указывали System.Console.WriteLine.

person Black Dog    schedule 09.11.2014
comment
к сожалению, подход с использованием потомка не работает, когда базовый класс запечатан (как и многие в библиотеке классов .NET) - person George Birbilis; 29.11.2015

Следующее было отклонено как редактирование ответа tvanfosson. Меня попросили внести это как мой собственный ответ. Я воспользовался его предложением и закончил реализацию оболочки ConfigurationManager. В принципе я просто заполнил ... в ответе tvanfosson.

Нет. Для методов расширения требуется экземпляр объекта. Однако вы можете написать статическую оболочку вокруг интерфейса ConfigurationManager. Если вы реализуете оболочку, вам не нужен метод расширения, поскольку вы можете просто добавить метод напрямую.

public static class ConfigurationManagerWrapper
{
    public static NameValueCollection AppSettings
    {
        get { return ConfigurationManager.AppSettings; }
    }

    public static ConnectionStringSettingsCollection ConnectionStrings
    {
        get { return ConfigurationManager.ConnectionStrings; }
    }

    public static object GetSection(string sectionName)
    {
        return ConfigurationManager.GetSection(sectionName);
    }

    public static Configuration OpenExeConfiguration(string exePath)
    {
        return ConfigurationManager.OpenExeConfiguration(exePath);
    }

    public static Configuration OpenMachineConfiguration()
    {
        return ConfigurationManager.OpenMachineConfiguration();
    }

    public static Configuration OpenMappedExeConfiguration(ExeConfigurationFileMap fileMap, ConfigurationUserLevel userLevel)
    {
        return ConfigurationManager.OpenMappedExeConfiguration(fileMap, userLevel);
    }

    public static Configuration OpenMappedMachineConfiguration(ConfigurationFileMap fileMap)
    {
        return ConfigurationManager.OpenMappedMachineConfiguration(fileMap);
    }

    public static void RefreshSection(string sectionName)
    {
        ConfigurationManager.RefreshSection(sectionName);
    }
}
person André C. Andersen    schedule 21.11.2015

Вы можете использовать приведение к нулю, чтобы заставить его работать.

public static class YoutTypeExtensionExample
{
    public static void Example()
    {
        ((YourType)null).ExtensionMethod();
    }
}

Расширение:

public static class YourTypeExtension
{
    public static void ExtensionMethod(this YourType x) { }
}

Твой тип:

public class YourType { }
person Wouter    schedule 29.05.2017

Вы МОЖЕТЕ сделать это, если хотите немного «поиграть», создав переменную статического класса и присвоив ей значение null. Однако этот метод не будет доступен для статических вызовов класса, поэтому не уверен, насколько он будет полезен:

Console myConsole = null;
myConsole.WriteBlueLine("my blue line");

public static class Helpers {
    public static void WriteBlueLine(this Console c, string text)
    {
        Console.ForegroundColor = ConsoleColor.Blue;
        Console.WriteLine(text);
        Console.ResetColor();
    }
}
person Tenaka    schedule 15.09.2010
comment
это именно то, что я сделал. Мой класс называется MyTrace :) - person Gishu; 13.10.2010
comment
Полезный совет. немного запаха кода, но я думаю, мы могли бы спрятать нулевой объект в базовом классе или что-то в этом роде. Спасибо. - person Tom Deloford; 19.02.2011
comment
Я не могу скомпилировать этот код. Ошибка System.Console: статические типы нельзя использовать в качестве параметров - person angularrocks.com; 22.02.2011
comment
Да, это невозможно. Черт, я думал, ты что-то там натворил! Статические типы нельзя передавать в качестве параметров в методы, что, я полагаю, имеет смысл. Будем надеяться, что MS увидит на этом дереве из-за деревьев и изменит его. - person Tom Deloford; 24.02.2011
comment
Надо было попробовать скомпилировать собственный код! Как говорит Том, это не будет работать со статическими классами. - person Tenaka; 25.02.2011