2010-04-11 8 views
20

Me gustaría crear muchos métodos de extensión para algunas clases genéricas, p. para¿Por qué es imposible declarar métodos de extensión en una clase genérica estática?

public class SimpleLinkedList<T> where T:IComparable 

Y he empezado a crear métodos como éste:

public static class LinkedListExtensions 
{ 
    public static T[] ToArray<T>(this SimpleLinkedList<T> simpleLinkedList) where T:IComparable 
    { 
     //// code 
    } 
} 

Pero cuando traté de hacer que la clase genérica LinkedListExtensions así:

public static class LinkedListExtensions<T> where T:IComparable 
{ 
    public static T[] ToArray(this SimpleLinkedList<T> simpleLinkedList) 
    { 
     ////code 
    } 
} 

consigo "métodos de extensión pueden solo se declarará en una clase estática no genérica, no anidada ".

Y trato de adivinar de dónde viene esta restricción y no tengo ideas.

EDITAR: Todavía no tiene una visión clara del problema. Parece que esto simplemente no se implementó por alguna razón.

+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

+0

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

+0

Probablemente para simplificar la escritura de compiladores (no solo el compilador de C#) ya que reduce la cantidad de condiciones que se deben verificar. –

Respuesta

0

Solo un pensamiento, pero ¿hay alguna razón por la que no pueda derivar simplemente de esta clase y agregar los métodos adicionales a la especialización en lugar de escribir un conjunto de métodos de extensión? Estoy seguro de que tienes tus razones, pero solo de tirar eso por ahí.

1

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:

  1. Puede no llevar ningún estado, ya que no es un mixin, solo azúcar sintáctica.
  2. 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.

3

El problema es cómo hace el compilador la resolución de la extensión?

Digamos que definen tanto los métodos que usted describe:

public static class LinkedListExtensions { 
    public static T[] ToArray<T>(this SimpleLinkedList<T> simpleLinkedList) where T:IComparable { 
     //// code 
    } 
} 
public static class LinkedListExtensions<T> where T:IComparable { 
    public static T[] ToArray(this SimpleLinkedList<T> simpleLinkedList) { 
     ////code 
    } 
} 

¿Qué método se utiliza en el siguiente caso?

SimpleLinkedList<int> data = new SimpleLinkedList<int>(); 
int[] dataArray = data.ToArray(); 

Supongo que los diseñadores de idiomas decidieron restringir los métodos de extensión a los tipos no genéricos para evitar este escenario.

+0

¡En cuanto a mis pruebas demostró que el compilador puede resolver esto! No tiene nada que ver con que la clase estática sea genérica. –

+0

@Johannes Rudolph: ¿Y qué método elegiría? No es obvio para mí en absoluto. – Gorpik

+0

@Johannes Rudolph - ¿Qué prueba hiciste? La pregunta original era acerca de cómo la segunda clase en el ejemplo no se compila, entonces, ¿cómo compilaste esto? –

13

En términos generales, ya que no se especifica la clase cuando se utiliza un método de extensión, el compilador no tiene forma de saber cuál es la clase en la que se define el método de extensión:

static class GenStatic<T> 
{ 
    static void ExtMeth(this Class c) {/*...*/} 
} 

Class c = new Class(); 
c.ExtMeth(); // Equivalent to GenStatic<T>.ExtMeth(c); what is T? 

Dado que los métodos de extensión ellos mismos pueden ser genéricos, esto no es un problema real en absoluto:

static class NonGenStatic 
{ 
    static void GenExtMeth<T>(this Class c) {/*...*/} 
} 

Class c = newClass(); 
c.ExtMeth<Class2>(); // Equivalent to NonGenStatic.ExtMeth<Class2>(c); OK 

puede volver a escribir fácilmente su ejemplo para que la clase estática no es genérico, pero los métodos genéricos son. De hecho, así es como se escriben las clases de .NET como Enumerable.

public static class LinkedListExtensions 
    { 
    public static T[] ToArray<T>(this SimpleLinkedList<T> where T:IComparable simpleLinkedList) 
    { 
     // code 
    } 
    } 
+0

Buen punto sobre el hecho de que los métodos de extensión no genéricos en clases genéricas estáticas causarían dolores de cabeza. –

+0

Si el método de extensión no usó ningún miembro estático de la clase en la que está contenido, no importaría qué clase se eligió. Puede aumentar su ejemplo haciendo que el método de extensión use algo así como un campo de clase estático; la elección del campo sería claramente importante desde el punto de vista semántico, pero el compilador no podría seleccionarla. – supercat

+0

@supercat: No importa. El compilador no puede elegir cualquier clase: debe saber qué clase específica elegir, incluso si la implementación del método es exactamente la misma para todos. – Gorpik

2

No piense en los métodos de extensión como vinculados a la clase estática en la que están contenidos. En cambio, piense en ellos como vinculados a un espacio de nombre específico. Por lo tanto, la clase estática en la que se definen es simplemente un shell utilizado para declarar estos métodos dentro del espacio de nombres. Y si bien podría escribir múltiples clases para diferentes tipos de métodos de extensión, no debería pensar en la clase misma como algo más que una forma de agrupar claramente sus métodos de extensión.

Los métodos de extensión no amplían ningún atributo de la clase en la que están contenidos. La firma del método definirá todo sobre el método de extensión. Y aunque también podría llamar a su método de esta manera, LinkedListExtensions.ToArray(...), no creo que esa fuera la intención de los métodos de extensión. Como tal, creo que los creadores del framework probablemente crearon la restricción que se encontró como una forma de informar a los desarrolladores simplemente que los métodos de extensión son independientes y no están directamente relacionados con la clase en la que residen.

-1

Las clases estáticas deberían poder definirse como abstractas. Si fueran capaces de hacer esto, entonces podrían especificar que no podrían ser utilizados directamente, sino que deben heredarse como una clase regular y luego las clases genéricas estáticas serían factibles para los métodos de extensión, ya que podrían definirse en la clase abstracta. heredar de dicha clase, y todo funcionaría correctamente.

Como es ahora, hay un montón de limitaciones significativas con las clases estáticas, esto solo es una que realmente ensucia con el mojo en muchos casos. Combinado con la incapacidad de hacer que el compilador deduzca el tipo de devolución y requiera que ingrese la implementación genérica completa para llamar a funciones genéricas que no tienen todos los genéricos en la firma del método hace que esto sea extremadamente débil y crea una gran cantidad de escribiendo que no debería necesitar estar allí.

(También existe el caso donde el segundo genérico se define en la definición del primero y no lo usará, aunque es obvio inferir también en tiempo de compilación. Este es muy molesto porque es completamente obvio.) MS tiene una tonelada de trabajo en Generics que podrían estar haciendo para mejorar estas cosas.

Cuestiones relacionadas