2008-10-30 15 views
430

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

Por ejemplo, si quiero agregar una extensión a la consola, llamada 'WriteBlueLine', por lo que puedo ir:

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

yo probamos este mediante la adición de un método estático locales, públicos, con la consola como un "este" parámetro ... ¡pero no 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 preguntando por lo imposible?

+3

Oh bueno. desafortunado, pero creo que voy a arreglarme. Sigo siendo un método de extensión virgen (en el código de producción de todos modos). Tal vez algún día, si tengo suerte. –

+0

He escrito una serie de extensiones HtmlHelper para ASP.NET MVC. Escribió uno para DateTime para darme el final de la fecha indicada (23: 59.59). Útil cuando le pide al usuario que especifique una fecha de finalización, pero realmente desea que sea el final de ese día. – tvanfosson

+10

No hay forma de agregarlos actualmente porque la característica no existe en C#. No porque sea imposible, sino porque los píos C# están muy ocupados, estaban más interesados ​​en los métodos de extensión para hacer que LINQ funcione y no vieron suficiente beneficio en los métodos de extensión estáticos para justificar el tiempo que tardarían en implementar. [Eric Lippert explica aquí] (http://stackoverflow.com/a/4914207/230390). –

Respuesta

227

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"); 
     } 
} 
+8

@Luis: en contexto, la idea sería "¿podría agregar un método de extensión a la clase ConfigurationManager para obtener una sección específica?" No puede agregar un método de extensión a una clase estática ya que requiere una instancia del objeto, pero puede escribir una clase contenedora (o fachada) que implemente la misma firma y difiera la llamada real al ConfigurationManager real. Puede agregar el método que desee a la clase contenedora para que no tenga que ser una extensión. – tvanfosson

+0

Me resulta más útil simplemente agregar un método estático a la clase que implementa ConfigurationSection. Así que, dada una implementación llamada MyConfigurationSection, llamaría a MyConfigurationSection.GetSection(), que devuelve la sección ya escrita, o null si no existe. El resultado final es el mismo, pero evita agregar una clase. – tap

+1

@tap - es solo un ejemplo, y el primero que me vino a la mente. Sin embargo, el principio de responsabilidad individual entra en juego. ¿Debería el "contenedor" en realidad ser responsable de interpretarse a sí mismo desde el archivo de configuración? Normalmente, simplemente tengo ConfigurationSectionHandler y lanzo el resultado desde ConfigurationManager a la clase apropiada y no me molesto con el wrapper. – tvanfosson

5

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 objetivo del modificador this es indicar al compilador de C# que pase la instancia en el lado izquierdo de . como el primer parámetro del método estático/de extensión.

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

4

Intenté hacer esto con System.Environment 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.

9

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

+4

Es porque un método de extensión se usa para extender una instancia de un objeto. Si no hicieran eso, serían simplemente métodos estáticos regulares. –

+21

Sería bueno hacer ambas cosas, ¿no? – Will

35

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 describió anteriormente) una clase contenedora inútil.

He aquí un buen ejemplo: intentar extender la clase de prueba estática de MS Unit Afirmar: Quiero 1 más Método de aserción AreEqual(x1,x2).

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

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

+13

También estaba tratando de extender la clase de prueba MS Unit Assert para agregar Assert.Throws y Assert.DoesNotThrow y tuve el mismo problema. –

+3

Sí, yo también :(Pensé que podía hacer 'Assert.Throws' a la respuesta http://stackoverflow.com/questions/113395/how-can--test-for-an-expected-exception-with-a -specific-exception-message-from-a – CallMeLaNN

15

Tal vez usted podría agregar una clase estática con su espacio de nombres personalizado y el mismo nombre de la 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); 
     } 
    } 
} 
+0

Pero esto no resuelve el problema de tener que volver a implementar * cada * método único de la clase estática original que desea mantener en su contenedor. Todavía es un contenedor, aunque lo hace tiene el mérito de necesitar menos cambios en el código que lo usa ... – binki

-3

Usted puede hacer esto si usted está dispuesto a "frigorífico" un poco haciendo una variable de la estática clase y asignarlo a nulo. Sin embargo, este método no estaría disponible para llamadas estáticas en la clase, por lo que no está seguro de cuánto uso 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(); 
    } 
} 
+0

esto es exactamente lo que hice. Mi clase se llama MyTrace :) – Gishu

+0

Consejo útil. un poco de olor a código, pero creo que podríamos esconder el objeto nulo en una clase base o algo así. Gracias. –

+1

No puedo compilar este código. Error 'System.Console': los tipos estáticos no se pueden usar como parámetros – Kuncevic

81

se puede añadir extensiones estáticas de clases en C#? No, pero puede 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. Aunque técnicamente no se pueden 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 los métodos de extensión en objetos nulos sin obtener la excepción nula (a menos que acceda a algo mediante @this).

Así que aquí es cómo utilizaría la siguiente:

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é tomo una llamada al constructor por defecto como un ejemplo, y Y por qué no me acaba de regresar nueva T() en el primer fragmento de código sin hacer toda esa basura Expression? Bueno, hoy es tu día de suerte porque obtienes un 2fer. Como cualquier desarrollador de .NET avanzado sabe, la nueva T() es lenta porque genera una llamada a System.Activator que usa la reflexión para obtener el constructor predeterminado antes de llamarlo. ¡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 exigen medidas desesperadas.

+2

Creo que para Dataset este truco funcionará, pero dudo que funcione para la clase de la Consola ya que la Consola es de clase estática, los tipos estáticos no se pueden usar como argumentos :) – ThomasBecker

+0

Sí, iba a decir lo mismo. Esto es métodos de extensión pseudoestáticos en una clase no estática. El OP era un método de extensión en una clase estática. – MarqueIV

+1

Es mucho mejor y más fácil tener alguna convención de nombres para métodos como 'XConsole',' ConsoleHelper' y más. –

7

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.

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

New() se compila a la instrucción IL "newobj" si el tipo se conoce en tiempo de compilación. Newobj toma un constructor para la invocación directa. Las llamadas a System.Activator.CreateInstance() compilan a la instrucción IL "call" para invocar System.Activator.CreateInstance(). New() cuando se usa contra tipos genéricos dará como resultado una llamada a System.Activator.CreateInstance(). La publicación del Sr. Obnoxious no estaba clara en este punto ... y bueno, desagradable.

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 
1

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ático.

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 debe escribir un método proxy estático para cada método que tenga System.Console. No es necesariamente algo malo, como se puede añadir 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 usted engancha algo en el WriteLine estándar. Podría ser un conteo de líneas o filtro de palabras malas o lo que sea. Siempre que especifique Console en su espacio de nombres, diga WebProject1 e importe el espacio de nombres System, 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 azules en la medida en que nunca especificó System.Console.WriteLine.

+0

desafortunadamente el enfoque de usar un descendiente no funciona cuando la clase base está sellada (como muchos en la biblioteca de clases .NET) –

1

Lo siguiente fue rechazado como edit a la respuesta de tvanfosson. Me pidieron que lo contribuyera como mi propia respuesta. Utilicé su sugerencia y terminé la implementación de un contenedor ConfigurationManager. En principio, simplemente llené el ... en la respuesta de tvanfosson.

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

Puede usar un yeso en nula para hacer 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 { } 
6

Me encontré sobre este tema al tratar de encontrar una respuesta a la misma pregunta del PO 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 como esto:

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