2008-10-30 4 views
430

Я поклонник методов расширения в C#, но не имел успеха, добавляя метод расширения к статическому классу, например, к консоли.Могу ли я добавить методы расширения к существующему статическому классу?

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

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

Я попытался это путем добавления локального, общественного статический метод, с консолью, как «этот» параметр ... но не кости!

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

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

+3

О, хорошо. несчастный, но я думаю, что пройду. Я ВСЕГДА использую метод расширения virgin (в любом случае в производственном коде). Может быть, когда-нибудь, если мне повезет. – 2008-11-21 17:25:45

+0

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

+10

Невозможно добавить их в настоящее время, потому что функция не существует в C#. Не потому, что это невозможно _per se_, а потому, что C# peeps очень заняты, в основном заинтересованы в методах расширения, чтобы заставить LINQ работать и не видели достаточной выгоды в статических методах расширения, чтобы оправдать время, которое они должны были выполнить. [Эрик Липперт объясняет здесь] (http://stackoverflow.com/a/4914207/230390). – 2012-10-31 09:54:26

ответ

227

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

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

     ..... 

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

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

5

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

Модификатор this должен сообщить компилятору C# передать экземпляр в левой части . в качестве первого параметра метода статического/расширения.

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

4

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

9

Nope. В определениях расширений требуется экземпляр типа, который вы расширяете. Его несчастный; Я не уверен, почему его необходимо ...

+4

Это потому, что метод расширения используется для расширения экземпляра объекта. Если бы они этого не сделали, это были бы обычные статические методы. – 2009-06-06 17:15:19

+21

Было бы неплохо сделать то и другое, не так ли? – Will 2009-06-08 13:29:40

35

Его невозможно.

И да, я думаю, что MS допустила ошибку здесь.

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

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

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

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

15

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

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

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

Вы можете добавить статические расширения для классов в 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(...) 

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

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

7

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

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

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

Этот код:

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 
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. Это может быть счетчик строк или фильтр плохих слов или что угодно. Всякий раз, когда вы просто указываете Консоль в своем пространстве имен, скажите WebProject1 и импортируйте пространство имен System, WebProject1.Console будет выбрана по умолчанию для System.Console для этих классов в пространстве имен WebProject1. Таким образом, этот код превратит все вызовы Console.WriteLine в синий цвет, поскольку вы никогда не указали System.Console.WriteLine.

1

Следующее отклонено как ответ edit на ответ 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); 
    } 
} 
0

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

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

Расширение:

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

YourType:

public class YourType { } 
6

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

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

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

ConsoleColor.Cyan.WriteLine("voilà"); 
Смежные вопросы