¿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 éxito al agregar un método de extensión a una clase estática, como Console.

Por ejemplo, si deseo agregar una extensión a la consola, llamada ‘WriteBlueLine’, para poder ir:

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

Intenté esto agregando un método público local estático, con Console como un parámetro ‘this’ … ¡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 preguntando por lo imposible?

No. Los métodos de extensión requieren una variable de instancia (valor) para un objeto. Sin embargo, puede escribir un contenedor estático en la interfaz de 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" ); } } 

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

 public static class Extensions { public static T Create(this T @this) where T : class, new() { return Utility.Create(); } } public static class Utility where T : class, new() { static Utility() { Create = Expression.Lambda>(Expression.New(typeof(T).GetConstructor(Type.EmptyTypes))).Compile(); } public static Func 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í está cómo 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 ejemplo, y Y por qué no devuelvo el nuevo T () en el primer fragmento de código sin hacer toda esa basura de 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 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 progtwigdores a escribir (como se describió anteriormente) una clase de contenedor sin sentido.

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

La única forma de hacerlo es señalar 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 extensiones estáticas. Los argumentos sobre bibliotecas de seccionamiento no se sostienen una vez que las instancias se pueden extender.

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

Me tropecé con este hilo al tratar de encontrar una respuesta a la misma pregunta que 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à"); 

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

En cuanto a los métodos de extensión, los mismos métodos de extensión 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 comstackdor no permite que se definan métodos de extensión para clases estáticas.

El Sr. Obnoxious escribió: “Como cualquier desarrollador avanzado de .NET 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”.

New () se comstack en la instrucción IL “newobj” si el tipo se conoce en tiempo de comstackción. Newobj toma un constructor para la invocación directa. Las llamadas a System.Activator.CreateInstance () comstackn a la instrucción IL “call” para invocar System.Activator.CreateInstance (). New () cuando se usa contra tipos generics 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 

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 objective de this modificador es decirle al comstackdor 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.

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

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.

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 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 enganchas algo en la 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.

Lo siguiente fue rechazado como una edición de 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 de 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 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); } } 

Puedes usar un molde en nulo para hacerlo funcionar.

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

Tu tipo:

 public class YourType { } 

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 quieren acceder a las otras funciones estáticas de la Consola, tendrán que hacer referencia explícita 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.

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

* Nota: la consola deberá agregarse a través del espacio de nombre en el que lo haya insertado.

PUEDE hacer esto si está dispuesto a “frig” un poco creando una variable de la clase estática y asignándola a nulo. 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 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(); } }