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:
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:
IList
IReadOnlyList
t-il pas IReadOnlyList
? 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:
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 .
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
public interface ICollection
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
. 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 .