type method generic create c# generics implicit-conversion type-constraints

c# - method - ¿Por qué una restricción de tipo genérico produce un error de conversión de referencia no implícita?



reflection generic type (4)

He creado un par de interfaces y clases genéricas para trabajar con citas de agenda:

interface IAppointment<T> where T : IAppointmentProperties { T Properties { get; set; } } interface IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties> { DateTime Date { get; set; } T Appointment { get; set; } } interface IAppointmentProperties { string Description { get; set; } } class Appointment<T> : IAppointment<T> where T : IAppointmentProperties { public T Properties { get; set; } } class AppointmentEntry<T> : IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties> { public DateTime Date { get; set; } public T Appointment { get; set; } } class AppointmentProperties : IAppointmentProperties { public string Description { get; set; } }

Estoy tratando de usar algunas restricciones en los parámetros de tipo para asegurar que solo puedan especificarse los tipos válidos. Sin embargo, cuando se especifica una restricción que define que T debe implementar IAppointment<IAppointmentProperties> , el compilador IAppointment<IAppointmentProperties> un error cuando usa una clase que es Appointment<AppointmentProperties> :

class MyAppointment : Appointment<MyAppointmentProperties> { } // This goes wrong: class MyAppointmentEntry : AppointmentEntry<MyAppointment> { } class MyAppointmentProperties : AppointmentProperties { public string ExtraInformation { get; set; } }

El error es:

The type ''Example.MyAppointment'' cannot be used as type parameter ''T'' in the generic type or method ''Example.AppointmentEntry<T>''. There is no implicit reference conversion from ''Example.MyAppointment'' to ''Example.IAppointment<Example.IAppointmentProperties>''.

¿Alguien podría explicar por qué esto no funciona?


Funcionará si vuelve a definir la interfaz de muestra desde:

interface ICage<T>

a

interface ICage<out T>

(por favor note la palabra clave de out )

entonces la siguiente afirmación es correcta:

ICage<IAnimal> cage = new Cage<Tiger>();


Porque declaró su clase MyAppointment utilizando el tipo concreto en lugar de la interfaz. Debe declarar de la siguiente manera:

class MyAppointment : Appointment<IAppointmentProperties> { }

Ahora la conversión puede ocurrir implícitamente.

Al declarar a AppointmentEntry<T> con la restricción where T: IAppointment<IAppointmentProperties> , está creando un contrato por el cual el tipo no especificado para AppointmentEntry<T> debe acomodar cualquier tipo que se declare con IAppointmentProperties . Al declarar el tipo con la clase concreta que ha violado ese contrato (implementa un tipo de IAppointmentProperties pero no cualquier tipo).


Vamos a simplificar:

interface IAnimal { ... } interface ICage<T> where T : IAnimal { void Enclose(T animal); } class Tiger : IAnimal { ... } class Fish : IAnimal { ... } class Cage<T> : ICage<T> where T : IAnimal { ... } ICage<IAnimal> cage = new Cage<Tiger>();

Tu pregunta es: ¿por qué es ilegal la última línea?

Ahora que he reescrito el código para simplificarlo, debería estar claro. Un ICage<IAnimal> es una jaula en la que puedes colocar cualquier animal , pero un Cage<Tiger> solo puede contener tigres , por lo que esto debe ser ilegal.

Si no fuera ilegal entonces podrías hacer esto:

cage.Enclose(new Fish());

Y oye, acabas de poner un pez en una jaula de tigres.

El sistema de tipos no permite esa conversión porque hacerlo violaría la regla de que las capacidades del tipo de origen no deben ser menores que las capacidades del tipo de destino. (Esta es una forma del famoso "principio de sustitución de Liskov".)

Más específicamente, diría que estás abusando de los genéricos. El hecho de que hayas establecido relaciones de tipo que son demasiado complicadas para que te analices a ti mismo es una prueba de que deberías simplificar todo esto; Si no está manteniendo todas las relaciones de tipo correctas y las escribió, sus usuarios seguramente tampoco podrán hacerlo.


Ya hay una muy buena respuesta por parte de Eric. Solo quería aprovechar esta oportunidad para hablar sobre Invariance , Covariance y Contravariance aquí.

Para las definiciones, consulte https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx

Digamos que hay un zoológico.

abstract class Animal{} abstract class Bird : Animal{} abstract class Fish : Animal{} class Dove : Bird{} class Shark : Fish{}

El zoológico se está reubicando, por lo que sus animales deben ser trasladados del viejo zoológico al nuevo.

Invariancia

Antes de que los movamos, tenemos que poner a los animales en diferentes contenedores. Todos los contenedores realizan las mismas operaciones: poner un animal en él o sacar un animal de él.

interface IContainer<T> where T : Animal { void Put(T t); T Get(int id); }

Obviamente para los peces necesitamos un tanque:

class FishTank<T> : IContainer<T> where T : Fish { public void Put(T t){} public T Get(int id){return default(T);} }

Así que los peces se pueden poner y salir del tanque (esperemos que aún estén vivos):

IContainer<Fish> fishTank = new FishTank<Fish>(); //Invariance, the two types have to be the same fishTank.Put(new Shark()); var fish = fishTank.Get(8);

Supongamos que se nos permite cambiarlo a IContainer<Animal> , y luego, accidentalmente, se puede poner una paloma en el tanque, lo cual, sin pensarlo, ocurrirá una tragedia.

IContainer<Animal> fishTank = new FishTank<Fish>(); //Wrong, some animal can be killed fishTank.Put(new Shark()); fishTank.Put(new Dove()); //Dove will be killed

Contravarianza

Para mejorar la eficiencia, el equipo de administración del zoológico decide separar el proceso de carga y descarga (la administración siempre hace esto). Entonces tenemos dos operaciones separadas, una para cargar solamente, la otra para descargar.

interface ILoad<in T> where T : Animal { void Put(T t); }

Entonces tenemos una jaula de pájaros:

class BirdCage<T> : ILoad<T> where T : Bird { public void Put(T t) { } } ILoad<Bird> normalCage = new BirdCage<Bird>(); normalCage.Put(new Dove()); //accepts any type of birds ILoad<Dove> doveCage = new BirdCage<Bird>();//Contravariance, Bird is less specific then Dove doveCage.Put(new Dove()); //only accepts doves

Covarianza

En el nuevo zoológico tenemos un equipo para descargar animales.

interface IUnload<out T> where T : Animal { IEnumerable<T> GetAll(); } class UnloadTeam<T> : IUnload<T> where T : Animal { public IEnumerable<T> GetAll() { return Enumerable.Empty<T>(); } } IUnload<Animal> unloadTeam = new UnloadTeam<Bird>();//Covariance, since Bird is more specific then Animal var animals = unloadTeam.GetAll();

Desde el punto de vista del equipo, no importa lo que esté dentro, simplemente descargan los animales de los contenedores.