2010-02-09 16 views
15

Me pregunto si de alguna manera es posible especializar métodos de interfaz genéricos de alguna manera en C#? He encontrado preguntas similares, pero nada exactamente como esto. Ahora sospecho que la respuesta es "No, no puedes", pero me gustaría que se confirme.especialización de interfaz genérica C#

Lo que tengo es algo como lo siguiente.

public interface IStorage 
{ 
    void Store<T>(T data); 
} 

public class Storage : IStorage 
{ 
    public void Store<T>(T data) 
    { 
     Console.WriteLine("Generic"); 
    } 

    public void Store(int data) 
    { 
     Console.WriteLine("Specific"); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     IStorage i = new Storage(); 
     i.Store("somestring"); // Prints Generic 
     i.Store(1); // Prints Generic 
     Storage s = (Storage)i; 
     s.Store("somestring"); // Prints Generic 
     s.Store(1); // Prints Specific 
    } 
} 

¿Hay alguna manera de hacer que utilice la versión especializada de la Tienda cuando se llama a través de la interfaz? Y si no, ¿alguien sabe la razón exacta por la cual C# trata los argumentos genéricos de esta manera?

Editar: El problema podría solucionarse si no fuera así para que C# no pueda resolver los argumentos de la plantilla en más de un paso.

void Foo<T>(T t) 
{ 
    SubFoo(t); 
} 

void SubFoo<T>(T t) 
{ 
    Console.WriteLine("Generic"); 
} 

void SubFoo(int t) 
{ 
    Console.WriteLine("Specific"); 
} 

Una llamada a Foo (1) que aquí se imprimirá "genérico", además, no debe el compilador ser capaz de resolver esto? ¿O el JIT previene esto?

+0

Posible duplicado de [Cómo hacer plantilla especialización en C#] (http://stackoverflow.com/questions/600978/how-to-do-template-specialization-in-c-sharp) –

Respuesta

18

La resolución de sobrecarga se realiza en tiempo de compilación, no en tiempo de ejecución en función del tipo real del valor pasado.

IStorage i = new Storage(); 
i.Store("somestring"); // Prints Generic 
i.Store(1); // Prints Generic 

Esto siempre llamará al método "genérico", porque sólo hay una sobrecarga de Store en IStorage y el compilador no sabe que i realidad contiene un objeto Storage. ¿Cómo puede el compilador saber sobre la otra sobrecarga en Storage?

Storage s = (Storage)i; 
s.Store("somestring"); // Prints Generic 
s.Store(1); // Prints Specific 

En este caso, el compilador sabe que s contiene un objeto Storage (o una derivada de Storage), porque s se declara de esa manera. Entonces ve dos sobrecargas. Elige la sobrecarga específica para los valores int, porque las reglas de resolución de sobrecarga dicen que prefieren sobrecargas específicas sobre sobrecargas genéricas.


es técnicamente posible determinar typeof(T) en el método genérico en tiempo de ejecución y enviar la llamada al método a un método específico. Pero si lo piensas, esto no tiene mucho sentido. Un método genérico significa que la misma implementación funciona para argumentos de diferentes tipos no relacionados. Si desea diferentes implementaciones para diferentes tipos, no debe usar genéricos para esto.


void Foo<T>(T t) 
{ 
    SubFoo(t); 
} 

void SubFoo<T>(T t); 
void SubFoo(int t); 

Generics funcionan bastante un poco diferente de plantillas de C++. El compilador de C# compila Foo solo una vez, a un método genérico. Recuerde: genérico significa la misma implementación para diferentes tipos. El compilador de C# no sabe en tiempo de compilación si T va a ser int o string o cualquier otro tipo. Entonces la única implementación posible de Foo que funciona para cualquier T es llamar a SubFoo <T>. Si se llamara a una de las sobrecargas SubFoo en función de T, la implementación de Foo ya no sería la misma para todas las T.

+0

+1 awesome explanation. –

+0

¿Cuál es el punto de la T genérica? ¿Por qué no simplemente escribe el parámetro de datos como objeto y prueba si (data is int)? –

+0

Eso tiene mucho sentido cuando se piensa en ello desde la perspectiva de los compiladores. Aunque tengo una memoria vaga, C++ podría lograr esto de alguna manera, aunque no podría hacerlo funcionar ahora. Editaré mi pregunta un poco para explicar un problema adicional que impide una solución a esto, espero que también puedas explicar eso, aunque aceptaré esta respuesta :) – Runeborg

0

se podría hacer algo como esto:

public interface IStorage<T> 
{ 
    void Store(object data); 
    void Store<T>(T data); 
} 

public class Storage : IStorage<int> 
{ 
    public void Store(object data) 
    { 
     Console.WriteLine("Generic"); 
    } 

    public void Store(int data) 
    { 
     Console.WriteLine("Specific"); 
    } 
} 

que ha escrito i como IStorage y que la interfaz no define el método tienda sobrecargado.

1

Si usted desea tomar ventaja de la resolución de sobrecarga en tiempo de compilación es posible que así ampliar la interfaz con un método que toma un int:

public interface IStorage 
{ 
    void Store<T>(T data); 
} 

public interface IIntStorage: IStorage 
{ 
    void Store(int data); 
} 

