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

Я поклонник методов расширения в С#, но не имел никакого успеха, добавляя метод расширения к статическому классу, например, к консоли.

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

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

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

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

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

Ответ 1

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

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

      .....

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

Ответ 2

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

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() в первый фрагмент кода, не выполняя весь этот мусор Expression? Хорошо сегодня ваш счастливый день, потому что вы получаете 2fer. Как знает любой продвинутый разработчик .NET, новый T() работает медленно, потому что он вызывает вызов System.Activator, который использует отражение, чтобы получить конструктор по умолчанию, прежде чем вызывать его. Черт бы тебя побрал Microsoft! Однако мой код вызывает конструктор по умолчанию для объекта напрямую.

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

Ответ 3

Невозможно.

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

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

New() скомпилируется в команду 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

Ответ 7

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

Точкой модификатора this является указание компилятору С# передать экземпляр в левой части . в качестве первого параметра метода static/extension.

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

Ответ 8

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

Ответ 9

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

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

И я использую его следующим образом:

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

Ответ 11

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

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.

Ответ 12

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

Ответ 13

Вы можете использовать cast on null, чтобы заставить его работать.

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

Ответ 14

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