Я поклонник методов расширения в 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 в консоль ... я делаю это неправильно? Или просить невозможного?
Я написал несколько расширений HtmlHelper для ASP.NET MVC. Написал один для DateTime, чтобы дать мне конец заданной даты (23: 59.59). Полезно, когда вы просите пользователя указать дату окончания, но на самом деле хотите, чтобы это был конец этого дня.
В настоящее время нет возможности добавить их, потому что эта функция не существует в C#. Не потому, что это невозможно как таковой, а потому, что наблюдатели C# очень заняты, в основном интересовались методами расширения, чтобы заставить LINQ работать, и не видели достаточной выгоды в статических методах расширения, чтобы оправдать время, которое они потратят на реализацию. Эрик Липперт объясняет здесь.
Просто позвоните Helpers.WriteBlueLine(null, "Hi"); :)





Вы не можете добавить к типу методы статический. Вы можете добавлять только (псевдо) методы экземпляра к экземпляру типа.
Смысл модификатора this - указать компилятору C# передать экземпляр слева от . в качестве первого параметра статического метода / метода расширения.
В случае добавления статических методов к типу для первого параметра нет экземпляра.
Я пытался сделать это с помощью System.Environment, когда изучал методы расширения, и безуспешно. Причина в том, как упоминают другие, потому что методы расширения требуют экземпляра класса.
Неа. Для определений методов расширения требуется экземпляр расширяемого типа. Это прискорбно; Не знаю, зачем это нужно ...
Это потому, что метод расширения используется для расширения экземпляра объекта. Если бы они этого не сделали, это были бы обычные статические методы.
Было бы неплохо сделать и то, и другое, не так ли?
Нет. Методы расширения требуют наличия переменной экземпляра (значения) для объекта. Однако вы можете написать статическую оболочку для интерфейса 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. Вы можете добавить любой метод к классу-оболочке, чтобы он не был расширением.
Я считаю более полезным просто добавить статический метод к классу, реализующему ConfigurationSection. Итак, учитывая реализацию MyConfigurationSection, я бы вызвал MyConfigurationSection.GetSection (), который возвращает уже набранный раздел, или null, если он не существует. Конечный результат тот же, но без добавления класса.
@tap - это всего лишь пример, и первое, что пришло в голову. Однако в игру вступает принцип единой ответственности. Должен ли «контейнер» действительно отвечать за интерпретацию самого себя из файла конфигурации? Обычно у меня просто есть ConfigurationSectionHandler, и я передаю вывод ConfigurationManager соответствующему классу и не беспокоюсь об оболочке.
Для внутреннего использования я начал создавать варианты 'X' статических классов и структур для добавления пользовательских расширений: 'ConsoleX' содержит новые статические методы для 'Console', 'MathX' содержит новые статические методы для 'Math', 'ColorX' расширяет методы Color и т. д. Не совсем то же самое, но легко запомнить и обнаружить в IntelliSense.
Это одна из худших идей C#, которые я когда-либо слышал. Обертка должна будет объявлять каждый статический член, существующий в обернутом классе. Все накладные расходы на вызов методов увеличатся вдвое. Огромная реализация, низкая производительность, высокая стоимость обслуживания, неприемлемо. Не могу поверить, сколько лайков получил этот ответ.
@Xtro Я согласен, что это ужасно, но не хуже, чем невозможность использовать тестовый дублер вместо него или, что еще хуже, отказаться от тестирования вашего кода, потому что статические классы делают это настолько сложным. Microsoft, кажется, согласна со мной, потому что они ввели классы HttpContextWrapper / HttpContextBase, чтобы обойти статический HttpContext.Current для MVC.
Утверждение, что методы расширения требуют экземпляра объекта, неверно. Вы можете передать значение null, которое не является экземпляром объекта. Вот почему большинство методов расширения требуют нулевой проверки, а методы экземпляра никогда (за исключением очень редкого исключения для финализаторов). Хотя правда, что они не действуют по типу.
@Abel - Я не был точен ради краткости. Им требуется значение (обычно хранящееся в переменной), представляющее экземпляр объекта, которым может быть null для ссылочного типа.
Точно. В противном случае в вашем ответе нет ничего плохого, но я бы хотел быть точным, иначе очень легко запутаться;)
@Abel В контексте вопроса я чувствовал, что достаточно просто различать переменную экземпляра (значение) и ссылку на статический класс.
Это невозможно.
И да, я думаю, что М.С. здесь ошибся.
Их решение не имеет смысла и заставляет программистов писать (как описано выше) бессмысленный класс-оболочку.
Вот хороший пример: Попытка расширить класс статического тестирования MS Unit Assert: мне нужен еще 1 метод Assert AreEqual(x1,x2).
Единственный способ сделать это - указать на разные классы или написать оболочку для сотен различных методов Assert. Почему!?
Если было принято решение разрешить расширения экземпляров, я не вижу логической причины не разрешать статические расширения. Аргументы о секционировании библиотек не выдерживают критики, если экземпляры могут быть расширены.
Я также пытался расширить класс MS Unit Test Assert, добавив Assert.Throws и Assert.DoesNotThrow, и столкнулся с той же проблемой.
Да и я тоже :( Думал сделать Assert.Throws на ответ stackoverflow.com/questions/113395/…
Этот пост сегодня так же неактуален, как и более 10 лет назад, нет никакой чистой выгоды от расширения классов Static с помощью дополнительных методов. Поначалу это всегда кажется как хорошая идея, но на практике существует слишком много причин, почему это анти шаблон. Класса-оболочки бессмысленно вообще нет, вместо этого есть очень полезная и специально созданная служебная программа или вспомогательный класс, чтобы хранить всю вашу настраиваемую логику в одном месте. Не пытайтесь реплицировать все функции на Assert, только кодируйте ваши пользовательские функции, разработчики вызывают вашу пользовательскую логику, когда им нужно, используйте Assert для всего остального.
Возможно, вы могли бы добавить статический класс с вашим пользовательским пространством имен и тем же именем класса:
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);
}
}
}
Но это не решает проблему необходимости повторной реализации единственного метода каждый из исходного статического класса, который вы хотите сохранить в своей оболочке. Это по-прежнему оболочка, хотя она требует меньшего количества изменений в коде, который ее использует ...
Вы МОЖЕТЕ сделать это, если хотите немного «поиграть», создав переменную статического класса и присвоив ей значение 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 :)
Полезный совет. немного запаха кода, но я думаю, мы могли бы спрятать нулевой объект в базовом классе или что-то в этом роде. Спасибо.
Я не могу скомпилировать этот код. Ошибка System.Console: статические типы нельзя использовать в качестве параметров
Да, это невозможно. Черт, я думал, ты что-то там натворил! Статические типы нельзя передавать в качестве параметров в методы, что, я полагаю, имеет смысл. Будем надеяться, что MS увидит на этом дереве из-за деревьев и изменит его.
Надо было попробовать скомпилировать собственный код! Как говорит Том, это не будет работать со статическими классами.
Можете ли вы добавить статические расширения к классам на 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 является статическим классом, статические типы не могут использоваться в качестве аргументов :)
Да, я собирался сказать то же самое. Это псевдостатические методы расширения нестатического класса. OP был методом расширения статического класса.
Гораздо лучше и проще просто иметь какое-то соглашение об именах для таких методов, как XConsole, ConsoleHelper и так далее.
Это увлекательный трюк, но результат неприятный. Вы создаете нулевой объект, а затем, кажется, вызываете для него метод - несмотря на годы, когда вам говорили, что «вызов метода для нулевого объекта вызывает исключение». Он работает, но .. ох ... Это сбивает с толку тех, кто будет поддерживать его позже. Я не буду голосовать против, потому что вы добавили в пул информации о том, что возможно. Но я искренне надеюсь, что никто никогда не воспользуется этой техникой !! Дополнительная жалоба: не передавайте одно из них методу и ожидайте получения OO-подкласса: вызываемый метод будет иметь тип объявление параметра, а не тип параметр передан в.
Это сложно, но мне это нравится. Одной из альтернатив (null as DataSet).Create(); может быть default(DataSet).Create();.
Мне нравится использовать var до такой степени, что он раздражает и делает var someObject = (SomeObject)null; или var someObject = default(SomeObject);, однако в этом случае действительно здорово, что вы можете сделать: SomeObject SomeObject = SomeObject.Create(); или, если вы хотите, чтобы вас ненавидели SomeObject SomeObject = SomeObject.Create<SomeObject>(); ;-)
Смысл метода расширения для статического класса состоит в том, чтобы сделать выражение совместимым с тем, как мы обычно вызываем метод расширения, то есть статический класс должен быть вызывающим, как этот Helper.SomeMethod(), как мы обычно пишем, теперь нам нужен другой метод расширения SomeExtensionMethod, который мы должен уметь использовать его как Helper.SomeExtensionMethod(). Ваше решение - это просто еще один способ реализации оболочки и меняет способ вызова методов расширения, не больше и не меньше.
не понимаю, почему это могло получить до 93 голосов? Из-за причудливого универсального кода, основанного на отражении, это не решает ничего, связанного с вопросом.
Хорошо сказано, @Hopeless, весь этот пост пронизан плохими аргументами в поддержку, большинство сторонников расширений статических классов прибегают к другим языкам или парадигмам разработки и поэтому просто не понимают, как выполнить свои требования родным способом. Но как этот пост получил столько голосов, когда он даже не решает проблему с OP, я никогда не пойму.
Что касается методов расширения, сами методы расширения являются статическими; но они вызываются, как если бы они были методами экземпляра. Поскольку статический класс не может быть создан, у вас никогда не будет экземпляра класса для вызова метода расширения. По этой причине компилятор не позволяет определять методы расширения для статических классов.
Г-н 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)
Следующее было отклонено как редактировать на ответ 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à");
В C# 7 это не поддерживается. Однако есть обсуждение интеграции чего-то подобного в C# 8 и предложения заслуживают поддержки.
Невозможно написать метод расширения, однако можно имитировать запрашиваемое поведение.
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.
}
Это обеспечит то поведение, которое вы ищете.
* Примечание. Консоль должна быть добавлена через пространство имен, в которое вы ее поместили.
Ну что ж. жаль, но я думаю, что я справлюсь. Я ВСЕ ЕЩЕ являюсь девственным методом расширения (во всяком случае, в производственном коде). Может быть, однажды, если мне повезет.