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

Я поклонник методов расширения в 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 в консоль ... я делаю это неправильно? Или просить невозможного?

Ну что ж. жаль, но я думаю, что я справлюсь. Я ВСЕ ЕЩЕ являюсь девственным методом расширения (во всяком случае, в производственном коде). Может быть, однажды, если мне повезет.

Andy McCluggage 21.11.2008 20:25

Я написал несколько расширений HtmlHelper для ASP.NET MVC. Написал один для DateTime, чтобы дать мне конец заданной даты (23: 59.59). Полезно, когда вы просите пользователя указать дату окончания, но на самом деле хотите, чтобы это был конец этого дня.

tvanfosson 21.11.2008 21:25

В настоящее время нет возможности добавить их, потому что эта функция не существует в C#. Не потому, что это невозможно как таковой, а потому, что наблюдатели C# очень заняты, в основном интересовались методами расширения, чтобы заставить LINQ работать, и не видели достаточной выгоды в статических методах расширения, чтобы оправдать время, которое они потратят на реализацию. Эрик Липперт объясняет здесь.

Jordan Gray 31.10.2012 13:54

Просто позвоните Helpers.WriteBlueLine(null, "Hi"); :)

Hüseyin Yağlı 24.07.2016 17:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
555
4
206 902
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

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

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

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

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

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

Это потому, что метод расширения используется для расширения экземпляра объекта. Если бы они этого не сделали, это были бы обычные статические методы.

Derek Ekins 06.06.2009 21:15

Было бы неплохо сделать и то, и другое, не так ли?

user1228 08.06.2009 17:29
Ответ принят как подходящий

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

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

      .....

      public static ConfigurationSection GetWidgetSection()
      {
          return GetSection( "widgets" );
      }
 }

@Luis - в контексте идея была бы такой: «Могу ли я добавить метод расширения к классу ConfigurationManager, чтобы получить определенный раздел?» Вы не можете добавить метод расширения к статическому классу, поскольку для него требуется экземпляр объекта, но вы можете написать класс-оболочку (или фасад), который реализует ту же сигнатуру и откладывает фактический вызов реального ConfigurationManager. Вы можете добавить любой метод к классу-оболочке, чтобы он не был расширением.

tvanfosson 18.02.2010 21:35

Я считаю более полезным просто добавить статический метод к классу, реализующему ConfigurationSection. Итак, учитывая реализацию MyConfigurationSection, я бы вызвал MyConfigurationSection.GetSection (), который возвращает уже набранный раздел, или null, если он не существует. Конечный результат тот же, но без добавления класса.

tap 08.10.2010 22:13

@tap - это всего лишь пример, и первое, что пришло в голову. Однако в игру вступает принцип единой ответственности. Должен ли «контейнер» действительно отвечать за интерпретацию самого себя из файла конфигурации? Обычно у меня просто есть ConfigurationSectionHandler, и я передаю вывод ConfigurationManager соответствующему классу и не беспокоюсь об оболочке.

tvanfosson 08.10.2010 22:29

Для внутреннего использования я начал создавать варианты 'X' статических классов и структур для добавления пользовательских расширений: 'ConsoleX' содержит новые статические методы для 'Console', 'MathX' содержит новые статические методы для 'Math', 'ColorX' расширяет методы Color и т. д. Не совсем то же самое, но легко запомнить и обнаружить в IntelliSense.

user1689175 26.09.2014 23:11

Это одна из худших идей C#, которые я когда-либо слышал. Обертка должна будет объявлять каждый статический член, существующий в обернутом классе. Все накладные расходы на вызов методов увеличатся вдвое. Огромная реализация, низкая производительность, высокая стоимость обслуживания, неприемлемо. Не могу поверить, сколько лайков получил этот ответ.

Xtro 04.06.2017 22:34

@Xtro Я согласен, что это ужасно, но не хуже, чем невозможность использовать тестовый дублер вместо него или, что еще хуже, отказаться от тестирования вашего кода, потому что статические классы делают это настолько сложным. Microsoft, кажется, согласна со мной, потому что они ввели классы HttpContextWrapper / HttpContextBase, чтобы обойти статический HttpContext.Current для MVC.

tvanfosson 05.06.2017 16:54

Утверждение, что методы расширения требуют экземпляра объекта, неверно. Вы можете передать значение null, которое не является экземпляром объекта. Вот почему большинство методов расширения требуют нулевой проверки, а методы экземпляра никогда (за исключением очень редкого исключения для финализаторов). Хотя правда, что они не действуют по типу.

Abel 09.10.2017 18:47

@Abel - Я не был точен ради краткости. Им требуется значение (обычно хранящееся в переменной), представляющее экземпляр объекта, которым может быть null для ссылочного типа.

tvanfosson 09.10.2017 18:51

Точно. В противном случае в вашем ответе нет ничего плохого, но я бы хотел быть точным, иначе очень легко запутаться;)

Abel 09.10.2017 18:54

@Abel В контексте вопроса я чувствовал, что достаточно просто различать переменную экземпляра (значение) и ссылку на статический класс.

tvanfosson 09.10.2017 18:58

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

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

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

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

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

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

Я также пытался расширить класс MS Unit Test Assert, добавив Assert.Throws и Assert.DoesNotThrow, и столкнулся с той же проблемой.

Stefano Ricciardi 24.02.2011 11:28

