Runtime: Veuillez ajouter l'interface IReadOnlySet et faire en sorte que HashSet, SortedSet l'implémente

Créé le 9 juin 2015  ·  104Commentaires  ·  Source: dotnet/runtime

Original

Depuis que IReadOnlyList été ajouté, la parité entre les ensembles et les listes a diminué. Ce serait bien de le rétablir.

L'utiliser serait une manière indépendante de l'implémentation de dire : "voici cette collection en lecture seule où les éléments sont uniques".

Il est clair que beaucoup de gens en ont besoin :

Serveur SQL : https://msdn.microsoft.com/en-us/library/gg503096.aspx
Roslyn : https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/InternalUtilities/IReadOnlySet.cs
Certains gars dans les commentaires : http://blogs.msdn.com/b/bclteam/archive/2013/03/06/update-to-immutable-collections.aspx

J'ai trouvé cette discussion en travaillant sur un problème du monde réel où je voulais utiliser la collection de clés d'un dictionnaire pour les opérations en lecture seule. Afin de soutenir ce cas, voici l'API que je propose.

Éditer

Raisonnement

API proposée

 namespace System.Collections.Generic {
+    public interface IReadOnlySet<out T> : IReadOnlyCollection<T>, IEnumerable, IEnumerable<T> {
+        bool Contains(T value);
+        bool IsProperSubsetOf(IEnumerable<T> other);
+        bool IsProperSupersetOf(IEnumerable<T> other);
+        bool IsSubsetOf(IEnumerable<T> other);
+        bool IsSupersetOf(IEnumerable<T> other);
+        bool Overlaps(IEnumerable<T> other);
+        bool SetEquals(IEnumerable<T> other);
+    }
-    public class HashSet<T> : ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T> {
+    public class HashSet<T> : ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T>, IReadOnlySet<T> {
     }
-    public class SortedSet<T> : ICollection, ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T> {
+    public class SortedSet<T> : ICollection, ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T>, IReadOnlySet<T> {
     }
+    public class ReadOnlySet<T> : ICollection<T>, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlySet<T>, ISet<T> {
+        public int Count { get; }
+        public ReadOnlySet(ISet<T> set);
+        public bool Contains(T value);
+        public bool IsProperSubsetOf(IEnumerable<T> other);
+        public bool IsProperSupersetOf(IEnumerable<T> other);
+        public bool IsSubsetOf(IEnumerable<T> other);
+        public bool IsSupersetOf(IEnumerable<T> other);
+        public bool Overlaps(IEnumerable<T> other);
+        public bool SetEquals(IEnumerable<T> other);
+    }
     public class Dictionary<TKey, TValue> {
-        public sealed class KeyCollection : ICollection, ICollection<TKey>, IEnumerable, IEnumerable<TKey>, IReadOnlyCollection<TKey> {
+        public sealed class KeyCollection : ICollection, ICollection<TKey>, IEnumerable, IEnumerable<TKey>, IReadOnlyCollection<TKey>, IReadOnlySet<TKey> {
+            public bool IsProperSubsetOf(IEnumerable<TKey> other);
+            public bool IsProperSupersetOf(IEnumerable<TKey> other);
+            public bool IsSubsetOf(IEnumerable<TKey> other);
+            public bool IsSupersetOf(IEnumerable<TKey> other);
+            public bool Overlaps(IEnumerable<TKey> other);
+            public bool SetEquals(IEnumerable<TKey> other);
         }
     }
 }

Questions ouvertes

  • L'ajout de ces méthodes à Dictionary<TKey, TValue>.KeyCollection acceptable compte tenu du coût de la taille du code pour un type générique couramment instancié ? Voir ici .
  • IReadOnlySet<T> devrait-il être implémenté dans d'autres Dictionary KeyCollection s tels que SortedDictionary ou ImmutableDictionary ?

Mises à jour

  • Suppression de TryGetValue car ce n'est pas dans ISet<T> et en tant que tel empêcherait potentiellement de rebaser ISet<T> pour implémenter IReadOnlySet<T> .
  • Ajout ReadOnlySet<T> classe ReadOnlyCollection<T> .
api-approved area-System.Collections up-for-grabs

Commentaire le plus utile

Conception d'API proposée :

public interface IReadOnlySet<T> : IReadOnlyCollection<T> {    
    bool Contains(T item);
    bool IsSubsetOf(IEnumerable<T> other);
    bool IsSupersetOf(IEnumerable<T> other);
    bool IsProperSupersetOf(IEnumerable<T> other);
    bool IsProperSubsetOf(IEnumerable<T> other);
    bool Overlaps(IEnumerable<T> other);
    bool SetEquals(IEnumerable<T> other);
}

Ceci est basé sur l'API ISet<> (sauf évidemment les méthodes de mutation).

C'est dommage que Comparer ne rentre pas dans cela, mais alors ni ISet<> ni IReadOnlyDictionary<> n'exposent les comparateurs, il est donc trop tard pour réparer maintenant.

Tous les 104 commentaires

Ce ne serait pas bien si nous avions juste une construction de langage pour rendre les choses immuables ? Alors nous n'aurions pas besoin d'avoir ces interfaces magiques.

@whoisj Quelle langue ? Le CLR en possède des dizaines.

Même en tant que fonctionnalité linguistique potentielle, cela nécessiterait une représentation de métadonnées. Dans ce cas, une interface marqueur (ou interface comportementale) est aussi bonne qu'une autre. Essayer de transmettre l'immuabilité d'un type de collection à travers une nouvelle entrée de métadonnées ne semble pas approprié car le CLR ne devrait pas faire d'hypothèses sur le fonctionnement interne d'une classe arbitraire (et le CLR n'a aucun concept de classes de collection à part pour tableaux).

@whoisj Je pense que cela est au moins envisagé pour l'une des futures versions de C#. Mais cette décision n'affecte pas le besoin d'interfaces symétriques dans toutes les collections. De plus, je peux imaginer des scénarios où une liste en lecture seule d'éléments modifiables pourrait être utile, par exemple dans des jeux qui se soucient à la fois de la qualité du code et des performances.

Aussi des collections immuables sont déjà disponibles :

https://msdn.microsoft.com/en-us/library/system.collections.immutable (v=vs.111).aspx

Pour obtenir une collection entièrement immuable, nous avons juste besoin d'un moyen de définir un immutable T , puis de l'utiliser pour déclarer une collection Immutable...<T> .

@HaloFour, nous avons déjà emprunté cette voie :smirk: mais je pense toujours que le CLR a besoin d'un moyen de dire "voici une poignée, lisez-la, mais toutes les actions qui provoquent tout type d'écriture échoueront ; oh et cette immutabilité est contagieuse, donc tout pointeur atteint depuis le pointeur immuable est également immuable (y compris this )".

@dsaf absolument ! Dans un autre numéro, j'ai proposé que nous ayons un anti-terme inscriptible pour readdonly afin de permettre l'utilisation d'une collection en lecture seule d'éléments inscriptibles. Quelque chose du genre readonly Bag<writable Element> .

J'ai suggéré que toute référence marquée d'un & soit traitée comme immuable par le compilateur. Je pense toujours qu'il suffit d'une vérification au moment de la compilation, et nécessairement imposée par le CLR lui-même, car je recherche principalement une vérification de la logique au moment de la compilation et non des garanties d'exécution. Cela couvrirait toute référence que les développeurs souhaitaient être immuable, mais par appel.

@whoisj Peut-être, mais c'est assez tangentiel et cela transforme cette demande de quelque chose que le dsaf pourrait brancher/PR cet après-midi en quelque chose qui implique des efforts entre trois équipes différentes.

Vous traitez également cela comme un problème de compilateur. À ce stade, aucun compilateur n'est impliqué (au-delà du compilateur JIT) et seul le vérificateur peut tenter d'empêcher l'exécution d'un code « incorrect ». Même les mécanismes d'immutabilité d'exécution existants, les champs initonly , peuvent être facilement vaincus si la vérification est ignorée (ou par réflexion).

Je suis d'accord que ce serait bien si le langage C# et le compilateur pouvaient mieux prendre en charge les méthodes "pures". L'attribut PureAttribute existe déjà mais il est utilisé sporadiquement et il n'y a pas vraiment de support de langue pour cela. Et même si C# le supportait via des erreurs de compilation (pure ne peut appeler que pur, etc.), il est très facilement vaincu en utilisant un autre langage. Mais ces méthodes doivent s'annoncer et s'imposer car une fois compilées en IL, tous les paris sont fondamentalement désactivés et aucun des compilateurs ne peut modifier la façon dont un assembly existant s'exécute.

@Foire HaloFour .

En supposant que nous n'ayons pas de moyen général de prendre en charge les références "pures" ou "const", alors je suppose que la proposition est la meilleure alternative.

Si vous en avez besoin maintenant, ma bibliothèque Commons (Commons.Collections https://github.com/yanggujun/commonsfornet/tree/master/src/Commons.Collections/Set ) prend en charge le jeu en lecture seule. Admin, veuillez supprimer ce message s'il s'agit d'une publicité... Ma suggestion est de rechercher une implémentation open source.

@yanggujun Merci pour la suggestion, cela semble être une belle bibliothèque, mais je vais lancer la mienne pour éviter des dépendances supplémentaires.

Ma suggestion est de chercher une implémentation open source.

Il s'agit d'une solution de contournement, les interfaces fondamentales comme IReadOnlySet devraient vraiment faire partie du .NET Framework lui-même.

Cela a-t-il besoin d'un speclet pour devenir "prêt pour l'examen de l'API" ?

Et pendant que nous y sommes, envisagez de le nommer différemment de "ReadOnly" (voir article intéressant : http://stackoverflow.com/questions/15262981/why-does-listt-implement-ireadonlylistt-in-net-4- 5)
"Lisible" semble bien.

@GiottoVerducci Non. Je préférerais garder un modèle de nommage cohérent même s'il est imparfait. Vous êtes cependant libre de soulever un problème distinct pour renommer les interfaces existantes.

Conception d'API proposée :

public interface IReadOnlySet<T> : IReadOnlyCollection<T> {    
    bool Contains(T item);
    bool IsSubsetOf(IEnumerable<T> other);
    bool IsSupersetOf(IEnumerable<T> other);
    bool IsProperSupersetOf(IEnumerable<T> other);
    bool IsProperSubsetOf(IEnumerable<T> other);
    bool Overlaps(IEnumerable<T> other);
    bool SetEquals(IEnumerable<T> other);
}

Ceci est basé sur l'API ISet<> (sauf évidemment les méthodes de mutation).

C'est dommage que Comparer ne rentre pas dans cela, mais alors ni ISet<> ni IReadOnlyDictionary<> n'exposent les comparateurs, il est donc trop tard pour réparer maintenant.

    bool Contains(T item);

Cela ne devrait-il pas être plutôt dans IReadOnlyCollection<T> puisque ICollection<T> a Contains(T item) ?

Le package de collections immuables a été supprimé de nuget alors qu'il était encore en version bêta.
Je pense que c'est un cas d'utilisation assez courant et qu'il devrait être géré dans les bibliothèques standard.

Y a-t-il plus de travail à faire sur l'API ici, comme le suggère la balise ? Je suis heureux de passer du temps là-dessus si cela peut être utile et que quelqu'un puisse indiquer ce qui est nécessaire.

L'API proposée par @ashmind a fière allure.

Est-ce que ISet<T> peut être fait pour étendre IReadOnlySet<T> ? Cela ne s'est pas produit IList<T> / IReadOnlyList<T> ?

Sinon, je suppose que les autres modifications à considérer sont l'ajout de IReadOnlySet<T> à la liste des interfaces pour toutes les implémentations ISet<T> dans corefx, y compris HashSet<T> , SortedSet<T> et leurs contreparties immuables dans System.Collections.Immutable .

Je suis d'accord avec @GiottoVerducci . L'utilisation d'un nom comme IReadOnlySet<T> ne déclare pas les capacités des contrats, mais les limitations des contrats. Utiliser ensuite ce même contrat combiné à un autre qui contredit ces limitations est source de confusion. Je pense que le nom du contrat doit décrire une affirmation positive concernant ce que l'implémenteur prend en charge. Un nom comme IReadableSet<T> n'est pas génial, certes, mais il décrit au moins mieux ce que fait l'implémenteur.

@HaloFour Je suis d'accord sur le principe, mais nous avons maintenant la même situation avec IReadOnlyList<T> . Le maintien de la cohérence l'emporte sur l'augmentation de la précision ici, à mon humble avis.

@drewnoakes

Je comprends, et la cohérence est importante. Je pense que cela explique aussi pourquoi ISet<T> ne devrait pas étendre IReadOnlySet<T> , cependant.

Le maintien de la cohérence l'emporte sur l'augmentation de la précision ici, à mon humble avis.

Je pense que cela explique aussi pourquoi ISet<T> ne devrait pas étendre IReadOnlySet<T> , cependant.

Je pense que vous manquez le point. C'est la raison pour laquelle IList<T> , ICollection<T> , IDictionary<TKey, TValue> devraient, en plus de ISet<T> , également être corrigés pour implémenter des interfaces de vue en lecture seule. Sinon, tout le monde doit continuer à être confus lorsqu'il s'agit de contourner la conception peu intuitive de la BCL .

@binki

Je ne suis pas en désaccord. Ce que je n'aime pas à ce sujet, c'est qu'un contrat stipulant un comportement en lecture seule est prolongé par un contrat stipulant un comportement en lecture _écriture_. Le nom est faux et la composition est fausse. Mais nous y sommes. J'adorerais voter pour changer les deux, mais je doute que ce soit sur la table.

@HaloFour

Quand vous obtenez une interface dans quelque chose, c'est une _vue_ dans quelque chose. La vue elle-même _est_ en lecture seule. En supposant que vous ayez écrit du code de type sécurisé et que vous ne feriez pas de transtypage ascendant, si vous recevez quelque chose qui est en lecture seule, il est, à toutes fins utiles, en lecture seule. Cela ne garantit pas que les données ne changeront pas. C'est comme ouvrir un fichier en lecture seule. Un fichier ouvert en lecture seule peut être muté par un autre processus. Ou comme un accès en lecture seule aux pages d'un site Web où un administrateur aurait une vue en lecture-écriture sur les données et pourrait les modifier sous vous.

Je ne sais pas pourquoi lecture seule est considéré comme le mauvais terme ici. Lecture seule n'implique _pas_ immuable. Il existe tout un package nuget/une API différente (où l'ajout/suppression génère un nouvel objet et l'instance actuelle est garantie de ne jamais muter, donc immuable) pour cela si c'est ce dont vous avez besoin.

Je pensais à quelque chose de similaire. "Lecture seule" dans .NET est également une garantie assez faible pour les champs. Étant donné une reprise, je suis sûr que tout cela aurait plus de sens. Pour l'instant, cela vaut la peine d'être pragmatique.

Donc en général, si une méthode accepte un IReadOnlySomething<T> vous pouvez, en général, supposer qu'elle ne le modifiera pas. Il n'y a aucune garantie que la méthode de réception ne transfère pas la référence, et il n'y a aucune garantie que l'implémentation de l'interface ne se modifie pas en interne lors de l'accès non plus.

En C++, const_cast affaiblit aussi les garanties de const , ce qui est dommage (surtout de nos jours avec le modificateur mutable ) mais en pratique cela n'enlève rien à l'utilité const est une fonctionnalité. Il faut juste savoir à quoi on a affaire.

@binki fait une bonne distinction. _Immuable_ dans le nom implique une garantie ferme de stabilité dans le temps pour toutes les parties concernées.

Quelqu'un a-t-il une source faisant autorité expliquant pourquoi IList<T> n'étend pas IReadOnlyList<T> ?

@binki

Une interface n'est pas une vue, c'est un contrat. Ce contrat déclare les capacités de l'implémenteur. Si l'implémenteur n'implémente pas réellement ces capacités, je considérerais cela comme une violation de contrat. Cette classe List<T> prétend qu'elle "est-un" IReadOnlyList<T> , mais ce n'est pas le cas. Il manque cette capacité.

Il existe plusieurs écoles de pensée sur ce sujet. J'appartiens clairement à l'école où l'héritage d'interface suit plus strictement les relations « est-un » entre les types. Je soutiens certainement une approche plus granulaire de la composition avec des interfaces et pense que List<T> et ses proches pourraient probablement bénéficier de la mise en œuvre de 3-4 interfaces supplémentaires (lecture, écriture, ajout, etc.) Mais je pense certainement que le Le nom d'une interface doit décrire ce qu'un type _peut_ faire, pas ce qu'il _ne pas_ faire. Les assertions de capacité négatives n'ont pas beaucoup de sens pour les contrats.

@drewnoakes

Pour l'instant, cela vaut la peine d'être pragmatique.

Je suis d'accord. Nous sommes là où nous en sommes. Si IList<T> devaient être modifiés pour étendre IReadOnlyList<T> alors il est logique que ISet<T> soit modifié pour étendre IReadOnlySet<T> , etc.

Est-ce trop redondant pour pousser également les interfaces IReadableX<T> , IWritableX<T> , etc. à cohabiter avec IReadOnlyX<T> ?

Quelqu'un a-t-il une source faisant autorité expliquant pourquoi IList<T> n'étend pas IReadOnlyList<T> ?

Apparemment, ce serait un changement révolutionnaire lors du chargement d'assemblys compilés avec des frameworks .net plus anciens. Parce que lors de l'implémentation d'une interface, la plupart des compilateurs généreront automatiquement des implémentations d'interface explicites lorsque le code source repose sur une implémentation d'interface implicite, si vous avez compilé votre classe en implémentant IList<T> contre une BCL qui n'a pas IList<T> implémentant IReadOnlyList<T> , le compilateur ne créera pas automatiquement les implémentations explicites IReadOnlyList<T> . Si je lis bien : http://stackoverflow.com/a/35940240/429091

@HaloFour Depuis que List<> et HashSet<> implémentent ICollection<> et IReadOnlyCollection<> , nous avons déjà adopté un chemin où IReadOnly fait référence à l'accès et non aptitude. Sur cette base, avoir IAnything étendre IReadOnlyAnything est parfaitement logique. Je suis d'accord que IReadable est meilleur que IReadOnly mais à ce stade, tout le monde comprend IReadOnly pour _signifier_ IReadable et l'utilise comme tel. En fait, je perpétue cela intentionnellement dans ma propre base de code parce qu'avoir deux façons de penser aux choses est une charge cognitive plus que tout à mon avis.

Nous sommes coincés avec le nom, mais le concept sous-jacent est suffisamment puissant pour que je souhaite vraiment qu'il soit possible pour toutes les interfaces d'étendre IReadOnly à l'avenir, tout comme nous le faisons avec les classes concrètes.

@ashmind Je pense qu'il est parfait qu'aucune des méthodes ne prenne des comparateurs. Dans les ensembles et les dictionnaires, les comparateurs ne sont pas facilement interchangeables car ils déterminent la structure de l'objet entier. De plus, cela n'aurait aucun sens de passer un comparateur à un CaseInsensitiveStringCollection ou à toute collection qui implique une certaine comparaison.

(Dans le cas d'une collection étrange qui implémente Contains(T, IEqualityComparer<T>) plus efficacement que la méthode d'extension qui est déjà disponible, ce serait probablement une méthode de classe unique. Il est difficile d'imaginer que Contains(T, IEqualityComparer<T>) soit courant assez pour se retrouver dans une interface spécialisée, mais rien n'empêche même cela de se produire.)

@jnm2

Je pense qu'il est parfait qu'aucune des méthodes ne prenne des comparateurs.

Juste pour clarifier, je voulais dire qu'il fallait exposer un comparateur, pas en prendre un. Étant donné que chaque ensemble ou dictionnaire doit avoir un algorithme d'égalité, cela aurait pu être exposé sur l'interface. Mais je ne me souviens plus de mon cas d'utilisation pour cela maintenant - quelque chose comme créer un ensemble en utilisant le même comparateur que dans un comparateur fourni en externe.

Bien que cette discussion soulève de nombreux points intéressants, elle semble s'éloigner de la suggestion simple et évidente qui a lancé ce fil. Et c'est décourageant parce que j'aimerais vraiment que cette question soit abordée.

Comme l'a dit l'OP, l'échec à maintenir la parité entre les types de collection lorsque IReadOnlyList a été ajouté sans IReadOnlySet est regrettable et de nombreuses personnes ont implémenté leurs propres versions de l'interface IReadOnlySet comme solutions de contournement (ma propre équipe a une solution de contournement similaire). Ces interfaces de contournement ne sont pas idéales car les classes corefx ne peuvent pas les implémenter. C'est la principale raison pour laquelle cela est fourni dans le cadre : si j'ai un HashSet, j'aimerais pouvoir l'utiliser comme IReadOnlySet sans copier ni encapsuler l'objet que j'ai déjà. Pour la performance au moins, c'est souvent souhaitable.

Le nom de l'interface doit clairement être IReadOnlySet. La cohérence l'emporte sur tout problème avec les noms IReadOnlyXXX. Ce navire a navigué.

Aucune des interfaces existantes (IReadOnlyCollection) ne peut être modifiée. Les exigences de rétrocompatibilité pour .NET n'autorisent pas de telles modifications. Il est regrettable que les comparateurs ne soient pas exposés dans les interfaces IReadOnlyXXX existantes (je l'ai également rencontré), mais encore une fois, le navire a navigué.

La seule question qui semble subsister d'un point de vue pratique se situe entre ces deux définitions potentielles de l'interface.

Précédemment proposé par @ashmind :

public interface IReadOnlySet<T> : IReadOnlyCollection<T> {    
    bool Contains(T item);
    bool IsSubsetOf(IEnumerable<T> other);
    bool IsSupersetOf(IEnumerable<T> other);
    bool IsProperSupersetOf(IEnumerable<T> other);
    bool IsProperSubsetOf(IEnumerable<T> other);
    bool Overlaps(IEnumerable<T> other);
    bool SetEquals(IEnumerable<T> other);
}

Proposition minimale :

public interface IReadOnlySet<T> : IReadOnlyCollection<T> {    
    bool Contains(T item);
}

Personnellement, je préfère cette proposition minimale puisque les autres méthodes peuvent être dérivées ; idéalement, il y aurait une implémentation standard de celles-ci en tant que méthodes d'extension sur l'interface IReadOnlySet afin que les implémenteurs de IReadOnlySet n'aient pas besoin de les fournir. Je pense également que cette proposition minimale est plus conforme aux autres interfaces IReadOnlyXXX minimales.

@aaron-meyers La seule préoccupation que j'aurais est que IsSubsetOf et les amis ne peuvent pas être dérivés de Contains d'une manière _efficace_. Lorsqu'il s'agit de deux tables de hachage, par exemple, s'appuyer sur Contains force l'implémentation à utiliser des boucles imbriquées plutôt que la correspondance de hachage.

Peut-être qu'une nouvelle interface, IComparableSet<T> pourrait contenir les opérations définies.

Nous avons déjà des méthodes d'extension sur IEnumerable<T> pour quelques opérations définies.

@jnm2 L' implémentation de ces méthodes utilisées par HashSet ne nécessite que Contains et l'énumération de l'autre collection (que IReadOnlySet obtiendrait en héritant de IReadOnlyCollection). Cela nécessite cependant de savoir que l'autre ensemble utilise le même comparateur. Il vaut peut-être la peine d'ajouter la propriété Comparer à IReadOnlySet afin que ces opérations puissent être implémentées efficacement dans les méthodes d'extension. Il est regrettable que IReadOnlyDictionary n'expose pas également le KeyComparer, mais cela peut valoir la peine de l'ajouter à IReadOnlySet même s'il n'est pas entièrement cohérent. Il y a de bonnes raisons pour lesquelles il aurait dû être inclus dans IReadOnlyDictionary en premier lieu, comme expliqué ici.

La proposition modifiée serait :

public interface IReadOnlySet<T> : IReadOnlyCollection<T> {    
    IEqualityComparer<T> Comparer { get; }
    bool Contains(T item);
}

Alternativement, le comparateur pourrait être sur une interface distincte et les implémentations de méthode d'extension des opérations d'ensemble n'utiliseraient la route efficace que si les deux objets implémentent l'interface et ont les mêmes comparateurs. La même approche pourrait être appliquée pour IReadOnlyDictionary (en fait, ils utilisent peut-être simplement la même interface). Quelque chose comme ISetComparable. Ou en tirant de @drewnoakes, il pourrait y avoir un IComparableSetmais au lieu de définir les opérateurs d'ensemble, il définit simplement le comparateur :

public interface IComparableSet<T> : IReadOnlySet<T> {    
    IEqualityComparer<T> Comparer { get; }
}

Dans ce cas, IReadOnlySet revient simplement à définir Contains :

public interface IReadOnlySet<T> : IReadOnlyCollection<T> {    
    bool Contains(T item);
}

public interface IReadOnlySet<T> : IReadOnlyCollection<T> {    
    bool Contains(T item);
}
public interface IReadOnlySetEx<T> : IReadOnlySet<T> {    
    bool IsSubsetOf(IEnumerable<T> other);
    bool IsSupersetOf(IEnumerable<T> other);
    bool IsProperSupersetOf(IEnumerable<T> other);
    bool IsProperSubsetOf(IEnumerable<T> other);
    bool Overlaps(IEnumerable<T> other);
    bool SetEquals(IEnumerable<T> other);
    IEqualityComparer<T> Comparer { get; }
}
public class HashSet<T>: IReadOnlySetEx<T>, ISet<T>
{
   // Improved implementation here.
}

public static class CollectionExtensiosn
{
    public static IEqualityComparer<T> GetComparer<T>(this IReadOnlySet<T> @set)
    {
           var setEx = <strong i="5">@set</strong> as IReadOnlySetEx<T>;
           if (setEx == null)
           {
                throw new ArgumentException("set should implement IReadOnlySetEx<T> for this method.")
           }
           return setEx.Comparer;
    }

    public static bool IsSubsetOf<T>(this IReadOnlySet<T> <strong i="6">@set</strong>, IEnumerable<T> other)
    {
         var setEx = <strong i="7">@set</strong> as IReadOnlySetEx<T>;
         if (setEx != null)
         {
              return setEx.IsSubsetOf(other);
         }
         // Non optimal implementation here.
    }

    // The same approach for dictionary.
    public static IEqualityComparer<T> GetKeyComparer<T>(this IReadOnlyDictionary<T> dictionary)
    {
           var dictionaryEx = set as IReadOnlyDictionaryEx<T>;
           if (dictionaryEx == null)
           {
                throw new ArgumentException("dictionary should implement IReadDictionaryEx<T> for this method.")
           }
           return dictionaryEx.KeyComparer;
    }

}

Nous pouvons utiliser cette approche.
L'utilisation ressemblera à ceci;

IReadOnlySet<string> mySet = new HashSet<string>();
bool test = mySet.IsSubsetOf(new []{"some", "strings", "set"}); // Extension method
var = mySet.GetComparer(); // Extension method

De nombreuses exigences satisfaites, IReadOnlySetest minimaliste. Mais GetComparer now méthode, pas propriété. Mais c'est un bon compromis.

    /// <summary>
    /// Readable set abstracton. Allows fast contains method, also shows that collection items are unique by some criteria.
    /// </summary>
    /// <remarks>
    /// Proposal for this abstraction is discussed here https://github.com/dotnet/corefx/issues/1973.
    /// </remarks>
    /// <typeparam name="T">The type of elements in the set.</typeparam>
    public interface IReadOnlySet<out T> : IReadOnlyCollection<T>
    {
        /// <summary>
        /// Determines whether a <see cref="T:System.Collections.Generic.HashSet`1"/> object contains the specified
        /// element.
        /// </summary>
        /// <typeparam name="TItem">The type of the provided item. This trick allows to save contravariance and save from boxing.</typeparam>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.HashSet`1"/> object contains the specified element;
        /// otherwise, false.
        /// </returns>
        /// <param name="item">The element to locate in the <see cref="T:System.Collections.Generic.HashSet`1"/> object.</param>
        bool Contains<TItem>(TItem item);
    }

namespace System.Collections.Generic
{
    /// <summary>
    /// Provides the base interface for the abstraction of sets. <br/>
    /// This is full-featured readonly interface but without contravariance. See contravariant version
    /// <see cref="IReadOnlySet{T}"/>.
    /// </summary>
    /// <typeparam name="T">The type of elements in the set.</typeparam>
    public interface IReadableSet<T> : IReadOnlySet<T>
    {
        /// <summary>
        /// Gets the <see cref="Generic.IEqualityComparer{T}"/> object that is used to determine equality for the values
        /// in the set.
        /// </summary>
        /// <returns>
        /// The <see cref="Generic.IEqualityComparer{T}"/> object that is used to determine equality for the values in the
        /// set.
        /// </returns>
        IEqualityComparer<T> Comparer { get; }

        /// <summary>
        /// Determines whether a <see cref="T:System.Collections.Generic.HashSet`1"/> object contains the specified
        /// element.
        /// </summary>
        /// <returns>
        /// true if the <see cref="T:System.Collections.Generic.HashSet`1"/> object contains the specified element;
        /// otherwise, false.
        /// </returns>
        /// <param name="item">The element to locate in the <see cref="T:System.Collections.Generic.HashSet`1"/> object.</param>
        bool Contains(T item);

        /// <summary>
        /// Determines whether the current set is a proper (strict) subset of a specified collection.
        /// </summary>
        /// <returns><see langword="true"/> if the current set is a proper subset of <paramref name="other"/>; otherwise, false.</returns>
        /// <param name="other">The collection to compare to the current set.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="other"/> is null.
        /// </exception>
        bool IsProperSubsetOf(IEnumerable<T> other);

        /// <summary>Determines whether the current set is a proper (strict) superset of a specified collection.</summary>
        /// <returns>true if the current set is a proper superset of <paramref name="other"/>; otherwise, false.</returns>
        /// <param name="other">The collection to compare to the current set. </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="other"/> is null.
        /// </exception>
        bool IsProperSupersetOf(IEnumerable<T> other);

        /// <summary>Determines whether a set is a subset of a specified collection.</summary>
        /// <returns>true if the current set is a subset of <paramref name="other"/>; otherwise, false.</returns>
        /// <param name="other">The collection to compare to the current set.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="other"/> is null.
        /// </exception>
        bool IsSubsetOf(IEnumerable<T> other);

        /// <summary>Determines whether the current set is a superset of a specified collection.</summary>
        /// <returns>true if the current set is a superset of <paramref name="other"/>; otherwise, false.</returns>
        /// <param name="other">The collection to compare to the current set.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="other"/> is null.
        /// </exception>
        bool IsSupersetOf(IEnumerable<T> other);

        /// <summary>Determines whether the current set overlaps with the specified collection.</summary>
        /// <returns>true if the current set and <paramref name="other"/> share at least one common element; otherwise, false.</returns>
        /// <param name="other">The collection to compare to the current set.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="other"/> is null.
        /// </exception>
        bool Overlaps(IEnumerable<T> other);

        /// <summary>Determines whether the current set and the specified collection contain the same elements.</summary>
        /// <returns>true if the current set is equal to <paramref name="other"/>; otherwise, false.</returns>
        /// <param name="other">The collection to compare to the current set.</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="other"/> is null.
        /// </exception>
        bool SetEquals(IEnumerable<T> other);
    }
}

Je viens de publier ces contrats avec des assistants d'injection pour le nuget (https://www.nuget.org/packages/ClrCoder.Collections.ReadOnlySet).
N'hésitez pas à l'utiliser et à soumettre des problèmes ici : https://github.com/dmitriyse/ClrCoder/issues
Si cela devient un peu populaire (probablement après quelques cycles de raffinement), nous pouvons suggérer cette amélioration à l'équipe CoreFX.

@terrajobst est-il acceptable que la compatibilité des classes existantes implémente de nouvelles interfaces ?

@safern, il y a un précédent dans List<T> avec l'ajout de IReadOnly .

Est-il donc prévu d'ajouter dans les prochaines versions du framework .NET ?

Quand cette œuvre va-t-elle atterrir ? Des délais ?

https://www.nuget.org/packages/System.Collections.Immutable/
Ensemble complet de collections immuables)

D'ACCORD. C'est là depuis bien trop longtemps. J'en ai beaucoup besoin et j'aimerais proposer une façon d'aborder cela alors.

Au lieu d'exposer tout cela :

IEqualityComparer<T> Comparer { get; }
bool IsProperSubsetOf(IEnumerable<T> other);
bool IsProperSupersetOf(IEnumerable<T> other);
bool IsSubsetOf(IEnumerable<T> other);
bool IsSupersetOf(IEnumerable<T> other);
bool Overlaps(IEnumerable<T> other);
bool SetEquals(IEnumerable<T> other);

et obligeant les clients à implémenter ces membres, ajoutons simplement ce qui est le plus crucial à avoir :

public interface IReadOnlySet<out T> : IReadOnlyCollection<T>
{
    bool Contains<T>(T item);
}

Et rien de plus. Plus d'API peuvent toujours être ajoutées à l'avenir, s'il y a un tel besoin. Mais il ne peut pas être supprimé, d'où cette proposition.

Nous ajouterions ensuite cette interface à la liste des interfaces étendues de ISet<T> .

De la documentation

ISet<T> : Cette interface fournit des méthodes pour implémenter des ensembles, qui sont des collections qui ont des éléments uniques et des opérations spécifiques. Le HashSetet SortedSetles collections implémentent cette interface.

Du code

L'interface ISet<T> a déjà notre méthode bool Contains<T>(T item); définie via ICollection<T> . Il a également int Count { get; } via ICollection<T> .

Ainsi serait :

public interface ISet<T> : ICollection<T>, IEnumerable<T>, IEnumerable, IReadOnlySet<T>

Changement insignifiant, peu à discuter, énorme avantage.

S'il vous plaît, faisons en sorte que cela se produise. Faites-moi savoir si une telle demande de tirage serait acceptée et fusionnée. Je peux le créer alors.

@karelz @terrajobst @safern @ianhays

J'ai trouvé cette discussion en travaillant sur un problème du monde réel où je voulais utiliser la collection de clés d'un dictionnaire pour les opérations en lecture seule. Afin de soutenir ce cas, voici l'API que je propose.

Raisonnement

API proposée

 namespace System.Collections.Generic {
+    public interface IReadOnlySet<out T> : IReadOnlyCollection<T>, IEnumerable, IEnumerable<T> {
+        bool Contains(T value);
+        bool IsProperSubsetOf(IEnumerable<T> other);
+        bool IsProperSupersetOf(IEnumerable<T> other);
+        bool IsSubsetOf(IEnumerable<T> other);
+        bool IsSupersetOf(IEnumerable<T> other);
+        bool Overlaps(IEnumerable<T> other);
+        bool SetEquals(IEnumerable<T> other);
+    }
-    public class HashSet<T> : ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T> {
+    public class HashSet<T> : ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T>, IReadOnlySet<T> {
     }
-    public class SortedSet<T> : ICollection, ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T> {
+    public class SortedSet<T> : ICollection, ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T>, IReadOnlySet<T> {
     }
+    public class ReadOnlySet<T> : ICollection<T>, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlySet<T>, ISet<T> {
+        public int Count { get; }
+        public ReadOnlySet(ISet<T> set);
+        public bool Contains(T value);
+        public bool IsProperSubsetOf(IEnumerable<T> other);
+        public bool IsProperSupersetOf(IEnumerable<T> other);
+        public bool IsSubsetOf(IEnumerable<T> other);
+        public bool IsSupersetOf(IEnumerable<T> other);
+        public bool Overlaps(IEnumerable<T> other);
+        public bool SetEquals(IEnumerable<T> other);
+    }
     public class Dictionary<TKey, TValue> {
-        public sealed class KeyCollection : ICollection, ICollection<TKey>, IEnumerable, IEnumerable<TKey>, IReadOnlyCollection<TKey> {
+        public sealed class KeyCollection : ICollection, ICollection<TKey>, IEnumerable, IEnumerable<TKey>, IReadOnlyCollection<TKey>, IReadOnlySet<TKey> {
+            public bool IsProperSubsetOf(IEnumerable<TKey> other);
+            public bool IsProperSupersetOf(IEnumerable<TKey> other);
+            public bool IsSubsetOf(IEnumerable<TKey> other);
+            public bool IsSupersetOf(IEnumerable<TKey> other);
+            public bool Overlaps(IEnumerable<TKey> other);
+            public bool SetEquals(IEnumerable<TKey> other);
         }
     }
 }

Questions ouvertes

  • L'ajout de ces méthodes à Dictionary<TKey, TValue>.KeyCollection acceptable compte tenu du coût de la taille du code pour un type générique couramment instancié ? Voir ici .
  • IReadOnlySet<T> devrait-il être implémenté dans d'autres Dictionary KeyCollection s tels que SortedDictionary ou ImmutableDictionary ?

Mises à jour

  • Suppression de TryGetValue car ce n'est pas dans ISet<T> et en tant que tel empêcherait potentiellement de rebaser ISet<T> pour implémenter IReadOnlySet<T> .
  • Ajout ReadOnlySet<T> classe ReadOnlyCollection<T> .

@TylerBrinkley merci d'avoir ajouté une proposition d'API dans le fil. Cela vous ennuierait-il d'ajouter une justification et des cas d'utilisation ? Pour que je puisse le marquer comme prêt pour examen et laisser les experts en API décider ?

@TylerBrinkley n'oubliez pas d'inclure la propriété EqualityComparer dans le IReadOnlySetinterface. Ils manquent actuellement dans les dictionnaires et les ensembles de CoreFX, mais c'est un problème.

@dmitriyse à quoi servirait une propriété getter only IEqualityComparer<T> ? Que ferais - tu avec ça?

Les dictionnaires et les ensembles doivent signaler leurs EqualityComparers pour permettre un clonage de collection correct.
Malheureusement, j'ai oublié où cette question a été discutée.

Si vous faites du clonage, ne travailleriez-vous pas avec un type concret ? Pourquoi l'interface devrait-elle prendre en charge le IEqualityComparer<T> ?

Par exemple, si vous avez défini avec les chaînes et le comparateur d'égalité d'insensibilité à la casse, vous recevrez une exception lors de la création d'un nouveau HashSet sans spécifier le bon EqualityComparer. Il y a des cas où vous ne pouvez pas savoir quel EqualityComparer est utilisé dans l'ensemble spécifié.

Ce n'est pas que du clonage. Je pense que le scénario beaucoup plus courant consiste à comparer deux ensembles - j'ai besoin de savoir qu'ils utilisent tous les deux le même comparateur afin d'implémenter une comparaison optimale à l'aide de Contains. Je pense qu'il y a un exemple dans ce fil.

Cela dit, je préfère avoir IReadOnlySet avec uniquement la méthode Contains que pas du tout. Ce serait bien de pouvoir implémenter la comparaison de Set de manière générique mais pas aussi courante que d'avoir simplement besoin d'une référence en lecture seule à un Set.

Obtenez Outlook pour iOS https://aka.ms/o0ukef


De : Tyler Brinkley [email protected]
Envoyé : jeudi 10 mai 2018 06:21:52 AM
À : dotnet/corefx
Cc : Aaron Meyers ; Mention
Objet : Re : [dotnet/corefx] Veuillez ajouter l'interface IReadOnlySet et faire en sorte que HashSet, SortedSet l'implémente (#1973)

Si vous faites du clonage, ne travailleriez-vous pas avec un type concret ? Pourquoi l'interface devrait-elle prendre en charge IEqualityComparer.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub https://eur01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fdotnet%2Fcorefx%2Fissues%2F1973%23issuecomment-388051258&data=02 % 7C01% 7C% 7Cc45ea16cd3034ddd69d808d5b678ff33% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% 7C636615553141417289 & sdata = xRI27JtyaAwnZ2anY05oTlxmPY5AaGVl% 2BRdXK2uR0% 2F8% 3D et réservé = 0 , ou couper le fil https://eur01.safelinks.protection.outlook.com/?url=https%3A% 2F% 2Fgithub.com% 2Fnotifications% 2Funsubscribe-auth% 2FAMuQLmqboBWyHweWHSUoE1YM2OrfHZZxks5txD7wgaJpZM4E9KK- & data = 02% 7C01% 7C% 7Cc45ea16cd3034ddd69d808d5b678ff33% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% 7C636615553141417289 & sdata = hLtAXEyFNVEgWike6tMwAfUVC% 2BucyjXUDwoLOLDV5gk% 3D et réservé = 0 .

Je suis d'accord, la seule façon de savoir quels types de doublons vous pourriez trouver dans l'ensemble (sensible à la casse, insensible à la casse, etc.) est d'exposer le comparateur.

Je commence à penser que ma proposition ne serait pas acceptée car l'ajout de toutes ces méthodes au Dictionary<TKey, TValue>.KeyCollection aurait un coût de taille de code important. Voir cette discussion concernant l'ajout d'une nouvelle API aux types génériques couramment instanciés.

Je ne pense pas que vous puissiez mettre un comparateur sur IReadOnlySet .

SortedSet prend un IComparer , tandis que HashSet prend un IEqualityComparer .

Bon point, le comparateur pourrait être considéré comme un détail d'implémentation qui n'appartient pas à une interface générale.

Obtenez Outlook pour iOS https://aka.ms/o0ukef


De : Cory Nelson [email protected]
Envoyé : jeudi 10 mai 2018 17:04:06
À : dotnet/corefx
Cc : Aaron Meyers ; Mention
Objet : Re : [dotnet/corefx] Veuillez ajouter l'interface IReadOnlySet et faire en sorte que HashSet, SortedSet l'implémente (#1973)

Je ne pense pas que vous allez pouvoir mettre un comparateur sur IReadOnlySet.

SortedSet prend un IComparer, tandis que HashSet prend un IEqualityComparer.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub https://eur02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fdotnet%2Fcorefx%2Fissues%2F1973%23issuecomment-388221165&data=02 % 7C01% 7C% 7C0ef6d84125be4c450fdc08d5b6d2b70a% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% 7C636615938478979295 & sdata = PHkDQPiJBEIks8yNyIA7vKODM% 2BkMFI4PRX4lUUBu% 2Bi0% 3D et réservé = 0 , ou couper le fil https://eur02.safelinks.protection.outlook.com/?url=https%3A% 2F% 2Fgithub.com% 2Fnotifications% 2Funsubscribe-auth% 2FAMuQLu5JGLcqrpMyGWLygbCsaSQSXFgNks5txNV2gaJpZM4E9KK- & data = 02% 7C01% 7C% 7C0ef6d84125be4c450fdc08d5b6d2b70a% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% 7C636615938478979295 & sdata = 9pnuMULuDu9HWb7un% 2FWYq6iYdjTKFsjN7nKiToaeHkk% 3D et réservé = 0 .

Il peut être intéressant d'ajouter des interfaces IUnorderedSet et IOrderedSet , mais je ne voudrais pas que cela fasse dérailler en poussant IReadOnlySet .

J'aime la suggestion de @pgolebiowski mais rendrait cette interface encore plus basique.

c# public interface ISetCharacteristic<in T> { bool Contains(T item); }

Cela convient alors également aux ensembles innombrables tels que les intervalles (réels). Entre cela et IReadOnlySet vous pourriez potentiellement intégrer quelques autres interfaces telles que ICountableSet (aka IEnumerableSet ), IUnorderedSet et IOrderedSet .

Mais convenez que seulement IReadOnlySet serait une grande amélioration par rapport à ce qui est actuellement disponible.

+1 pour @NickRedwood

Il est ouvert depuis plus de 3 ans maintenant. Veuillez faire en sorte que cela se produise en changeant l'approche. L'introduction du changement mentionné par @NickRedwood permet toutes les autres améliorations dont vous discutez dans ce fil. Toutes les approches s'accordent sur cette seule chose triviale. Ajoutons donc cette seule chose triviale qui est fondamentale, puis créons un autre problème pour les améliorations potentielles qui sont toutes facultatives.

@TylerBrinkley @safern @karelz @terrajobst

Je ne vois pas vraiment d'avantage à une interface IReadOnlySet<T> avec seulement une méthode Contains . Bien que cette méthode soit utile, elle est déjà incluse dans l'interface ICollection<T> qui possède également une propriété IsReadOnly destinée à indiquer si les méthodes de modification de ICollection<T> sont prises en charge. Implémentez simplement ICollection<T> et faites en sorte que IsReadOnly retourne true . La seule autre fonctionnalité qu'il devrait prendre en charge serait une propriété Count , énumérable, et une méthode CopyTo qui ne semble pas trop limitative.

Je serais très heureux que le IReadOnlySet<T> proposé soit implémenté sur la base de IReadOnlyCollection<T> - ce serait une grande amélioration par rapport à ce qui est actuellement disponible. ( @TylerBrinkley - supposons que vous vouliez dire IReadOnlyCollection<T> plutôt que ICollection<T> .

Je suppose que je me trompe du côté des interfaces plus basiques, et s'il y en avait une pour Set, ce serait une interface à méthode unique avec une méthode Contains . Il a une bonne base mathématique et est très propre. Cependant, lors de la mise à niveau vers des classes existantes, j'accepte que la mise à niveau moins est meilleure, et s'il ne devait y avoir qu'une seule interface mise à niveau, alors IReadOnlySet<T> basé sur IReadOnlyCollection<T> devrait l'être.

IReadOnlyCollection<> n'a pas .Contains car cela l'empêcherait d'être covariant.

Bon point et j'ai corrigé mon message précédent pour être contravariant. IReadOnlySet<T> devrait être invariant à moins que l'approche publiée au début du fil ne soit utilisée, et je ne suis pas sûr de celle-là.

@TylerBrinkley personnellement, je ne suis pas un fan de la propriété IsReadOnly , je préférerais de loin que ces informations soient connues au moment de la compilation plutôt qu'à l'exécution. Si vous faisiez référence à l'interface IReadOnlyCollection<T> , je pense toujours qu'il serait utile pour l'appelant ou l'appelé de savoir que la recherche sera rapide au lieu d'une itération potentielle à travers une collection, bien que je ne sois pas certain si un "ensemble" implique cela.

Avoir juste une méthode Contains ne définit pas ce qu'un Set devrait faire, juste ce qu'un Collection devrait faire. Je veux dire que Contains n'est même pas membre de ISet mais de ICollection dont ISet hérite. Les autres membres de ISet devraient être tenus de définir ce qu'un Set devrait faire. Je comprends que la plupart des gens utilisent probablement des ensembles exclusivement pour sa méthode Contains , mais les ensembles sont bien plus que cela.

@TylerBrinkley Mais est-il même possible de définir IReadOnlyCollection<T>.Contains(T) à ce stade ? J'ai supposé que non. C'est pourquoi la seule option est d'introduire IReadOnlySet<T> et, une fois introduit, de s'assurer que IReadOnlySet<T>.Contains(T) est déclaré.

@jnm2 dit :

IReadOnlyCollection<> n'a pas .Contains car cela l'empêcherait d'être covariant.

Cela me semble être le plus gros problème en ce moment. Il serait étrange que IReadOnlySet<T> soit invariant alors que IReadOnlyCollection<T> est covariant. Il semble que de nombreuses interfaces IReadOnly* existantes aient été soigneusement conçues pour être des out T les interfaces inscriptibles étant nécessairement invariantes. La plupart d'entre nous voudraient que IReadOnlySet<T> déclare au moins une méthode Contains(T) et pourtant cela l'empêcherait d'être covariante.

Si ICollection est vraiment le bon endroit pour définir Contains(T) , est-ce possible ? Peut-être IReadOnlyProbableCollection<T>.Contains(T) qui serait invariant et implémenté par Collection<T> et HashSet<T> ?

Je pense que la suggestion de @NickRedwood de ISetCharacteristic<T> semble peut-être plus propre. Cela a même l'avantage de vous permettre de ne pas implémenter Count ce qui pourrait être utile.

personnellement, je ne suis pas un fan de la propriété IsReadOnly, je préférerais de loin que ces informations soient connues au moment de la compilation plutôt qu'à l'exécution.

Cet homme parle bien, versez-lui plus de vin.

@binki Je suis d'accord qu'il doit y avoir une méthode Contains sur IReadOnlySet<T> puisque IReadOnlyCollection<T> n'en incluait pas, mais je pense aussi que tous les autres ISet<T> non mutants ISet<T> doivent être incluses.

Outre le cas d'utilisation Dictionary.KeyCollection j'ai mentionné ci-dessus, quels autres cas d'utilisation pouvez-vous proposer pour ajouter cette interface ?

OK, on ​​dirait que la conception de l'API est :

public interface IReadOnlySet<T> : IReadOnlyCollection<T> {    
    bool Contains(T item);
}

C'est la seule partie commune du design sur laquelle tout le monde semble pouvoir s'accorder.

JE DIS QUE MAINTENANT NOUS TERMINONS LA CONCEPTION ICI. Si vous souhaitez l'étendre à l'avenir, faites-le dans un autre numéro. C'est la fonctionnalité la plus importante qui devrait être dans prod.

Qui est pour, qui est contre ?

J'aime la suggestion de @ashmind . Ce serait génial si Dictionary.Keys pouvait implémenter cette interface car elle est techniquement IReadOnlySet<TKey> .

Pourriez-vous s'il vous plaît marquer ceci comme api-ready-for-review ? Je viens de me retrouver à nouveau besoin de cette interface et elle n'est toujours pas là.

Pourquoi cela n'est-il pas encore mis en œuvre ?

[EDIT] Excuses - ma réponse initiale était confondue avec une autre API. Je n'ai pas d'opinion tranchée sur celui-ci, texte modifié. Désolé pour la confusion!

Pourquoi cela n'est-il pas encore mis en œuvre ?

L'API n'a pas encore attiré l'attention des propriétaires de zone - @safern. Je ne sais pas à quel point il figure sur la liste des priorités des collections. Le nombre de votes positifs est assez élevé.
La vérité est que nous verrouillons maintenant pour .NET Core 3.0, il faudra donc attendre au moins la prochaine version car elle n'est pas critique pour 3.0.

Je suis également surpris que ce ne soit pas fourni hors de la boîte.

Les structures de données immuables peuvent être d'une grande aide pour améliorer la maintenabilité du code, mais elles sont difficiles à utiliser sans une interface dans les bibliothèques standard qui spécifie la sémantique et permet à différentes implémentations de fonctionner ensemble.

Sans cela, chaque bibliothèque voulant utiliser un ensemble immuable comme paramètre/valeur de retour doit recourir soit à l'utilisation de IEnumerable qui est moins efficace et sémantiquement incorrecte, soit à l'utilisation de leurs propres interfaces/classes dans leurs signatures, ce qui est incompatible avec celles des autres.

Je suis curieux de savoir s'il existe des solutions de contournement pour cela qui sont efficaces en termes de recherches Contains et évitent de spécifier le type concret du paramètre/valeur de retour. Le mieux auquel je puisse penser serait d'utiliser IEnumerable dans la signature et de passer ReadOnlyDictionary.Keys, mais cela semble un peu désagréable, surtout dans une bibliothèque. Étant donné que Enumerable.Contains dans Linq utilisera l'implémentation de collection de Contains , cela devrait être efficace tout en étant compatible, mais ne communique pas l'intention qui peut conduire à utiliser des implémentations moins performantes.

@adamt06 Vous cherchez ISet<T> .

@scalablecory Vous avez raison, avec une implémentation immuable, et il y en a une : ImmutableHashSet<T>

Est-ce que quelqu'un sait/comprend pourquoi ICollectionn'étend pas IReadOnlyCollection??
Je pensais que c'était un peu lié à ce fil. Au lieu que j'ouvre un nouveau fil. Peut-être que j'ai juste mal compris quelque chose.

Une autre réflexion, mais complètement hors sujet, Et pourquoi ReaOnlyCollection n'a-t-il pas :

c# bool Contains(T item); void CopyTo(T[] array, int arrayIndex);

Oh, je vois que vous vouliez dire que IReadOnlyCollection ne les a pas. C'est parce que sinon l'interface ne pourrait pas être covariante .

Je ne suis pas sûr, mais c'est probablement quelque chose à voir avec l'implémentation d'interface explicite causant des problèmes de compatibilité descendante. Je peux voir pourquoi la mise à jour des interfaces existantes pour hériter d'autres interfaces est un problème, mais je ne vois pas pourquoi créer une nouvelle interface IReadOnlySet<T> et faire en sorte que HashSet implémente serait un problème.

D'ACCORD. Mais c'est toujours aussi ne pas voir ICollectionne devrait pas être:
c# ICollection<T> : IReadOnlyCollection<out T>
Pourquoi une collection en lecture/écriture ne devrait-elle pas également être une extension d'une collection en lecture seule ?

@generik0

D'ACCORD. Mais est toujours aussi ne vois pas ICollection ne devrait pas être:

ICollection<out T> : IReadOnlyCollection<out T>

Pourquoi une collection en lecture/écriture ne devrait-elle pas également être une extension d'une collection en lecture seule ?

Tout d'abord, veuillez noter qu'il est impossible de déclarer ICollection<out T> car ICollection<T> a un membre .Add(T item) .

Deuxièmement, notez que ICollection<T> apparaît dans .net-2.0 et IReadOnlyCollection<out T> apparaît dans .net-4.5 . Si vous compilez du code en implémentant ICollection<T> et que ICollection<T> est modifié pour implémenter une nouvelle interface, tous les binaires compilés existants se briseront à l'exécution car tout ce qui implémente uniquement ICollection<T> n'aura pas son IReadOnlyCollection<T> slots remplis (éventuellement automatiquement) par le compilateur. C'est le problème de compatibilité qui a empêché ICollection<T> d'implémenter IReadOnlyCollection<T> .

Je ne pense pas que cela soit clairement abordé par cette réponse SO, mais il y a un SO pour cela: https://stackoverflow.com/a/14944400 .

@binki " " fixé à ICCollection, merci d'avoir signalé ma faute de frappe..
DotNetStandard i net461. Et c'est là que le changement devrait être. netstandard 2+

je vais répéter...
Pourquoi une collection en lecture/écriture ne devrait-elle pas également être une extension d'une collection en lecture seule ?

Et pourquoi devrait-on avoir besoin de convertir une collection en "ToArray()" juste pour exposer moins si c'est le cas ?

Et bien sûr, vous pouvez ajouter de nouvelles interfaces dans des versions supérieures sans casser les choses. ICollection a déjà implémenté les méthodes IReadOnlyCollection. La notation devrait casser,... :-/

@generik0 On dirait que cela ne briserait pas la compatibilité des sources, ce à quoi vous pensez [ne

Ok @jnm2 merci.
Je vais arrêter mon discours hors sujet maintenant, je pense juste que c'est une mauvaise architecture. Merci à tous d'avoir écouté / expliqué :-)

@jnm2

@generik0 On dirait que cela ne briserait pas la compatibilité des sources, ce à quoi vous pensez, mais cela briserait la compatibilité binaire qui fonctionne avec les tables IL, pas C#.

Pour pinailler (désolé), cela briserait également la compatibilité des sources si votre source implémentait explicitement ICollection<T> d'avant .net-4.5. La classe commencerait à échouer à compiler en raison d'un échec d'implémentation explicite de IReadOnlyCollection<T>.Count . Mais la compatibilité binaire est un problème plus important car elle vous empêcherait de cibler une large gamme de versions .net (vous devriez quand même avoir des binaires différents pour fonctionner sur ≥.net-4.5 que <.net-2 et vous doivent cibler les deux au lieu de cibler uniquement l'ancien framework).

Je me demande si, avec l'ajout de la prise en charge de l'implémentation de l'interface par défaut dans C#8, ICollection<T> pourrait implémenter IReadOnlyCollection<T> pour .NET Core 3.0+.

Peut-être qu'une extension est nécessaire, alors il s'agit de la même collection. c'est-à-dire si vous avez besoin d'obtenir une énumérable ou une icollection à une reaadonlycollection... Des idées ?

[édité par @jnm2 commentaire]
c# public static class CollectionExtensions { public static IReadOnlyCollection<T> CastAsReadOnlyCollection<T>(this IEnumerable<T> collection) { if((collection is IReadOnlyCollection<T> readOnlyCollection )) { return readOnlyCollection ; } throw new ArgumentException($"{collection.GetType()} not supported as readonly collection"); } }

@generik0 Pourquoi ne pas utiliser enumerable as IReadOnlyCollection<T> ?? throw ... ou (IReadOnlyCollection<T>)enumerable au lieu d'appeler cette méthode ?

@jnm2 omg. Vous avez raison. Parfois, vous ne voyez pas l'image claire et compliquez les choses !!!

J'appellerais toujours l'extension, juste pour avoir la simplicité ;-)
Merci mon pote....

Quel est le statut ici ? J'aimerais vraiment cette interface dans la bibliothèque standard et que HashSet<> l'implémente.

Il semble que notre meilleur pari soit d'attendre un peu plus longtemps pour que la proposition Shapes soit (espérons-le) mise en œuvre. Ensuite, vous pourrez créer n'importe quel groupe de formes pour représenter les types d'ensemble que vous souhaitez et fournir des implémentations afin que les ensembles existants tels que HashSet<T> conforment.

Une bibliothèque communautaire pour faire exactement cela pourrait alors émerger, couvrant tous les différents types d'ensembles (intensionnel (prédicats) et extensionnel (listé), ordonné, partiellement ordonné, non ordonné, dénombrable, dénombrable infini, infini indénombrable, éventuellement des domaines mathématiques aussi comme Rational , Nombres naturels, etc.) sous différentes formes, avec toutes les méthodes d'union, d'intersection, de cardinalité définies pour et entre ces ensembles.

Cela me semble être dans 5 ans. Pourquoi un simple changement qui peut être mis en œuvre en une journée devrait-il attendre une fonctionnalité non encore spécifiée 1000 fois plus grande qui pourrait même ne pas se produire ?

Cela me semble être dans 5 ans. Pourquoi un simple changement qui peut être mis en œuvre en une journée devrait-il attendre une fonctionnalité non encore spécifiée 1000 fois plus grande qui pourrait même ne pas se produire ?

Je réponds juste au manque de progrès sur IReadOnlySet<T> - ce problème est déjà ouvert depuis 4 ans après tout.

À la manière de Microsoft : les choses les plus simples et les plus utiles prennent des décennies. C'est époustouflant. 5 ans et plus.

Ce qui est encore plus drôle, c'est qu'ils l'ont ici
https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.sdk.sfc.ireadonlyset-1

pensées de @terrajobst ?

  • Nous pensons généralement qu'il s'agit bien d'un trou dans notre hiérarchie d'interface. Nous suggérons de se concentrer sur l'ajout de l'interface et son implémentation sur les implémentations d'ensemble existantes. Nous ne pouvons pas faire en sorte que ISet<T> étende IReadOnlySet<T> (pour la même raison que nous ne pouvions pas faire pour les autres interfaces mutables). Nous pouvons ajouter un ReadOnlySet<T> à un stade ultérieur. Nous devrions vérifier que IReadOnlySet<T> est juste ISet<T> moins les API mutables.
  • Nous devrions également implémenter IReadOnlySet<T> sur ImmutableSortedSet<T> et ImmutableHashSet<T> (et leurs constructeurs)
  • Nous devrions rechercher d'autres implémentations de ISet<T> .
 namespace System.Collections.Generic {
+    public interface IReadOnlySet<out T> : IReadOnlyCollection<T>, IEnumerable, IEnumerable<T> {
+        bool Contains(T value);
+        bool IsProperSubsetOf(IEnumerable<T> other);
+        bool IsProperSupersetOf(IEnumerable<T> other);
+        bool IsSubsetOf(IEnumerable<T> other);
+        bool IsSupersetOf(IEnumerable<T> other);
+        bool Overlaps(IEnumerable<T> other);
+        bool SetEquals(IEnumerable<T> other);
+    }
-    public class HashSet<T> : ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T> {
+    public class HashSet<T> : ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T>, IReadOnlySet<T> {
     }
-    public class SortedSet<T> : ICollection, ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T> {
+    public class SortedSet<T> : ICollection, ICollection<T>, IDeserializationCallback, IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, ISerializable, ISet<T>, IReadOnlySet<T> {
     }
 }

Fais le! JE TE DÉFIE!

@terrajobst

Nous ne pouvons pas faire en sorte que ISet<T> étende IReadOnlySet<T> (pour la même raison que nous ne pouvions pas faire pour les autres interfaces mutables).

Est-ce toujours vrai même avec les méthodes d'interface par défaut ? Cela signifie-t-il que https://github.com/dotnet/corefx/issues/41409 doit être fermé ?

@terrajobst

Nous ne pouvons pas faire en sorte que ISet<T> étende IReadOnlySet<T> (pour la même raison que nous ne pouvions pas faire pour les autres interfaces mutables).

Est-ce toujours vrai même avec les méthodes d'interface par défaut ? Cela signifie-t-il que dotnet/corefx#41409 doit être fermé ?

Nous en avons discuté. Nous avions l' habitude de penser que travailleraient qui tamise, mais quand nous avons la solution , nous avons conclu qu'il entraînerait généralement dans un diamant tesson qui se traduirait par une correspondance ambiguë. Cependant, cela a été récemment contesté, je pense donc que je dois l'écrire et m'assurer qu'il fonctionne ou ne fonctionne pas.

@terrajobst / @danmosemsft Quelqu'un a-t-il été affecté à cela ?

Et, @terrajobst pour clarifier le travail que nous voulons réaliser est :
```
Nous devrions également implémenter IReadOnlySetsur ImmutableSortedSetet ImmutableHashSet(et leurs constructeurs)
Nous devrions rechercher d'autres implémentations d'ISet.
````
En plus d'implémenter les interfaces ci-dessus sur HashSet, SortedSet.

L'analyse étant simplement rechercher n'importe quoi et l'évoquer si cela semble discutable.

Si c'est toujours en jeu, je serais intéressé

@Jlalond non , qui vous est attribué. Merci pour l'offre.

@danmosemsft @terrajobst
Juste une mise à jour. Je travaille là-dessus, j'ai ajouté l'interface à la bibliothèque principale privée, en trébuchant simplement en faisant en sorte que collections.generic et immuable reprennent cela.

Dernière question si vous savez par hasard Dan, dois-je apporter des modifications à Mono pour cela? Je ne sais pas où se termine le corefx et où commence le mono. Donc, si vous savez que cela pourrait me sauver de certaines recherches indépendantes

@Jlalond, vous ne devriez pas avoir besoin de modifier Mono. Une partie de la raison du déplacement de l'environnement d'exécution Mono dans ce référentiel est de faciliter l'utilisation des mêmes bibliothèques exactes avec CoreCLR ou l'environnement d'exécution Mono. Il n'y a qu'une petite partie de la bibliothèque principale qui diverge :
coreclrsrcSystem.Private.CoreLib vs mononetcoreSystem.Private.CoreLib. (La plupart de la bibliothèque principale est partagée avec les bibliothèquesSystem.Private.CoreLib). Donc, à moins que vous ne touchiez à cela, vous n'êtes pas affecté.

@danmosemsft Merci pour la clarification, j'espère que cela sera fait sous peu.

@danmosemsft, faites juste un suivi. CoreLib construit dans les assemblys src, je peux voir les modifications référencées. Cependant, les assemblages de référence ne semblent détecter aucun changement. C'est tout ce qui me retient, mais je ne trouve aucune information dans la doc. Toute personne ou pointeur que vous pouvez me donner pour que je puisse le faire (je veux dire, 5 ans plus tard)

Adressé par #32488

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

jzabroski picture jzabroski  ·  3Commentaires

matty-hall picture matty-hall  ·  3Commentaires

Timovzl picture Timovzl  ·  3Commentaires

chunseoklee picture chunseoklee  ·  3Commentaires

jamesqo picture jamesqo  ·  3Commentaires