¿Puedo agregar métodos de extensión a una clase estática existente?


Soy un fan de los métodos de extensión en C#, pero no he tenido ningún éxito al agregar un método de extensión a una clase estática, como Console.

Por ejemplo, si quiero agregar una extensión a la consola, llamada 'WriteBlueLine', para que pueda ir:

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

Lo intenté añadiendo un método local, público y estático, con la consola como parámetro 'this'... pero no dice!

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

Esto no agregó un método 'WriteBlueLine' a la consola... ¿lo estoy haciendo mal? O pidiendo la imposible?

Author: Bill the Lizard, 2008-10-30

15 answers

No. Los métodos de extensión requieren una variable de instancia (valor) para un objeto. Sin embargo, puede escribir un wrapper estático alrededor de la interfaz ConfigurationManager. Si implementa el wrapper, no necesita un método de extensión, ya que puede agregar el método directamente.

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

      .....

      public static ConfigurationSection GetWidgetSection()
      {
          return GetSection( "widgets" );
      }
 }
 243
Author: tvanfosson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-09 15:59:09

¿Se pueden agregar extensiones estáticas a las clases en C#? No, pero puedes hacer esto:

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

Así es como funciona. Si bien técnicamente no puede escribir métodos de extensión estáticos, este código explota una laguna en los métodos de extensión. Esa laguna es que puede llamar a métodos de extensión en objetos null sin obtener la excepción null (a menos que acceda a algo a través de @this).

Así que así es como usarías esto:

    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(...)

Ahora, ¿POR qué elegí llamar al valor predeterminado constructor como ejemplo, y ¿por qué no devuelvo new T() en el primer fragmento de código sin hacer toda esa basura de expresión? Bueno, hoy es su día de suerte porque obtiene un 2fer. Como cualquier desarrollador avanzado de. NET sabe, new T () es lento porque genera una llamada al Sistema.Activador que utiliza la reflexión para obtener el constructor predeterminado antes de llamarlo. ¡Maldito Microsoft! Sin embargo, mi código llama directamente al constructor predeterminado del objeto.

Las extensiones estáticas serían mejor que esto, pero los tiempos desesperados requieren medidas desesperadas.

 84
Author: Mr. Obnoxious,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-20 10:43:50

No es posible.

Y sí, creo que la EM cometió un error aquí.

Su decisión no tiene sentido y obliga a los programadores a escribir (como se describió anteriormente) una clase wrapper sin sentido.

Aquí hay un buen ejemplo: Tratando de extender la clase estática de pruebas unitarias de MS Assert: Quiero 1 método Assert más AreEqual(x1,x2).

La única manera de hacer esto es apuntar a diferentes clases o escribir un wrapper alrededor de 100s de diferentes métodos Assert. ¡Por qué!?

Si el se tomó la decisión de permitir extensiones de instancias No veo ninguna razón lógica para no permitir extensiones estáticas. Los argumentos sobre las bibliotecas de secciones no se mantienen una vez que las instancias se pueden extender.

 38
Author: Tom Deloford,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-16 13:35:00

Tal vez podría agregar una clase estática con su espacio de nombres personalizado y el mismo nombre de clase:

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);
        }
    }
}
 16
Author: Pag Sun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-07 04:11:31

Me tropecé con este hilo mientras trataba de encontrar una respuesta a la misma pregunta que el OP tenía. No encontré la respuesta que quería pero terminé haciendo esto.

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

Y lo uso así:

ConsoleColor.Cyan.WriteLine("voilà");
 12
Author: SqueakyBed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-01 15:12:46

No. Las definiciones de métodos de extensión requieren una instancia del tipo que está extendiendo. Es desafortunado; no estoy seguro de por qué es necesario...

 10
Author: Will,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-21 16:44:39

En cuanto a los métodos de extensión, los propios métodos de extensión son estáticos; pero se invocan como si fueran métodos de instancia. Dado que una clase estática no es instanciable, nunca tendría una instancia de la clase desde la que invocar un método de extensión. Por esta razón, el compilador no permite que se definan métodos de extensión para clases estáticas.

El Sr. Odioso escribió: "Como cualquier desarrollador avanzado de.NET sabe, new T() es lento porque genera una llamada al Sistema.Activador que utiliza reflexión para obtener el constructor predeterminado antes de llamarlo".

