Pourquoi ICollection générique n’implémente-t-il pas IReadOnlyCollection dans .NET 4.5?

Dans .NET 4.5 / C # 5, IReadOnlyCollection est déclaré avec une propriété Count :

 public interface IReadOnlyCollection : IEnumerable, IEnumerable { int Count { get; } } 

Je me demande si ICollection n’aurait pas aussi bien pu implémenter l’ IReadOnlyCollection :

 public interface ICollection : IEnumerable, IEnumerable, *IReadOnlyCollection* 

Cela aurait signifié que les classes implémentant ICollection auraient automatiquement implémenté IReadOnlyCollection . Cela me semble raisonnable.

L’abstraction ICollection peut être considérée comme une extension de l’ IReadOnlyCollection . Notez que List , par exemple, implémente à la fois ICollection et IReadOnlyCollection .

Cependant, il n’a pas été conçu de cette manière.

Qu’est-ce que j’oublie ici? Pourquoi la mise en œuvre actuelle aurait-elle été choisie à la place?


METTRE À JOUR

Je cherche une réponse qui utilise le raisonnement de conception orientée object pour expliquer pourquoi:

  • Une classe concrète telle que List implémentant à la fois IReadOnlyCollection et ICollection

est un meilleur design que:

  • ICollection implémenter IReadOnlyCollection

Veuillez également noter qu’il s’agit essentiellement de la même question que:

  1. Pourquoi IList IReadOnlyList t-il pas IReadOnlyList ?
  2. Pourquoi IDictionary met-il IReadOnlyDictionary œuvre IReadOnlyDictionary ?

Jon était juste ici https://stackoverflow.com/a/12622784/395144 , vous devriez marquer sa réponse comme la réponse:

 int ICollection.Count { ... } // comstackr error! 

Étant donné que les interfaces peuvent avoir des implémentations explicites, l’extraction des interfaces de base n’est pas rétrocompatible (avec les classes de base, vous n’avez pas ce problème).

C’est pourquoi…

 Collection : IReadOnlyCollection List : IReadOnlyList Dictionary : IReadOnlyDictionary 

… mais pas leurs interfaces.

IMHO, ils ont fait une erreur de conception initialement, tout à fait insoluble maintenant (sans casser les choses).

EDIT: cacher ne aide pas, les anciennes implémentations (explicites) ne seront pas encore compilées (sans modifier le code):

 interface INew { T Get(); } interface IOld : INew { void Set(T value); new T Get(); } class Old : IOld { T IOld.Get() { return default(T); } void IOld.Set(T value) { } } 

‘Sample.Old’ n’implémente pas le membre d’interface ‘Sample.INew.Get ()’

