2011-11-11 8 views
8

Estoy escribiendo un método generalizado para usarlo en una tarea especial en una plantilla T4. El método debería permitirme usar tipos especializados desde una interfaz general. Pensé en las siguientes firmas:¿Un método genérico puede usar tipos contravariantes/covariantes?

interface IGreatInterface { 
    Object aMethodAlpha<U>(U parameter) where U : IAnInterface; 
    Object aMethodBeta(IAnInterface parameter) 
} 

public class AnInterestingClass : IAnInterface{} 

Cuando trato de poner en práctica las opciones del compilador IGreatInterface un error de aMethodBeta() porque he hecho mi T4 para escribir ese método utilizando un subtipo de IAnInterface (es decir, quiero que aplicar método como este: Object aMethodBeta(AnInterestingClass parameter)).

Puede usar el método aMethodAlpha<U>() pero no está tan limpio como quiero porque mi T4 tiene que generar un código adicional. Yo (quizás erróneamente) propongo que una implementación de ese método, que tiene que ser hecha por un T4, podría ser
Object aMethodAlpha<AnInterestingClass>(AnInterestingClass parameter).

Estoy pensando que los métodos genéricos no admiten tipos contravariantes, pero no estoy seguro; Supongo que es la forma en que el compilador impide que el codificador a utilizar un tipo específico que tiene un método no definido en el tipo general ...

  1. tiene que utilizar el tipo exacto cuando se está aplicando un método genérico?
  2. ¿Hay algún truco para cambiar este comportamiento?
+0

No entiendo muy bien su pregunta. ¿Puedes publicar el código que implementa IGreatInterface y el error específico del compilador? – phoog

+2

@Juan: Como nota al margen, ayuda marcar sus actualizaciones con una * actualización * o algo así para que veamos qué ha cambiado. –

Respuesta

20

Esta pregunta es muy confuso . Déjame ver si puedo aclararlo.

Cuando trato de poner en práctica IGreatInterface las opciones del compilador de un error de aMethodBeta() porque he hecho que el método utilizando un subtipo de IAnInterface Quiero implementar ese método como este: Object aMethodBeta(AnInterestingClass parameter).

Eso no es legal. Simplificando un tanto:

class Food {} 
class Fruit : Food {} 
class Meat : Food {} 
interface IEater 
{ 
    void Eat(Food food); 
} 
class Vegetarian : IEater 
{ 
    public void Eat(Fruit fruit); 
} 

Clase Vegetarian no cumple con el contrato de IEater. Debería poder pasar cualquier Comida para comer, pero Vegetarian solo acepta fruta. C# no admite covariación del parámetro formal del método virtual porque no es seguro.

Ahora, se podría decir entonces, ¿qué tal esto:

interface IFruitEater 
{ 
    void Eat(Fruit fruit); 
} 
class Omnivore : IFruitEater 
{ 
    public void Eat(Food food); 
} 

Ahora tenemos la seguridad de tipos; Omnivore se puede utilizar como IFruitEater porque un Omnivore puede comer fruta, así como cualquier otro alimento.

Desafortunadamente, C# no es compatible con método virtual contraviorencia de tipo de parámetro formal aunque hacerlo en teoría es seguro. Pocos idiomas lo admiten.

De forma similar, C# no es compatible con varianza del tipo de retorno del método virtual tampoco.

No estoy seguro de si eso realmente respondió su pregunta o no. ¿Puedes aclarar la pregunta?

ACTUALIZACIÓN:

¿Qué hay de:

interface IEater 
{ 
    void Eat<T>(T t) where T : Food; 
} 
class Vegetarian : IEater 
{ 
    // I only want to eat fruit! 
    public void Eat<Fruit>(Fruit food) { } 
} 

No, eso no es legal tampoco. El contrato de IEater es que proporcionará un método Eat<T> que puede tomar cualquier T que es Food.Puede no parcialmente la ejecución del contrato, más de lo que usted puede hacer esto:

interface IAdder 
{ 
    int Add(int x, int y); 
} 
class Adder : IAdder 
{ 
    // I only know how to add two! 
    public int Add(2, int y){ ... } 
} 

Sin embargo, usted puede hacer esto:

interface IEater<T> where T : Food 
{ 
    void Eat(T t); 
} 
class Vegetarian : IEater<Fruit> 
{ 
    public void Eat(Fruit fruit) { } 
} 

Eso es perfectamente legal. Sin embargo, no se puede hacer:

interface IEater<T> where T : Food 
{ 
    void Eat(T t); 
} 
class Omnivore : IEater<Fruit> 
{ 
    public void Eat(Food food) { } 
} 

porque, de nuevo, C# no soporta método virtual contravarianza parámetro formal o covarianza.

Tenga en cuenta que C# hace apoyo polimorfismo paramétrico de covarianza cuando lo hace, es conocido por ser typesafe. Por ejemplo, esto es legal:

IEnumerable<Fruit> fruit = whatever; 
IEnumerable<Food> food = fruit; 

Se puede usar una secuencia de fruta como secuencia de alimentos. O bien,

