2016-07-17 8 views
1

J'ai rencontré un cas où le fait que List.AsReadOnly() renvoie un ReadOnlyCollection au lieu d'un IReadOnlyCollection a rendu les choses difficiles pour moi. Comme la collection retournée était la Value d'un Dictionary, elle ne pouvait pas être automatiquement mise à jour à IReadOnlyCollection. Cela semblait étrange, et après examen du code source .Net, j'ai confirmé que la méthode AsReadOnly() fait quelque chose de différent pour List que pour Dictionary, à savoir renvoyer la classe concrète au lieu de l'interface.Pourquoi List.AsReadOnly renvoie-t-il un ReadOnlyCollection mais Dictionary.AsReadOnly renvoie un IReadOnlyDictionary?

Quelqu'un peut-il expliquer pourquoi c'est? Il semble que ce soit un mauvais service d'avoir cette incohérence, surtout parce que nous voulons utiliser les interfaces quand c'est possible et surtout quand il est public.

Dans mon code, je pensais d'abord que puisque mon client était juste une méthode privée, je pouvais changer sa signature de paramètre de IReadOnlyDictionary<T, IReadOnlyCollection<T>> à IReadOnlyDictionary<T, ReadOnlyCollection<T>>. Mais, je compris que ce fait ressembler à la méthode privée pourrait modifier les valeurs de collecte, donc je mets un casting explicite gênant dans le code précédent afin de pouvoir utiliser correctement l'interface:

.ToDictionary(
    item => item, 
    item => (IReadOnlyCollection<T>) relatedItemsSelector(item) 
     .ToList() 
     .AsReadOnly() // Didn't expect to need the direct cast 
) 

Oh, et depuis Je vois toujours la covariance et la contravariance confuses, est-ce que quelqu'un pourrait me dire lequel empêche la distribution automatique et essayer de me rappeler de façon raisonnable comment se souvenir d'eux pour l'avenir? (par exemple, les collections ne sont pas ______ variable [co/contra] pour _____ [entrée/sortie] paramètres.) Je comprends pourquoi cela ne peut pas être, car il peut y avoir de nombreuses implémentations de l'interface et il n'est pas sûr de jeter tous les éléments individuels du dictionnaire au type demandé. Sauf si je souffle même cet aspect simple et je ne le comprends pas, dans ce cas, j'espère que vous pouvez m'aider à corriger ...

Répondre

0

La raison est historique. Les interfaces IReadOnly * ont été ajoutées dans .NET 4.5, tandis que List<T>.AsReadOnly() a été ajouté dans .NET 2.0. Changer son type de retour aurait été un changement de rupture.

La distribution explicite n'est pas si mauvaise. Ce n'est même pas un cast d'exécution, puisque le compilateur peut le vérifier statiquement (aucune distribution n'est émise vers IL). En passant, vous pouvez le convertir en IReadOnlyList<T> qui offre également un accès indexé à la liste. Vous pouvez également écrire une méthode d'extension qui renvoie le type dont vous avez besoin (par exemple, AsReadOnlyList()).

En ce qui concerne la variance {co, contra}, je trouve plus facile de se souvenir en utilisant les mots-clés C# in (contravariant) et out (covariant). in paramètres de type peuvent uniquement apparaître comme entrée arguments de méthode, tandis que les paramètres de type out peuvent uniquement apparaître comme sortie (valeurs de retour). Une méthode qui accepte un paramètre, par ex. de type Base, est sûr d'être appelé avec le type Derived, par conséquent, il est sûr de lancer in paramètres dans cette direction. out est juste le contraire.

Par exemple:

interface IIn<in T> { Set(T value); } 
IIn<Base> b = ... 
IIn<Derived> d = b; 
d.Set(derived); // safe since any method accepting Base can handle Derived 

interface IOut<out T> { T Get(); } 
IOut<Derived> d = ... 
IOut<Base> b = d; 
b.Get(); // safe since any Derived is Base 

non en lecture seule interfaces de collecte ne peuvent pas être * -variant car ils auraient à être à la fois in et out, et ce serait dangereux. Le compilateur et le CLR ne le permettent pas. .NET a une forme de variance à risque avec des tableaux:

var a = new[] { "s" }; 
var o = (object[])a; 
o[0] = 1; // ArrayTypeMismatchException 

Vous pouvez voir comment ils voulaient éviter ce gâchis avec une variance générique. Vraisemblablement, ils pourraient ajouter des interfaces en écriture seule qui permettraient la direction in (cotravariant), mais je suppose qu'ils n'ont pas trouvé beaucoup de valeur dans cela.

+0

Oh oh oh! Je supposais qu'il ne se lancerait pas automatiquement car il * n'était pas * covariant, mais maintenant je vois que précisément parce que 'IReadOnlyCollection' est en lecture seule, le compilateur peut voir qu'il est sûr de transtyper du type dérivé à la base . Donc, si c'est le cas, pourquoi ai-je dû faire une distribution explicite? – ErikE

+0

La covariance et la contravariance ne se produisent que lorsque vous transtypez entre deux * interfaces * génériques et modifiez le * paramètre de type * (de base à dérivé ou vice-versa). Le casting que vous avez utilisé est juste une distribution - c'est permis parce que ReadOnlyCollection 'implémente' IReadOnlyList '. –

+0

Oh, oups, ça a du sens. Cela devient plus clair. Pourquoi ne pouvait-il pas implicitement jeter? – ErikE