Il y a probablement plusieurs raisons. Voici deux:

  1. D’énormes problèmes de compatibilité ascendante

    Comment écririez-vous la définition d’ ICollection ? Cela semble naturel:

     interface ICollection : IReadOnlyCollection { int Count { get; } } 

    Mais cela pose un problème, car IReadOnlyCollection déclare également une propriété Count (le compilateur émettra un avertissement ici). Mis à part l’avertissement, le laisser tel quel (ce qui équivaut à écrire new int Count ) permet aux développeurs d’avoir différentes implémentations pour les deux propriétés Count en implémentant au moins un explicitement. Cela pourrait être “amusant” si les deux implémentations décidaient de renvoyer des valeurs différentes. Permettre aux gens de se tirer dans le pied n’est pas le style de C #.

    OK, alors qu’en est-il de:

     interface ICollection : IReadOnlyCollection { // Count is "inherited" from IReadOnlyCollection } 

    Eh bien, cela casse tout le code existant qui a décidé de mettre en œuvre explicitement Count :

     class UnluckyClass : ICollection { int ICollection.Count { ... } // comstackr error! } 

    Il me semble donc qu’il n’y a pas de bonne solution à ce problème: soit vous cassez le code existant, soit vous imposez une implémentation sujette aux erreurs sur tout le monde . Donc, le seul coup gagnant est de ne pas jouer .

  2. Vérifications de type à l’exécution sémantique

    Il serait inutile d’écrire du code comme

     if (collection is IReadOnlyCollection) 

    (ou l’équivalent de la reflection) à moins que IReadOnlyCollection soit “opt-in”: si ICollection était un sur-ensemble de IReadOnlyCollection alors pratiquement toutes les classes de collection réussiraient ce test, le rendant inutile en pratique.

Ce serait sémantiquement faux, car évidemment, tous les ICollection sont pas en lecture seule.

Cela dit, ils auraient pu appeler l’interface IReadableCollection , alors qu’une implémentation pourrait s’appeler ReadOnlyCollection .

Cependant, ils ne sont pas allés dans cette voie. Pourquoi? J’ai vu un membre de l’équipe BCL écrire qu’il ne voulait pas que l’API des collections devienne trop compliquée . (Bien que ce soit déjà, franchement.)

Quand j’ai lu votre question pour la première fois, je me suis dit “Hé, il a raison! Mais le but d’une interface est de décrire un contrat – Une description du comportement. Si votre classe implémente IReadOnlyCollection , elle doit être en lecture seule. Collection n’est pas. Donc, pour une Collection implémentant une interface qui implique implicitement la lecture seule pourrait entraîner des problèmes assez désagréables. Les consommateurs d’un IReadOnlyCollection vont faire certaines suppositions à propos de cette collection sous-jacente – comme itérer sur elle est sûre car personne ne peut le modifier, etc. etc. S’il était structuré comme vous le suggérez, cela pourrait ne pas être vrai!

Le raisonnement de conception orienté object proviendrait de la relation “Is A” entre une classe et toutes les interfaces implémentées par la classe.

Dans .NET 4.5 / c # 5.0 List est à la fois un ICollection et un IReadOnlyCollection . Vous pouvez instancier une List tant que type, en fonction de la manière dont vous souhaitez que votre collection soit utilisée.

Si ICollection implémente IReadOnlyCollection , vous avez la possibilité d’avoir List soit ICollection ou IReadOnlyCollection , ce qui signifie que ICollection “Is A” IReadOnlyCollection qui ce n’est pas.

Actualisé

Si toutes les classes ayant hérité d’ ICollection implémentaient IReadOnlyCollection je dirais qu’il est plus logique que ICollection implémente l’interface. Comme ce n’est pas le cas, pensez à ce que cela donnerait si ICollection implémentait IReadOnlyCollection :

public interface IReadOnlyCollection : IEnumerable, IEnumerable{}
public interface ICollection
: IReadOnlyCollection {}

Si vous deviez regarder la signature pour ICollection , cela ressemble à la collection IS-A en lecture seule. Attendez, regardons IReadOnlyCollection et ahh … il implémente IEnumerable et IEnumerable pour ne pas nécessairement être une collection en lecture seule. Fonctionnellement, les implémentations proposées par la question d’origine sont les mêmes, mais sémantiquement, je pense qu’il est plus correct de pousser une implémentation de l’interface aussi haut que possible.

Cette interface est plus une interface de description alors vraiment fonctionnelle.

Dans l’approche suggérée, chaque collection doit être comprise comme quelque chose que vous ne pouvez lire qu’une annonce est toujours la même. Donc, vous ne pouvez pas append ou supprimer une chose après la création.

Nous pourrions nous demander pourquoi l’interface s’appelle IReadOnlyCollection , pas ‘IReadOnlyEnumerable’ car elle utilise IEnumerable, IEnumerable . La réponse est simple: ce type d’interface n’aurait aucune senece, car Enumerable est déjà en lecture seule.

Alors regardons dans doc IReadOnlyCollection (T)

La première (et dernière) phrase dans la description dit:

Représente une collection d’éléments fortement typés et en lecture seule.

Et je pense que cela explique tout si vous savez à quoi sert le typage fort .