IEnumerable<Fruit> fruitComparer = whatever; 
IComparable<Apples> appleComparer = fruitComparer; 

Si tiene algo que pueda comparar dos frutas cualquiera, puede comparar dos manzanas cualquiera.

Sin embargo, este tipo de covarianza y contravarianza solo es legal cuando todo lo siguiente es verdadero: (1) la varianza es probadamente segura, (2) el autor de las anotaciones de varianza agregadas que indican el co y contra -varianzas, (3) los diferentes tipos de argumentos involucrados son todos tipos de referencia, (4) el tipo genérico es un delegado o una interfaz.

+0

(¡Gran respuesta! Este comentario se utilizará para aclarar la pregunta más adelante ...). Usando su ejemplo: ¿puedo crear una firma 'IEater.Eat (T food) donde T: Food' y luego implementar' Omnivore.Eat (Fruit food) '? sin el compilador marcándome por no implementar 'IEater.Coma (comida T) '?. De su respuesta, creo que no es posible ... – JPCF

+0

@JuanPabloContreras: He agregado un texto adicional con respecto a su comentario. –

+0

@EricLippert De ser posible, podría dar su opinión sobre esta pregunta http://stackoverflow.com/questions/8109478/process-address-space-vs-virtual-memory – Sandeep

2

Si desea heredar de una interfaz genérica, consulte la respuesta de phoog. Si está tratando de implementar una interfaz de forma co-variante, eso me lleva a la discusión a continuación.

Supongamos:

internal interface IAnInterface { } 

public class SomeSubClass : IAnInterface { } 

public class AnotherSubClass : IAnInterface { } 

public GreatClass : IGreatInterface { ... } 

El problema con tratar de implementar la interfaz con un (co-variante) más derivado argumento es que no hay guarante cuando este se llama a través de una interfaz que un IAnInterface aprobada en será una SomeSubClass instancia. Es por eso que es no permitido directamente.

IGreatInterface x = new GreatClass(); 

x.aMethodBeta(new AnotherSubClass()); 

SI Usted podría hacer covarianza, esto sería un fracaso debido a que estaría esperando un SomeSubClass pero obtendría un AnotherSubClass.

Lo que podía hacer es hacer explícita la implementación de interfaces:

class GreatInterface : IGreatInterface 
{ 
    // explicitly implement aMethodBeta() when called from interface reference 
    object IGreatInterface.aMethodBeta(IAnInterface parameter) 
    { 
     // do whatever you'd do on IAnInterface itself... 
     var newParam = parameter as SomeSubClass; 

     if (newParam != null) 
     { 
      aMethodBeta(newParam); 
     } 

     // otherwise do some other action... 
    } 

    // This version is visible from the class reference itself and has the 
    // sub-class parameter 
    public object aMethodBeta(SomeSubClass parameter) 
    { 
     // do whatever 
    } 
} 

Por lo tanto, si se hizo esto, su interfaz es compatible con la genérica, la clase tiene un método más específico, pero sigue siendo compatible con la interfaz . La principal diferencia es que había necesidad de manejar el caso en el que se pasa de una aplicación inesperada de IAnInterface en

ACTUALIZACIÓN:. Suena como usted quiere algo como esto:

public interface ISomeInterface 
{ 
    void SomeMethod<A>(A someArgument); 
} 

public class SomeClass : ISomeInterface 
{ 
    public void SomeMethod<TA>(TA someArgument) where TA : SomeClass 
    { 

    } 
} 

Esto no está permitido cuando implemente un método genérico desde una interfaz, las restricciones deben coincidir.

+0

hmmm ... de su respuesta Puedo inferir que .net no acepta subtipos con métodos genéricos ... ¿Estoy en lo correcto? – JPCF

+0

@Juan: No estoy seguro de lo que quiere decir con "no acepta". ¿Te refieres como argumentos? Como tipos de parámetros? –

+0

@Juan: ¿Quiere decir que puedo implementar una interfaz pero hacer que el tipo sea más derivado que el tipo de interfaz? No, no puedes. Puede intercambiar argumentos alternativamente, contraer variadamente y puede, simultáneamente, asignar interfaces y delegados de forma covariante (si se marcan adecuadamente), pero no la implementación. –

0

Tal vez usted está buscando este:

interface IGreatInterface<in U> where U : IAnInterface 
{ 
    Object aMethodAlpha(U parameter); 
} 

class SomeClass : IAnInterface { /*...*/ } 

class GreatClass : IGreatInterface<SomeClass> 
{ 
    public Object aMethodAlpha(SomeClass parameter) {} 
} 

EDIT:

Sí, tiene usted razón: si se define un método genérico en una interfaz, no se puede aplicar este método con una método concreto usando un tipo compatible.

¿Cómo sobre el uso de un delegado (ya que los delegados apoyan co- y contravarianza):

[ejemplo de eliminar porque me dio la varianza hacia atrás - no funciona.]

+0

no ... por favor lea la actualización ... – JPCF

Cuestiones relacionadas