Una pregunta muy interesante, nunca he tenido la tentación de usar clases genéricas estáticas, pero al menos parece posible.
En el contexto de la declaración de métodos de extensión, no solo puede declarar métodos de extensión para un cierto tipo genérico (como IEnumerable<T>
) sino también el parámetro de tipo T
en la ecuación. Si aceptamos tratar IEnumerable<int>
y IEnumerable<string>
como diferentes tipos, esto también tiene sentido a nivel conceptual.
Ser capaz de declarar sus métodos de extensión en una clase genérica estática le evitaría repetir sus restricciones de parámetros de tipo una y otra vez, agrupando de hecho todos los métodos de extensión para IEnumerable<T> where T : IComparable
.
Según la especificación (cita requerida), los métodos de extensión solo se pueden declarar en clases estáticas no anidadas y no genéricas. La razón de las dos primeras restricciones es bastante obvia:
- Puede no llevar ningún estado, ya que no es un mixin, solo azúcar sintáctica.
- Debe tener el mismo alcance léxico que el tipo para el que proporciona extensiones.
La restricción en clases estáticas no genéricas me parece un poco arbitraria, no puedo encontrar una razón técnica aquí. Pero es posible que los diseñadores de idiomas decidieran desanimarlo para que escriba métodos de extensión que dependan del parámetro de tipo de una clase genérica para la que desee proporcionar métodos de extensión. En su lugar, quieren que proporciones una implementación verdaderamente genérica de tu método de extensión, pero que te permita ofrecer versiones optimizadas/especializadas (en tiempo de compilación) de tus métodos de extensión, además de tu implementación general.
Me recuerda a la especialización de plantillas en C++. EDITAR: Desafortunadamente esto es incorrecto, por favor vea mis adiciones a continuación.
Ok, como este es un tema realmente interesante, investigué un poco más.En realidad hay una restricción técnica que perdí aquí. Veamos algo de código:
public static class Test
{
public static void DoSomething<T>(this IEnumerable<T> source)
{
Console.WriteLine("general");
}
public static void DoSomething<T>(this IEnumerable<T> source) where T :IMyInterface
{
Console.WriteLine("specific");
}
}
Esto en realidad el error del mensaje del compilador:
Type 'ConsoleApplication1.Test' already defines a member called 'DoSomething' with the same parameter types
Ok, junto tratamos dividirlo en dos clases extensiones diferentes:
public interface IMyInterface { }
public class SomeType : IMyInterface {}
public static class TestSpecific
{
public static void DoSomething<T>(this IEnumerable<T> source) where T : IMyInterface
{
Console.WriteLine("specific");
}
}
public static class TestGeneral
{
public static void DoSomething<T>(this IEnumerable<T> source)
{
Console.WriteLine("general");
}
}
class Program
{
static void Main(string[] args)
{
var general = new List<int>();
var specific = new List<SomeType>();
general.DoSomething();
specific.DoSomething();
Console.ReadLine();
}
}
En contra de mi impresión inicial (era tarde ayer por la noche), esto generará una ambigüedad en los sitios de llamadas. Para resolver esta ambigüedad, uno necesitaría llamar al método de extensión de una manera tradicional, pero eso va en contra de nuestra intención.
Esto nos deja en una situación en la que no es posible declarar las especializaciones genéricas de los métodos de extensión en tiempo de compilación. Por otro lado, todavía no hay ninguna razón por la que no podamos declarar los métodos de extensión solo para un único parámetro de tipo genérico especial. Por lo tanto, sería bueno declararlos en una clase genérica estática.
Por otro lado escribir un método de extensión como:
public static void DoSomething<T>(this IEnumerable<T> source) where T : IMyInterface {}
o
public static void DoSomething(this IEnumerable<IMyInterface> source) {}
no es muy diferente y sólo requiere un poco de fundición en el sitio vs. llamada en la extensión lado del método (es probable que implemente alguna optimización que dependa del tipo específico, por lo que necesitaría convertir T en IMyInterface o lo que sea de todos modos). Entonces, la única razón por la que se me ocurre es que los diseñadores de idiomas quieren animarlo a escribir extensiones genéricas solo de una manera verdaderamente genérica.
Aquí pueden ocurrir algunas cosas interesantes si tomamos co/contravariancia en la ecuación, que está por introducirse con C# 4.0.
@ Dan Bryant, están permitidas las clases genéricas estáticas. Solo que no para clases que contengan métodos de extensión. – Dykam
Aparte de la pregunta ... ¿Por qué en su primer ejemplo se define .ToArray() como un método de extensión? Si está en la clase SimpleLinkedList seguramente puede definir un método público T [] ToArray() allí. –
Probablemente para simplificar la escritura de compiladores (no solo el compilador de C#) ya que reduce la cantidad de condiciones que se deben verificar. –