public class Storage : IIntStorage 
{ 
    public void Store<T>(T data) 
    { 
     Console.WriteLine("Generic"); 
    } 

    public void Store(int data) 
    { 
     Console.WriteLine("Specific"); 
    } 
} 

Ahora bien, si se llama a Store(1) a través de la interfaz de IIntStorage se utilizará el método especializado (similar a cómo llamó al método Storage directamente), pero si lo llama a través del IStorage, seguirá utilizando la versión genérica.

+0

Quería una interfaz común y no una clase especializada para cada tipo :) – Runeborg

-1

Dado que los genéricos C# son plantillas de tiempo de ejecución en algunas circunstancias, debe utilizar la especialización en tiempo de ejecución. Por ejemplo, en los métodos estáticos genéricos, la herencia y las interfaces no son utilizables. Si desea especializarse métodos estáticos genéricos - en particular los métodos de extensión - lo que tiene que detectar el tipo de código con construcciones como:

si (typeof (T) == typeof (bool))

Para especialización de tipos de referencia (como una cadena por ejemplo) y un argumento de datos T, usted preferiría:

cadena s = datos como cadena; if (s! = Null)

En este ejemplo, un problema proviene de la conversión entre T y bool en código especializado: Usted sabe que T es bool pero el lenguaje no permite la conversión entre esos tipos. La solución proviene del tipo de objeto: un objeto puede convertirse a cualquier tipo (la conversión se verifica en tiempo de ejecución y no en tiempo de compilación, en este caso). entonces si tiene

T data;

puede escribir:

bool b = (int) de datos (objeto); data = (T) (objeto) b;

Esto no es perfecto: si la igualdad de tipo es bastante rápida, en algunas circunstancias debe probar si T es un tipo derivado de un tipo especificado (un poco más). Y cuando T es un tipo de valor como bool, envía al objeto y luego vuelve a escribir tipo de valor promedio boxing/unboxing y prueba de tipo de tiempo de ejecución para tipos de referencia. El optimizador de tiempo de ejecución puede eliminar estos pasos innecesarios, pero no puedo decir si lo hacen.

Dependiendo del uso de su método estático, recuerde que puede aplicar donde T: ... restricciones en los tipos parametrizados. Y ese valor predeterminado (T) devuelve falso para booleano, cero para tipos de base numéricos y nulo para tipos de referencia.

Runtime especialización implica un adicional de pasos de prueba y el boxeo/unboxing/tipo de ejecución comprueba, por lo que no es la panacea, pero también permite que se especializan métodos genéricos en un tiempo aceptable en muchas circunstancias: Para una operación larga (en particular, para la optimización) o cuando ocultar o agrupar tipos de gestión de complejidad es más importante que las actuaciones.

+0

Antes de enviar desde 'T' o verificar el tipo de una instancia de' T', es una buena idea verificar el tipo de 'T' en sí. Si 'T' es un tipo de valor que no admite nulos, una ubicación de almacenamiento de tipo' T' contendrá una instancia de tipo 'T'; Llamar a 'GetType()' en la cosa en la ubicación de almacenamiento requeriría encajonarlo, pero verificar el tipo de 'T' no. Tenga en cuenta que incluso si 'T' no es un tipo de valor, es posible que una ubicación de almacenamiento de tipo' T' contenga una referencia a una instancia de tipo de valor encuadrado. – supercat

+0

El objetivo de la pregunta es evitar el casting. – Runeborg

2

¿Por qué la especialización genérica basada en códigos tiene mucho sentido en el mundo real y, en particular, en los métodos de extensión?

Tomaré un ejemplo de las colecciones porque evrybody posee colecciones de .NET más o menos.

Tomaré el ejemplo simple del método de extensión .Last(this IEnumerable<<T>> coll). En .NET Framework, este método utiliza especialización de tipo en código.

En primer lugar, en relación con el beneficio de la especialización tipo, este ejemplo es bastante claro.Algunas colecciones enumerables necesitan escanear toda la colección y devolver el último elemento, una matriz basada solo necesita devolver el último elemento asignado de la matriz, muchas listas vinculadas tienen un puntero al último elemento ... Así que implementar un genérico con especialización tipo puede hacer que el método .Last() sea mucho más eficiente.

En segundo lugar, porque este método es estático, tener muchas implementaciones para cada tipo de colección o interfaces no resolvería el problema de la correcta selección del método. En efecto, la selección del método correcto se realiza en tiempo de compilación según el tipo aparente de objeto coll. Si lo imagina, desea aplicar métodos de extensiones consecutivas en un List<<T>>, el primero puede no necesitar muchos por implementaciones especializadas de tipo colección y usar uno solo basado en IEnumerable<<T>>. Entonces, incluso si tenemos un .Last(this List<<T>> coll), el primer método de extensión no especializado devolverá un IEnumerable<<T>> y el .Last(this List<<T>> coll) especializado no se usará para el List<<T>>.

Así que si su código utiliza ensamblajes externos (incluso .NET Framework), si tiene que proporcionar una solución en dos semanas a un complejo problema arquitectónico ... deja el dominio de la perfección para entrar en el mundo real . Y la especialización de tipo genérico se convierte en una opción para no ignorar.

Cuestiones relacionadas