Pregunta

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

Por ejemplo, si quiero agregar una extensión a la Consola, llamada 'WriteBlueLine', para poder ir:

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

Intenté esto agregando un método estático local, público, con la Consola como un parámetro 'esto' ... ¡pero sin dados!

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 pedir lo imposible?

¿Fue útil?

Solución

No. Los métodos de extensión requieren una variable de instancia (valor) para un objeto. Sin embargo, puede escribir un contenedor estático alrededor de la interfaz ConfigurationManager . Si implementa el contenedor, no necesita un método de extensión, ya que solo 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" );
      }
 }

Otros consejos

¿Puedes 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ática, en su lugar, este código explota una laguna en los métodos de extensión. La laguna es que puede llamar a métodos de extensión en objetos nulos sin obtener la excepción nula (a menos que acceda a algo a través de @this).

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 constructor predeterminado como un ejemplo, y Y por qué no devuelvo nueva T () en el primer fragmento de código sin hacer toda esa basura de Expresión? Bueno, hoy es tu día de suerte porque obtienes un 2fer. Como sabe cualquier desarrollador avanzado de .NET, la nueva T () es lenta porque genera una llamada a System.Activator que utiliza la reflexión para obtener el constructor predeterminado antes de llamar. ¡Maldito seas Microsoft! Sin embargo, mi código llama directamente al constructor predeterminado del objeto.

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

No es posible.

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

Su decisión no tiene sentido y obliga a los programadores a escribir (como se describe más arriba) una clase de contenedor sin sentido.

Este es un buen ejemplo: Intentar extender la clase de prueba de unidad de MS estática Afirmar: quiero 1 método Assert más AreEqual (x1, x2) .

La única forma de hacer esto es apuntar a diferentes clases o escribir un contenedor alrededor de cientos de diferentes métodos Assert. ¿Por qué ??

Si 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 la sección de bibliotecas no se levantan una vez que se pueden extender las instancias.

Me tropecé con este hilo al intentar encontrar una respuesta a la misma pregunta que tenía el OP. 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à");

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

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

En cuanto a los métodos de extensión, los métodos de extensión en sí mismos son estáticos; pero se invocan como si fueran métodos de instancia. Como una clase estática no es instanciable, nunca tendría una instancia de la clase para 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.

Sr. Obnoxious escribió: "Como sabe cualquier desarrollador avanzado de .NET, la nueva T () es lenta porque genera una llamada a System.Activator que utiliza la reflexión para obtener el constructor predeterminado antes de llamar" " ;.

New () se compila al IL " newobj " Instrucción si el tipo es conocido en tiempo de compilación. Newobj toma un constructor para la invocación directa. Las llamadas a System.Activator.CreateInstance () se compilan en el IL " call " instrucción para invocar System.Activator.CreateInstance (). New () cuando se usa contra tipos genéricos resultará en una llamada a System.Activator.CreateInstance (). El mensaje del Sr. Obnoxious no estaba claro en este punto ... y bueno, odioso.

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

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

El punto del modificador este es decirle al compilador de C # que pase la instancia en el lado izquierdo del . como el primer parámetro de la extensión estática método.

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

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

sí, en un sentido limitado.

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

Esto funciona pero la consola no lo hace 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 mientras no esté en el mismo espacio de nombres. El problema es que tiene que escribir un método proxy estático para cada método que tenga System.Console. 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 que conectas algo a la línea de escritura estándar. Podría ser un recuento de líneas o un filtro de palabras malas o lo que sea. Siempre que especifique Console en su espacio de nombres, diga WebProject1 e importe el sistema de espacio de nombres, WebProject1.Console se elegirá sobre System.Console como predeterminado para esas clases en el espacio de nombres WebProject1. Por lo tanto, este código convertirá todas las llamadas de Console.WriteLine en azul en la medida en que nunca especificó System.Console.WriteLine.

Lo siguiente se rechazó como una edit para la respuesta de tvanfosson. Me pidieron que contribuyera como mi propia respuesta. Utilicé su sugerencia y terminé la implementación de un contenedor ConfigurationManager . En principio, simplemente completé el ... en la respuesta de tvanfosson.

  

No. Los métodos de extensión requieren una instancia de un objeto. Usted puede   sin embargo, escriba un contenedor estático alrededor del ConfigurationManager   interfaz. Si implementa el contenedor, no necesita una extensión   Método ya que solo puedes 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);
    }
}

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

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 Console.WriteBlueLine (fooText) en otras clases. Si las otras clases desean acceder a las otras funciones estáticas de la Consola, deberán ser referenciadas explícitamente a través de su espacio de nombres.

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

Entonces tendrías 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ás buscando.

* Note Console tendrá que agregarse a través del espacio de nombres en el que lo pones.

Puedes usar un cast en nulo 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) { }
}

YourType:

public class YourType { }

PUEDES hacer esto si estás dispuesto a " frig " un poco haciendo una variable de la clase estática y asignándola a nulo. Sin embargo, este método no estaría disponible para las llamadas estáticas en la clase, por lo que no estoy seguro de qué tan útil serí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();
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top