J'ai créé quelques interfaces et classes génériques pour travailler avec les rendez-vous de l'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; }
}
J'essaie d'utiliser certaines contraintes sur les paramètres de type pour garantir que seuls les types valides peuvent être spécifiés. Cependant, lorsque vous spécifiez une contrainte définissant que T
doit implémenter IAppointment<IAppointmentProperties>
, le compilateur donne une erreur lors de l'utilisation d'une classe qui est Appointment<AppointmentProperties>
:
class MyAppointment : Appointment<MyAppointmentProperties>
{
}
// This goes wrong:
class MyAppointmentEntry : AppointmentEntry<MyAppointment>
{
}
class MyAppointmentProperties : AppointmentProperties
{
public string ExtraInformation { get; set; }
}
L'erreur est:
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>'.
Quelqu'un pourrait-il expliquer pourquoi cela ne fonctionne pas?
Simplifions:
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>();
Votre question est: pourquoi la dernière ligne est-elle illégale?
Maintenant que j'ai réécrit le code pour le simplifier, il devrait être clair. n ICage<IAnimal>
est une cage dans laquelle vous pouvez placer n'importe quel animal , mais un Cage<Tiger>
ne peut contenir que des tigres , donc cela doit être illégal.
Si ce n'était pas illégal, vous pouvez le faire:
cage.Enclose(new Fish());
Et hé, vous venez de mettre un poisson dans une cage de tigre.
Le système de types n'autorise pas cette conversion car cela violerait la règle selon laquelle les capacités du type source ne doivent pas être inférieures aux capacités de la cible type. (Il s'agit d'une forme du fameux "principe de substitution de Liskov".)
Plus précisément, je dirais que vous abusez des génériques. Le fait que vous ayez établi des relations de type trop compliquées pour que vous vous analysiez est la preuve que vous devez simplifier le tout; si vous ne gardez pas toutes les relations de type droites et que vous avez écrit la chose, vos utilisateurs ne pourront sûrement pas non plus la garder droite.
Il y a déjà une très bonne réponse d'Eric. Je voulais juste saisir cette occasion pour parler de l'invariance , de la covariance et Contravariance ici.
Pour les définitions, veuillez consulter https://docs.Microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance
Disons qu'il y a un zoo.
abstract class Animal{}
abstract class Bird : Animal{}
abstract class Fish : Animal{}
class Dove : Bird{}
class Shark : Fish{}
Le zoo déménage, ses animaux doivent donc être déplacés de l'ancien zoo vers le nouveau.
Invariance
Avant de les déplacer, nous devons mettre les animaux dans différents conteneurs. Les conteneurs font tous les mêmes opérations: y mettre un animal ou en sortir un animal.
interface IContainer<T> where T : Animal
{
void Put(T t);
T Get(int id);
}
Évidemment, pour les poissons, nous avons besoin d'un réservoir:
class FishTank<T> : IContainer<T> where T : Fish
{
public void Put(T t){}
public T Get(int id){return default(T);}
}
Ainsi, les poissons peuvent être introduits et sortis du réservoir (si tout va bien encore en vie):
IContainer<Fish> fishTank = new FishTank<Fish>(); //Invariance, the two types have to be the same
fishTank.Put(new Shark());
var fish = fishTank.Get(8);
Supposons que nous soyons autorisés à le changer en IContainer<Animal>
, alors vous pouvez accidentellement mettre une colombe dans le réservoir, ce qui se produira de manière inconsciente.
IContainer<Animal> fishTank = new FishTank<Fish>(); //Wrong, some animal can be killed
fishTank.Put(new Shark());
fishTank.Put(new Dove()); //Dove will be killed
Contravariance
Afin d'améliorer l'efficacité, l'équipe de gestion du zoo dicide pour séparer le processus de chargement et de déchargement (la gestion le fait toujours). Nous avons donc deux opérations distinctes, l'une pour le chargement uniquement, l'autre pour le déchargement.
interface ILoad<in T> where T : Animal
{
void Put(T t);
}
Ensuite, nous avons une cage à oiseaux:
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
Covariance
Dans le nouveau zoo, nous avons une équipe pour décharger les animaux.
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();
Du point de vue de l'équipe, peu importe ce qu'il y a à l'intérieur, ils déchargent simplement les animaux des conteneurs.
Parce que vous avez déclaré votre classe MyAppointment
en utilisant le type concret plutôt que l'interface. Vous devez déclarer comme suit:
class MyAppointment : Appointment<IAppointmentProperties> {
}
Maintenant, la conversion peut se produire implicitement.
En déclarant AppointmentEntry<T>
avec la contrainte where T: IAppointment<IAppointmentProperties>
vous créez un contrat où le type non spécifié pour AppointmentEntry<T>
must accepte tout type déclaré avec IAppointmentProperties
. En déclarant le type avec la classe concrète, vous avez violé ce contrat (il implémente a type de IAppointmentProperties
mais pas any type).
Cela fonctionnera si vous redéfinissez l'exemple d'interface à partir de:
interface ICage<T>
à
interface ICage<out T>
(veuillez noter le mot clé out
)
alors l'énoncé suivant est correct:
ICage<IAnimal> cage = new Cage<Tiger>();
Dans le cas où quelqu'un d'autre a également ce message d'erreur: j'ai trouvé la même interface définie deux fois dans des espaces de noms différents et les classes qui ont été essayées d'être liées ensemble n'ont pas utilisé la même interface.