New() es compilado a la instrucción IL "newobj" si el tipo es conocido en tiempo de compilación. Newobj toma un constructor para invocación directa. Llamadas al Sistema.Activador.CreateInstance () compila a la instrucción IL "call" para invocar System.Activador.CreateInstance(). New () cuando se usa contra tipos genéricos resultará en una llamada al Sistema.Activador.CreateInstance(). The post by Mr. Obnoxious was unclear on this point... y bueno, detestable.

Este código:

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

Produce esta 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
Author: Brian Griffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-11 19:52:37

No puede agregar métodos estáticos a un tipo. Solo puede agregar métodos de instancia (pseudo-)a una instancia de un tipo.

El punto del modificador this es decirle al compilador de C# que pase la instancia del lado izquierdo del . como primer parámetro del método static/extension.

En el caso de agregar métodos estáticos a un tipo, no hay instancia que pasar para el primer parámetro.

 5
Author: Brannon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-30 04:00:49

A partir de C#7 esto no está soportado. Sin embargo, hay discusiones sobre la integración de algo así en C#8 y propuestas que vale la pena apoyar.

 5
Author: mbx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-28 10:02:29

Traté de hacer esto con el Sistema.Entorno cuando estaba aprendiendo métodos de extensión y no tuve éxito. La razón es, como otros mencionan, porque los métodos de extensión requieren una instancia de la clase.

 4
Author: Robert S.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-21 16:48:31

Sí, en un sentido limitado.

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

Esto funciona, pero la consola no porque es estática.

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

Esto funciona porque siempre y cuando no esté en el mismo espacio de nombres. El problema es que tienes que escribir un método estático proxy para cada método de ese Sistema.La consola tiene. No es necesariamente algo malo, ya que puedes agregar algo como esto:

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

O

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

La forma en que funciona es enganchando algo en la línea de escritura estándar. Podría ser un recuento de líneas o malo filtro de palabras o lo que sea. Cada vez que especifique la consola en su espacio de nombres, diga WebProject1 e importe el sistema de espacio de nombres, WebProject1.Se elegirá la consola sobre el Sistema.Consola como predeterminada para esas clases en el espacio de nombres WebProject1. Así que este código girará toda la consola.WriteLine llama a blue en la medida en que nunca especificó el sistema.Consola.WriteLine.

 1
Author: Black Dog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-09 17:41:56

Lo siguiente fue rechazado como una editar a la respuesta de tvanfosson. Me pidieron que contribuyera como mi propia respuesta. Usé su sugerencia y terminé la implementación de un wrapper ConfigurationManager. En principio, simplemente rellené el ... en la respuesta de tvanfosson.

No. Los métodos de extensión requieren una instancia de un objeto. Puedes sin embargo, escriba un wrapper estático alrededor del ConfigurationManager interfaz. Si implementa el wrapper, no necesita una extensión método ya que solo puede agregar el método directamente.

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);
    }
}
 1
Author: André C. Andersen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:20

Puede usar un molde en null para que funcione.

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

La extensión:

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

Sutipo:

public class YourType { }
 0
Author: Wouter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-29 08:05:37

No es posible escribir un método de extensión, sin embargo, es posible imitar el comportamiento que está pidiendo.

using FooConsole = System.Console;

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

Esto le permitirá llamar a la consola.WriteBlueLine (fooText) en otras clases. Si las otras clases quieren acceso a las otras funciones estáticas de Console, tendrán que ser referenciadas explícitamente a través de su espacio de nombres.

Siempre puede agregar todos los métodos a la clase de reemplazo si desea tenerlos todos en un solo lugar.

So usted tendría algo como

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

Esto proporcionaría el tipo de comportamiento que está buscando.

*Nota La consola tendrá que agregarse a través del espacio de nombres en el que la coloque.

 0
Author: Douglas Potesta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-12 04:39:56

Puede hacer esto si está dispuesto a "frig" un poco haciendo una variable de la clase estática y asignándola a null. Sin embargo, este método no estaría disponible para llamadas estáticas en la clase, por lo que no estoy seguro de cuánto se usaría:

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();
    }
}
 -4
Author: Tenaka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-09-15 14:39:26