Да и я тоже :( Думал сделать Assert.Throws на ответ stackoverflow.com/questions/113395/…

CallMeLaNN 14.07.2011 06:55

Этот пост сегодня так же неактуален, как и более 10 лет назад, нет никакой чистой выгоды от расширения классов Static с помощью дополнительных методов. Поначалу это всегда кажется как хорошая идея, но на практике существует слишком много причин, почему это анти шаблон. Класса-оболочки бессмысленно вообще нет, вместо этого есть очень полезная и специально созданная служебная программа или вспомогательный класс, чтобы хранить всю вашу настраиваемую логику в одном месте. Не пытайтесь реплицировать все функции на Assert, только кодируйте ваши пользовательские функции, разработчики вызывают вашу пользовательскую логику, когда им нужно, используйте Assert для всего остального.

Chris Schaller 14.03.2021 05:01

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

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);
        }
    }
}

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

binki 19.12.2014 18:49

Вы МОЖЕТЕ сделать это, если хотите немного «поиграть», создав переменную статического класса и присвоив ей значение 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();
    }
}

это именно то, что я сделал. Мой класс называется MyTrace :)

Gishu 13.10.2010 15:00

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

Tom Deloford 19.02.2011 15:14

Я не могу скомпилировать этот код. Ошибка System.Console: статические типы нельзя использовать в качестве параметров

angularrocks.com 22.02.2011 12:23

Да, это невозможно. Черт, я думал, ты что-то там натворил! Статические типы нельзя передавать в качестве параметров в методы, что, я полагаю, имеет смысл. Будем надеяться, что MS увидит на этом дереве из-за деревьев и изменит его.

Tom Deloford 24.02.2011 17:48

Надо было попробовать скомпилировать собственный код! Как говорит Том, это не будет работать со статическими классами.

Tenaka 25.02.2011 18:33

Можете ли вы добавить статические расширения к классам на 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! Однако мой код напрямую вызывает конструктор объекта по умолчанию.

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

Я думаю, что для Dataset этот трюк сработает, но я сомневаюсь, что он работает для класса Console, поскольку Console является статическим классом, статические типы не могут использоваться в качестве аргументов :)

ThomasBecker 28.04.2015 16:55

Да, я собирался сказать то же самое. Это псевдостатические методы расширения нестатического класса. OP был методом расширения статического класса.

Mark A. Donohoe 27.09.2015 12:14

Гораздо лучше и проще просто иметь какое-то соглашение об именах для таких методов, как XConsole, ConsoleHelper и так далее.

Alex Zhukovskiy 11.01.2017 13:15

Это увлекательный трюк, но результат неприятный. Вы создаете нулевой объект, а затем, кажется, вызываете для него метод - несмотря на годы, когда вам говорили, что «вызов метода для нулевого объекта вызывает исключение». Он работает, но .. ох ... Это сбивает с толку тех, кто будет поддерживать его позже. Я не буду голосовать против, потому что вы добавили в пул информации о том, что возможно. Но я искренне надеюсь, что никто никогда не воспользуется этой техникой !! Дополнительная жалоба: не передавайте одно из них методу и ожидайте получения OO-подкласса: вызываемый метод будет иметь тип объявление параметра, а не тип параметр передан в.

ToolmakerSteve 24.01.2018 23:16

Это сложно, но мне это нравится. Одной из альтернатив (null as DataSet).Create(); может быть default(DataSet).Create();.

Teroneko 27.04.2018 11:25

Мне нравится использовать var до такой степени, что он раздражает и делает var someObject = (SomeObject)null; или var someObject = default(SomeObject);, однако в этом случае действительно здорово, что вы можете сделать: SomeObject SomeObject = SomeObject.Create(); или, если вы хотите, чтобы вас ненавидели SomeObject SomeObject = SomeObject.Create<SomeObject>(); ;-)

Brent Rittenhouse 23.06.2018 00:08

Смысл метода расширения для статического класса состоит в том, чтобы сделать выражение совместимым с тем, как мы обычно вызываем метод расширения, то есть статический класс должен быть вызывающим, как этот Helper.SomeMethod(), как мы обычно пишем, теперь нам нужен другой метод расширения SomeExtensionMethod, который мы должен уметь использовать его как Helper.SomeExtensionMethod(). Ваше решение - это просто еще один способ реализации оболочки и меняет способ вызова методов расширения, не больше и не меньше.

Hopeless 30.07.2020 02:08

не понимаю, почему это могло получить до 93 голосов? Из-за причудливого универсального кода, основанного на отражении, это не решает ничего, связанного с вопросом.

Hopeless 30.07.2020 02:13

Хорошо сказано, @Hopeless, весь этот пост пронизан плохими аргументами в поддержку, большинство сторонников расширений статических классов прибегают к другим языкам или парадигмам разработки и поэтому просто не понимают, как выполнить свои требования родным способом. Но как этот пост получил столько голосов, когда он даже не решает проблему с OP, я никогда не пойму.

Chris Schaller 14.03.2021 05:08

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

Г-н 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

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

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","****")); }

или же

 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.

к сожалению, подход с использованием потомка не работает, когда базовый класс запечатан (как и многие в библиотеке классов .NET)

George Birbilis 29.11.2015 00:28

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

No. Extension methods require an instance of an object. You can however, write a static wrapper around the ConfigurationManager interface. If you implement the wrapper, you don't need an extension method since you can just add the method directly.

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);
    }
}

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

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 { }

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

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

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

ConsoleColor.Cyan.WriteLine("voilà");

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

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.
}

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

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

Другие вопросы по теме