Runtime: Adicione a interface IReadOnlySet e faça HashSet, SortedSet implementá-la

Criado em 9 jun. 2015  ·  104Comentários  ·  Fonte: dotnet/runtime

Original

Desde que IReadOnlyList foi adicionado, a paridade entre conjuntos e listas diminuiu. Seria ótimo restabelecê-lo.

Usá-lo seria uma forma agnóstica de implementação de dizer: "aqui está esta coleção somente leitura onde os itens são únicos".

Claramente, é necessário para muitas pessoas:

SQL Server: https://msdn.microsoft.com/en-us/library/gg503096.aspx
Roslyn: https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/InternalUtilities/IReadOnlySet.cs
Algum cara nos comentários: http://blogs.msdn.com/b/bclteam/archive/2013/03/06/update-to-immutable-collections.aspx

Encontrei essa discussão ao trabalhar em um problema do mundo real, em que queria usar a coleção de chaves de um dicionário para operações de conjunto somente leitura. Para apoiar esse caso, aqui está a API que proponho.

Editar

Justificativa

API proposta

 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);
         }
     }
 }

Perguntas abertas

  • Adicionar esses métodos a Dictionary<TKey, TValue>.KeyCollection aceitável, dado o custo do tamanho do código para um tipo genérico comumente instanciado? Veja aqui .
  • IReadOnlySet<T> ser implementado em outros Dictionary KeyCollection s, como SortedDictionary ou ImmutableDictionary ?

Atualizações

  • Removido TryGetValue porque não está em ISet<T> e, como tal, evitaria potencialmente rebasear ISet<T> para implementar IReadOnlySet<T> .
  • Adicionada classe ReadOnlySet<T> que é semelhante a ReadOnlyCollection<T> .
api-approved area-System.Collections up-for-grabs

Comentários muito úteis

Projeto de API proposto:

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);
}

Isso é baseado na API ISet<> (exceto métodos de mutação, obviamente).

É uma pena que Comparer não se encaixe nisso, mas nem ISet<> nem IReadOnlyDictionary<> comparadores de exposição, então é tarde demais para consertar agora.

Todos 104 comentários

Não seria bom se tivéssemos apenas alguma construção de linguagem para tornar as coisas imutáveis? Então, não precisaríamos ter essas interfaces mágicas.

@whoisj Qual idioma? O CLR tem dezenas deles.

Mesmo como um recurso de linguagem potencial, exigiria uma representação de metadados. Para este caso, uma interface de marcador (ou interface comportamental) é tão boa quanto qualquer outra. Tentar transmitir a imutabilidade de um tipo de coleção por meio de uma nova entrada de metadados não parece apropriado, uma vez que o CLR não deveria fazer suposições sobre como uma classe arbitrária funciona internamente (e o CLR não tem nenhum conceito de classes de coleção, exceto para matrizes).

@whoisj Acho que isso é pelo menos considerado para uma das futuras versões do C #. Mas essa decisão não afeta a necessidade de interfaces simétricas em todas as coleções. Além disso, posso imaginar cenários em que uma lista somente leitura de itens mutáveis ​​pode ser útil, por exemplo, em jogos que se preocupam com a qualidade e o desempenho do código.

Também coleções imutáveis ​​já estão disponíveis:

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

Para alcançar uma coleção totalmente imutável, só precisamos definir uma immutable T e então usá-la para declarar uma coleção Immutable...<T> .

@HaloFour , já percorremos esse caminho antes: sorriso: mas ainda acredito que o CLR precisa de uma maneira de dizer "aqui está um identificador, leia a partir dele, mas todas as ações que causam qualquer tipo de gravação falharão; ah, e essa imutabilidade é contagioso, portanto, qualquer identificador alcançado a partir do identificador imutável também é imutável (incluindo this ) ".

@dsaf absolutamente! Em outra edição, propus que temos um anti-termo gravável para readdonly para permitir o uso de coleção somente leitura de elementos graváveis. Algo na linha de readonly Bag<writable Element> .

Sugeri que qualquer referência marcada com & seja tratada como imutável pelo compilador. Eu ainda sinto que só precisa ser uma verificação de tempo de compilação e necessariamente aplicada pelo próprio CLR, já que estou buscando principalmente a verificação de tempo de compilação da lógica e não garantias de tempo de execução. Isso cobriria qualquer referência que os desenvolvedores desejassem que fosse imutável, mas em uma base por chamada.

@whoisj Talvez, mas isso é bastante tangencial e transforma esse pedido de algo que dsaf poderia ramificar / RP esta tarde em algo que envolve esforço de três equipes diferentes.

Você também está tratando isso como uma preocupação do compilador. Neste ponto, não há um compilador envolvido (além do compilador JIT) e apenas o verificador pode tentar impedir a execução de código "impróprio". Mesmo os mecanismos de tempo de execução existentes de imutabilidade, initonly fields, podem ser facilmente derrotados se a verificação for ignorada (ou por meio de reflexão).

Eu concordo que seria bom se a linguagem C # e o compilador pudessem ter um suporte melhor para métodos "puros". O atributo PureAttribute já existe, mas é usado esporadicamente e não há suporte de idioma para ele. E mesmo que o C # tenha suportado isso por meio de erros do compilador (pure só pode chamar pure, etc.), ele é facilmente derrotado usando uma linguagem diferente. Mas esses métodos têm que se anunciar e se impor, uma vez que uma vez compilados para IL, todas as apostas estão basicamente erradas e nenhum dos compiladores pode alterar a forma como um assembly existente é executado.

Feira @HaloFour .

Supondo que não tenhamos uma maneira geral de oferecer suporte a referências "puras" ou "const", suponho que a proposta seja a melhor alternativa.

Se você precisar agora, minha biblioteca Commons (Commons.Collections https://github.com/yanggujun/commonsfornet/tree/master/src/Commons.Collections/Set) tem o suporte de conjunto somente leitura. Admin, por favor, exclua esta postagem se isso for considerado um anúncio ... Minha sugestão é procurar por alguma implementação de código aberto.

@yanggujun Obrigado pela sugestão, parece uma boa biblioteca, mas vou lançar a minha própria para evitar dependências extras.

Minha sugestão é procurar alguma implementação de código aberto.

Esta é uma solução alternativa, interfaces fundamentais como IReadOnlySet devem realmente fazer parte do próprio .NET Framework.

Isso precisa de um speclet para ficar "pronto para revisão da API"?

E já que estamos nisso, considere nomear algo diferente de "ReadOnly" (veja a postagem interessante: http://stackoverflow.com/questions/15262981/why-does-listt-implement-ireadonlylistt-in-net-4- 5)
"Legível" parece bom.

@GiottoVerducci Não. Eu preferiria manter um padrão de nomenclatura consistente, mesmo que seja imperfeito. Você está livre para levantar uma questão separada para renomear as interfaces existentes.

Projeto de API proposto:

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);
}

Isso é baseado na API ISet<> (exceto métodos de mutação, obviamente).

É uma pena que Comparer não se encaixe nisso, mas nem ISet<> nem IReadOnlyDictionary<> comparadores de exposição, então é tarde demais para consertar agora.

    bool Contains(T item);

Isso não deveria estar em IReadOnlyCollection<T> já que ICollection<T> tem Contains(T item) ?

O pacote de coleções imutáveis não foi listado no nuget enquanto ainda era beta.
Acho que este é um caso de uso bastante comum e deve ser tratado em bibliotecas padrão.

Há mais trabalho a ser feito na API aqui, como a tag sugere? Fico feliz em dedicar algum tempo a isso se for útil e alguém puder apontar o que é necessário.

A API @ashmind proposta parece ótima.

ISet<T> ser feito para estender IReadOnlySet<T> ? Isso não aconteceu IList<T> / IReadOnlyList<T> ?

Se não, então suponho que as outras mudanças a serem consideradas estão adicionando IReadOnlySet<T> à lista de interface para todas as ISet<T> implementações em corefx incluindo HashSet<T> , SortedSet<T> e seus contrapartes imutáveis ​​em System.Collections.Immutable .

Tenho que concordar com @GiottoVerducci . Usar um nome como IReadOnlySet<T> não declara os recursos do contrato, ele declara as limitações do contrato. Então, usar esse mesmo contrato combinado com outro que contradiz essas limitações é confuso. Acredito que o nome do contrato deve descrever uma afirmação positiva pertencente ao que o implementador suporta. Um nome como IReadableSet<T> não é ótimo, reconhecidamente, mas pelo menos descreve melhor o que o implementador faz.

@HaloFour Concordo em princípio, mas temos a mesma situação agora com IReadOnlyList<T> . Manter a consistência supera o aumento na precisão aqui, IMHO.

@drewnoakes

Eu entendo, e a consistência é importante. Acho que isso também responde por que ISet<T> não deve estender IReadOnlySet<T> , no entanto.

Manter a consistência supera o aumento na precisão aqui, IMHO.

Acho que isso também responde por que ISet<T> não deve estender IReadOnlySet<T> , no entanto.

Eu acho que você está perdendo o ponto. Essa é a razão pela qual IList<T> , ICollection<T> , IDictionary<TKey, TValue> deve, além de ISet<T> , também ser corrigido para implementar interfaces de visualização somente leitura. Caso contrário, todos continuarão confusos ao trabalhar em torno do design não intuitivo do BCL .

@binki

Eu não discordo. O que eu não gosto nisso é ter um contrato que estipula o comportamento de leitura _only_ sendo estendido por um contrato que estipula o comportamento de leitura _escrita_. O nome está errado e a composição está errada. Mas aqui estamos. Eu adoraria votar para mudar os dois, mas duvido que isso esteja na mesa.

@HaloFour

Quando você coloca uma interface em algo, é uma _visão_ em algo. A própria visualização _é_ somente leitura. Supondo que você escreveu um código de tipo seguro e não fará o upcasting, se você receber algo que seja somente leitura, será, para todos os efeitos, somente leitura. Isso não é garantia de que os dados não serão alterados. É como abrir um arquivo somente leitura. Um arquivo aberto como somente leitura pode sofrer mutação por outro processo. Ou como o acesso somente leitura a páginas em um site onde um administrador teria uma visão de leitura e gravação dos dados e pode alterá-los sob você.

Não sei por que somente leitura é considerado o termo errado aqui. Somente leitura _não_ implica imutável. Existe um pacote nuget inteiro / API diferente (onde adicionar / remover gera um novo objeto e a instância atual tem garantia de nunca sofrer mutação - sendo, portanto, imutável) para isso, se for o que você precisa.

Eu estava pensando em algo semelhante. "Somente leitura" em .NET também é uma garantia muito fraca para os campos. Repetindo, tenho certeza de que tudo isso faria mais sentido. Por enquanto, vale a pena ser pragmático.

Portanto, em geral, se um método aceita um IReadOnlySomething<T> você pode, em geral, assumir que ele não o modificará. Não há garantia de que o método de recebimento não fará o upcast da referência e não há garantia de que a implementação da interface também não se modificará internamente quando acessada.

Em C ++, const_cast enfraquece as garantias de const também, o que é uma pena (esp hoje em dia com o modificador mutable ), mas na prática não diminui o quão útil const é um recurso. Você só precisa saber com o que está lidando.

@binki faz uma boa distinção. _Imutável_ no nome implica uma dura garantia de estabilidade ao longo do tempo para todos os envolvidos.

Alguém tem uma fonte confiável para explicar por que IList<T> não estende IReadOnlyList<T> ?

@binki

Uma interface não é uma visão, é um contrato. Esse contrato declara as capacidades do implementador. Se o implementador não realmente implementar esses recursos, consideraria isso uma violação do contrato. Essa classe List<T> afirma que "é um" IReadOnlyList<T> , mas não é. Ele não tem essa capacidade.

Existem várias escolas de pensamento sobre este assunto. Eu pertenço claramente à escola em que a herança de interface segue mais estritamente os relacionamentos "é um" entre os tipos. Eu certamente apoio uma abordagem mais granular para composição com interfaces e acho que List<T> e seus parentes provavelmente poderiam se beneficiar da implementação de algumas interfaces adicionais (ler, escrever, anexar, etc.) Mas eu certamente acho que o O nome de uma interface deve descrever o que um tipo _pode_ fazer, não o que _não_ pode_ fazer. Asserções de capacidade negativa não fazem muito sentido para contratos.

@drewnoakes

Por enquanto, vale a pena ser pragmático.

Eu concordo. Estamos onde estamos. Se IList<T> fosse alterado para estender IReadOnlyList<T> então faria sentido ISet<T> ser alterado para estender IReadOnlySet<T> , etc.

Está sendo redundante demais também exigir interfaces de IReadableX<T> , IWritableX<T> , etc. para viver ao lado de IReadOnlyX<T> ?

Alguém tem uma fonte confiável sobre por que IList<T> não estende IReadOnlyList<T> ?

Aparentemente, seria uma alteração de quebra de ABI ao carregar assemblies que foram compilados em estruturas .net mais antigas. Porque ao implementar uma interface, a maioria dos compiladores irá gerar automaticamente implementações de interface explícitas quando o código-fonte depende da implementação de interface implícita, se você compilou sua classe implementando IList<T> contra um BCL que não tem IList<T> implementação IReadOnlyList<T> , o compilador não criará automaticamente as explícitas IReadOnlyList<T> implementações. Se estou lendo certo: http://stackoverflow.com/a/35940240/429091

@HaloFour Como List<> e HashSet<> implementam ICollection<> e IReadOnlyCollection<> , já adotamos um caminho onde IReadOnly refere-se a acesso e não capacidade. Com base nisso, ter IAnything extend IReadOnlyAnything faz todo o sentido. Eu concordo que IReadable é melhor do que IReadOnly mas neste ponto todos entendem IReadOnly para _mean_ IReadable e usam como tal. Na verdade, estou perpetuando isso intencionalmente em minha própria base de código porque ter duas maneiras de pensar sobre as coisas é mais carga cognitiva do que qualquer coisa, na minha opinião.

Estamos presos ao nome, mas o conceito por trás dele é poderoso o suficiente para que eu realmente desejasse que fosse possível para todas as interfaces estender IReadOnly daqui para frente, assim como fazemos com classes concretas.

@ashmind Acho perfeito que nenhum dos métodos tenha comparadores. Em conjuntos e dicionários, os comparadores não são algo que você possa trocar facilmente porque eles determinam a estrutura de todo o objeto. Além disso, não faria sentido passar um comparador para CaseInsensitiveStringCollection ou qualquer coleção que implique uma certa comparação.

(No caso de uma coleção estranha que implementa Contains(T, IEqualityComparer<T>) mais eficiente do que o método de extensão que já está disponível, provavelmente seria um método de classe único. É difícil imaginar Contains(T, IEqualityComparer<T>) sendo comum o suficiente para acabar em uma interface especializada, mas nada impede que isso aconteça.)

@ jnm2

Eu acho que é perfeito que nenhum dos métodos tenha comparações.

Só para esclarecer, eu queria dizer que deveria expor o comparador, não levar um. Como todo Conjunto ou Dicionário deve ter algum algoritmo de igualdade, isso pode ter sido exposto na interface. Mas não me lembro do meu caso de uso para isso agora - algo como criar um conjunto usando o mesmo comparador de um fornecido externamente.

Embora esta discussão traga muitos pontos interessantes, parece estar longe da sugestão simples e óbvia que iniciou este tópico. E isso é desanimador, porque eu realmente gostaria de ver esse problema resolvido.

Como o OP disse, a falha em manter a paridade entre os tipos de coleção quando IReadOnlyList foi adicionado sem IReadOnlySet é lamentável e muitas pessoas implementaram suas próprias versões da interface IReadOnlySet como soluções alternativas (minha própria equipe tem uma solução alternativa semelhante). Essas interfaces alternativas não são ideais porque as classes corefx não podem implementá-las. Este é o principal motivo para fornecer isso na estrutura: se eu tiver um HashSet, gostaria de poder usá-lo como um IReadOnlySet sem copiar ou embrulhar o objeto que já tenho. Pelo menos para o desempenho, isso geralmente é desejável.

O nome da interface deve ser claramente IReadOnlySet. A consistência supera qualquer preocupação com os nomes IReadOnlyXXX. Esse navio partiu.

Nenhuma das interfaces existentes (IReadOnlyCollection) pode ser alterada. Os requisitos de back-compat para .NET não permitem mudanças como essa. É uma pena que os Comparadores não sejam expostos nas interfaces IReadOnlyXXX existentes (também encontrei isso), mas novamente o navio partiu.

A única questão que parece permanecer do ponto de vista prático é entre essas duas definições potenciais da interface.

Anteriormente proposto por @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);
}

Proposta mínima:

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

Pessoalmente, prefiro essa proposta mínima, uma vez que os outros métodos podem ser derivados; idealmente, haveria uma implementação padrão desses como métodos de extensão na interface IReadOnlySet para que os implementadores de IReadOnlySet não precisassem fornecê-los. Também acho que essa proposta mínima está mais alinhada com as outras interfaces IReadOnlyXXX mínimas.

@ aaron-meyers A única preocupação que eu teria é que IsSubsetOf e amigos não podem ser derivados de Contains de uma maneira _eficiente_. Quando são duas tabelas de hash, por exemplo, confiar em Contains força a implementação a usar loops aninhados em vez de correspondência de hash.

Talvez uma nova interface, IComparableSet<T> possa conter as operações definidas.

Já temos métodos de extensão em IEnumerable<T> para algumas operações de conjunto.

@ jnm2 A implementação desses métodos usados ​​por HashSet requer apenas Contém e enumeração da outra coleção (que IReadOnlySet obteria herdando IReadOnlyCollection). Requer, porém, saber que o outro conjunto usa o mesmo comparador. Talvez valha a pena adicionar a propriedade Comparer a IReadOnlySet para que essas operações possam ser implementadas com eficiência nos métodos de extensão. É uma pena que IReadOnlyDictionary não exponha o KeyComparer também, mas pode valer a pena adicioná-lo a IReadOnlySet, embora não seja totalmente consistente. Há boas razões para que ele tenha sido incluído no IReadOnlyDictionary em primeiro lugar, conforme abordado aqui.

A proposta modificada seria:

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

Alternativamente, o Comparador poderia estar em uma interface separada e as implementações do método de extensão das operações de conjunto usariam apenas a rota eficiente se ambos os objetos implementassem a interface e tivessem os mesmos comparadores. A mesma abordagem poderia ser aplicada para IReadOnlyDictionary (na verdade, talvez eles apenas usem a mesma interface). Algo como ISetComparable. Ou desenhando de @drewnoakes pode haver um IComparableSetmas em vez de definir os operadores de conjunto, ele apenas define o comparador:

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

Nesse caso, IReadOnlySet volta a definir apenas 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;
    }

}

Podemos usar essa abordagem.
O uso será semelhante a este;

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

Muitos requisitos satisfeitos, IReadOnlySeté minimalista. Mas GetComparer agora é um método, não uma propriedade. Mas é uma boa troca.

    /// <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);
    }
}

Acabei de publicar este contrato com ajudantes de injeção para o nuget (https://www.nuget.org/packages/ClrCoder.Collections.ReadOnlySet).
Sinta-se à vontade para usá-lo e enviar problemas aqui: https://github.com/dmitriyse/ClrCoder/issues
Se se tornar um pouco popular (provavelmente após algumas rodadas de refinamento), podemos sugerir esta melhoria para a equipe CoreFX.

@terrajobst está tudo bem para compat para classes existentes para implementar novas interfaces?

@safern há precedente em List<T> obter IReadOnly adicionado.

Então, está planejado adicionar nos próximos lançamentos do .NET framework?

Quando esse trabalho vai pousar? Quaisquer prazos?

https://www.nuget.org/packages/System.Collections.Immutable/
Conjunto completo de coleções imutáveis)

OK. Isso está aqui há muito tempo. Eu preciso muito dele e gostaria de propor uma maneira de abordar isso então.

Em vez de expor tudo isso:

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);

e forçando os clientes a implementar esses membros, vamos apenas adicionar o que é mais importante ter:

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

e nada mais. Mais API sempre pode ser adicionado no futuro, se houver necessidade. Mas não pode ser removido, daí esta proposta.

Em seguida, adicionaríamos essa interface à lista de interfaces estendida por ISet<T> .

Da documentação

ISet<T> : esta interface fornece métodos para implementar conjuntos, que são coleções que possuem elementos únicos e operações específicas. O HashSete SortedSetas coleções implementam essa interface.

Do código

A interface ISet<T> já tem nosso método bool Contains<T>(T item); definido por meio de ICollection<T> . Ele também tem int Count { get; } por meio de ICollection<T> .

Então seria:

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

Mudança trivial, pouco para discutir, grande benefício.

Por favor, vamos fazer acontecer. Informe se essa solicitação pull seria aceita e mesclada. Posso criar então.

@karelz @terrajobst @safern @ianhays

Encontrei essa discussão ao trabalhar em um problema do mundo real, em que queria usar a coleção de chaves de um dicionário para operações de conjunto somente leitura. Para apoiar esse caso, aqui está a API que proponho.

Justificativa

API proposta

 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);
         }
     }
 }

Perguntas abertas

  • Adicionar esses métodos a Dictionary<TKey, TValue>.KeyCollection aceitável, dado o custo do tamanho do código para um tipo genérico comumente instanciado? Veja aqui .
  • IReadOnlySet<T> ser implementado em outros Dictionary KeyCollection s, como SortedDictionary ou ImmutableDictionary ?

Atualizações

  • Removido TryGetValue porque não está em ISet<T> e, como tal, evitaria potencialmente rebasear ISet<T> para implementar IReadOnlySet<T> .
  • Adicionada classe ReadOnlySet<T> que é semelhante a ReadOnlyCollection<T> .

@TylerBrinkley obrigado por adicionar uma proposta de API no tópico. Você se importaria de adicionar argumentos e casos de uso? Para que eu possa marcá-lo como pronto para revisão e deixar os especialistas em API decidirem?

@TylerBrinkley não se esqueça de incluir a propriedade EqualityComparer no IReadOnlySetinterface. Eles estão atualmente ausentes em Dicionários e conjuntos no CoreFX, mas é um problema.

@dmitriyse que uso teria uma propriedade getter apenas IEqualityComparer<T> ? O que você faria com isso?

Os dicionários e conjuntos devem relatar seus EqualityComparers para permitir a clonagem correta da coleção.
Infelizmente, esqueci onde esse assunto foi discutido.

Se você estiver fazendo clonagem, não estaria trabalhando com um tipo concreto? Por que a interface precisa oferecer suporte a IEqualityComparer<T> ?

Por exemplo, se você definiu com as strings e o comparador de igualdade sem distinção entre maiúsculas e minúsculas, receberá uma exceção na criação de um novo HashSet sem especificar EqualityComparer correto. Há casos em que você não pode saber qual EqualityComparer é usado no conjunto especificado.

Não é apenas clonagem. Acho que o cenário muito mais comum é comparar dois conjuntos - eu preciso saber que ambos usam o mesmo comparador para implementar uma comparação ideal usando Contains. Acho que há um exemplo neste tópico.

Dito isso, prefiro IReadOnlySet apenas com o método Contains do que com nada. Seria bom ser capaz de implementar a comparação de Set genericamente, mas não tão comum quanto apenas precisar de uma referência somente leitura para um Set.

Obtenha o Outlook para iOS https://aka.ms/o0ukef


De: Tyler Brinkley [email protected]
Enviado: quinta-feira, 10 de maio de 2018 6:21:52
Para: dotnet / corefx
Cc: Aaron Meyers; Menção
Assunto: Re: [dotnet / corefx] Adicione a interface IReadOnlySet e faça HashSet, SortedSet implementá-la (# 1973)

Se você estiver fazendo clonagem, não estaria trabalhando com um tipo concreto? Por que a interface precisa oferecer suporte ao IEqualityComparer.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no 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 & reservados = 0 , ou cortar o fio https://eur01.safelinks.protection.outlook.com/?url=https%3A% 2F% 2Fgithub.com% 2Fnotifications% 2Funsubscribe-auth% & 2FAMuQLmqboBWyHweWHSUoE1YM2OrfHZZxks5txD7wgaJpZM4E9KK- dados = 02% 7C01% 7C% 7Cc45ea16cd3034ddd69d808d5b678ff33% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% & 7C636615553141417289 sdata = hLtAXEyFNVEgWike6tMwAfUVC% 2BucyjXUDwoLOLDV5gk% 3D & reservados = 0 .

Eu concordo - a única maneira de saber quais tipos de duplicatas você pode encontrar no conjunto (diferencia maiúsculas de minúsculas, não diferencia maiúsculas de minúsculas, etc) é expondo o comparador.

Estou começando a pensar que minha proposta não seria aceita, pois adicionar todos esses métodos a Dictionary<TKey, TValue>.KeyCollection teria um custo de tamanho de código significativo. Consulte esta discussão sobre como adicionar uma nova API a tipos genéricos comumente instanciados.

Acho que você não conseguirá comparar IReadOnlySet .

SortedSet pega IComparer , enquanto HashSet pega IEqualityComparer .

Bom ponto, o comparador pode ser considerado um detalhe de implementação que não pertence a uma interface geral.

Obtenha o Outlook para iOS https://aka.ms/o0ukef


De: Cory Nelson [email protected]
Enviado: quinta-feira, 10 de maio de 2018 17:04:06
Para: dotnet / corefx
Cc: Aaron Meyers; Menção
Assunto: Re: [dotnet / corefx] Adicione a interface IReadOnlySet e faça HashSet, SortedSet implementá-la (# 1973)

Não acho que você conseguirá colocar um comparador no IReadOnlySet.

SortedSet leva um IComparer, enquanto HashSet leva um IEqualityComparer.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no 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 & reservados = 0 , ou cortar o fio https://eur02.safelinks.protection.outlook.com/?url=https%3A% 2F% 2Fgithub.com% 2Fnotifications% 2Funsubscribe-auth% & 2FAMuQLu5JGLcqrpMyGWLygbCsaSQSXFgNks5txNV2gaJpZM4E9KK- dados = 02% 7C01% 7C% 7C0ef6d84125be4c450fdc08d5b6d2b70a% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% & 7C636615938478979295 sdata = 9pnuMULuDu9HWb7un% 2FWYq6iYdjTKFsjN7nKiToaeHkk% 3D & reservados = 0 .

Pode haver algum valor em adicionar interfaces IUnorderedSet e IOrderedSet , mas eu não gostaria que isso descarrilasse empurrando IReadOnlySet .

Eu gosto da sugestão de @pgolebiowski , mas tornaria essa interface ainda mais básica.

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

Isso é adequado para conjuntos incontáveis, como intervalos (reais) também. Entre isso e IReadOnlySet você poderia potencialmente se encaixar em algumas outras interfaces, como ICountableSet (também conhecida IEnumerableSet ), IUnorderedSet e IOrderedSet .

Mas concorde que apenas IReadOnlySet seria uma grande melhoria em relação ao que está disponível atualmente.

+1 para @NickRedwood

Está aberto há mais de 3 anos. Faça isso acontecer mudando a abordagem. A introdução da mudança mencionada por @NickRedwood permite todos os outros aprimoramentos que você está discutindo neste tópico. Todas as abordagens concordam sobre essa única coisa trivial. Portanto, vamos adicionar essa única coisa trivial que é fundamental e, em seguida, criar outro problema para aprimoramentos potenciais que são todos opcionais.

@TylerBrinkley @safern @karelz @terrajobst

Eu realmente não vejo muitos benefícios em uma interface IReadOnlySet<T> com apenas um método Contains . Embora esse método seja útil, ele já está incluído na interface ICollection<T> que também tem uma propriedade IsReadOnly destinada a indicar se os métodos de modificação de ICollection<T> são suportados. Basta implementar ICollection<T> e fazer IsReadOnly return true . A única outra funcionalidade que seria esperada para suportar seria uma propriedade Count , sendo enumerável, e um método CopyTo que não parece muito limitante.

Eu ficaria muito feliz se a proposta IReadOnlySet<T> fosse implementada com base em IReadOnlyCollection<T> - seria uma grande melhoria em relação ao que está disponível atualmente. ( @TylerBrinkley - suponha que você quis dizer IReadOnlyCollection<T> vez de ICollection<T> .

Acho que errei por usar interfaces mais básicas e, se houvesse uma para Set, seria uma interface de método único com um método Contains . Tem uma boa base matemática e é muito limpo. No entanto, ao fazer o retrofit para classes existentes, aceito que o retrofit menos é melhor, e se houvesse apenas uma interface adaptada, então IReadOnlySet<T> base em IReadOnlyCollection<T> deveria ser.

IReadOnlyCollection<> não tem .Contains porque isso evitaria que ele fosse covariante.

Bom ponto e eu corrigi meu post anterior para ser contravariante. IReadOnlySet<T> precisaria ser invariável, a menos que a abordagem postada no início do tópico seja usada, e eu não tenho certeza sobre isso.

@TylerBrinkley, pessoalmente, não sou fã da propriedade IsReadOnly , prefiro que essas informações sejam conhecidas em tempo de compilação em vez de em tempo de execução. Se você estava se referindo à interface IReadOnlyCollection<T> , ainda acho que seria útil para o chamador ou receptor saber que a pesquisa será rápida em vez de uma iteração potencial por meio de uma coleção, embora não tenha certeza se um "conjunto" implica isso.

Ter apenas um método Contains não define o que Set deve fazer, apenas o que Collection deve fazer. Quero dizer que Contains nem mesmo é membro de ISet mas de ICollection qual ISet herda. Os outros membros de ISet devem definir o que Set deve fazer. Eu entendo que a maioria das pessoas provavelmente usa conjuntos exclusivamente para seu método Contains , mas há muito mais para conjuntos do que apenas isso.

@TylerBrinkley Mas é mesmo possível definir IReadOnlyCollection<T>.Contains(T) neste ponto? Eu presumi que não. É por isso que a única opção é introduzir IReadOnlySet<T> e, quando introduzido, garantir que IReadOnlySet<T>.Contains(T) esteja declarado nele.

@ jnm2 diz:

IReadOnlyCollection<> não tem .Contains porque isso evitaria que ele fosse covariante.

Este parece ser o maior problema para mim agora. Seria estranho que IReadOnlySet<T> fosse invariante quando IReadOnlyCollection<T> fosse covariante. Parece que muitas interfaces IReadOnly* foram cuidadosamente definidas como out T com as interfaces graváveis ​​sendo necessariamente invariáveis. A maioria de nós gostaria que IReadOnlySet<T> declarasse pelo menos um método Contains(T) e, ainda assim, isso evitaria que ele fosse covariante.

Se ICollection realmente for o lugar certo para definir Contains(T) , isso é possível? Talvez IReadOnlyProbableCollection<T>.Contains(T) que seria invariante e implementado por Collection<T> e HashSet<T> ?

Acho que a sugestão de @NickRedwood de ISetCharacteristic<T> parece mais limpa, talvez. Isso tem a vantagem de permitir que você não implemente Count que pode ser útil.

pessoalmente, não sou fã da propriedade IsReadOnly, preferiria muito mais ter essas informações conhecidas em tempo de compilação em vez de em tempo de execução.

Esse homem fala bem, sirva-lhe mais vinho.

@binki Concordo que deve haver um método Contains em IReadOnlySet<T> já que IReadOnlyCollection<T> não incluiu um, no entanto, também acho que todos os outros ISet<T> não mutantes

Além do caso de uso Dictionary.KeyCollection que mencionei acima, que outros casos de uso você pode criar para adicionar esta interface?

OK, parece que o design da API é:

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

É a única parte comum do design com a qual todos parecem concordar.

EU DIGO QUE AGORA TERMINAMOS O DESIGN AQUI. Se você quiser estendê-lo no futuro, faça-o em uma edição diferente. Esta é a funcionalidade mais importante que deve estar em produção.

Quem é a favor, quem é contra?

Eu gosto da sugestão de @ashmind . Seria incrível se Dictionary.Keys pudesse implementar essa interface, pois é tecnicamente IReadOnlySet<TKey> .

Você poderia marcar isso como api-ready-for-review ? Acabei de precisar dessa interface novamente e ela ainda não está lá.

Por que ainda não foi implementado?

[EDIT] Desculpas - minha resposta original foi confundida com outra API. Não tenho opinião forte sobre este, texto editado. Desculpe pela confusão!

Por que ainda não foi implementado?

A API ainda não chamou a atenção dos proprietários de área - @safern. Não tenho certeza de quão alto ele está na lista de prioridades das Coleções. O número de votos positivos é bastante alto.
A verdade é que agora estamos bloqueando o .NET Core 3.0, então ele terá que esperar pelo próximo lançamento, pois não é crítico para o 3.0.

Também estou surpreso que isso não seja fornecido fora da caixa.

Estruturas de dados imutáveis ​​podem ser uma grande ajuda para melhorar a capacidade de manutenção do código, mas são difíceis de usar sem uma interface nas bibliotecas padrão que especifica a semântica e permite que diferentes implementações trabalhem juntas.

Sem isso, cada biblioteca que pretende usar um conjunto imutável como parâmetro / valor de retorno precisa recorrer ao uso de IEnumerable, que é menos eficiente e semanticamente incorreto, ou ao uso de suas próprias interfaces / classes em suas assinaturas, que são incompatíveis com as de qualquer outra pessoa.

Estou curioso para saber se há alguma solução alternativa para isso que seja eficiente em termos de Contains lookups e evite especificar o tipo concreto do parâmetro / valor de retorno. O melhor que eu poderia pensar seria usar IEnumerable na assinatura e passar ReadOnlyDictionary.Keys, mas isso parece um pouco desagradável, especialmente em uma biblioteca. Como Enumerable.Contains no Linq usará a implementação de coleção de Contains , isso deve ser eficiente enquanto compatível, mas não comunica a intenção que pode levar ao uso de implementações de menor desempenho.

@ adamt06 Você está procurando por ISet<T> .

@scalablecory Você está certo, com uma implementação imutável, e há uma: ImmutableHashSet<T>

Alguém sabe / entende por que ICollectionnão estende IReadOnlyCollection??
Eu pensei que isso estava um pouco relacionado a este tópico. Em vez de eu abrir um novo tópico. Talvez eu esteja apenas entendendo algo mal.

Outro pensamento, mas completamente fora do assunto, e por que o ReaOnlyCollection não tem:

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

Oh, vejo que você quis dizer que IReadOnlyCollection não os tem. É porque senão a interface não poderia ser covariante .

Não tenho certeza, mas provavelmente tem a ver com a implementação explícita da interface, causando problemas de compatibilidade com versões anteriores. Eu posso ver por que atualizar as interfaces existentes para herdar de outras interfaces de ser um problema, mas não vejo por que criar uma nova interface IReadOnlySet<T> e ter HashSet implementando seria um problema.

OK. Mas ainda não vejo ICollectionnão deveria ser:
c# ICollection<T> : IReadOnlyCollection<out T>
Por que uma coleção de leitura / gravação não deve ser uma extensão de uma coleção somente leitura?

@ generik0

OK. Mas ainda não vejo que ICollection não deveria ser:

ICollection<out T> : IReadOnlyCollection<out T>

Por que uma coleção de leitura / gravação não deve ser uma extensão de uma coleção somente leitura?

Primeiro, observe que é impossível declarar ICollection<out T> porque ICollection<T> tem um membro .Add(T item) .

Em segundo lugar, observe que ICollection<T> aparece em .net-2.0 e IReadOnlyCollection<out T> em .net-4.5 . Se você compilar o código implementando ICollection<T> e então ICollection<T> for alterado para implementar uma nova interface, quaisquer binários compilados existentes serão interrompidos em tempo de execução porque qualquer coisa que apenas implementar ICollection<T> não terá seu IReadOnlyCollection<T> slots preenchidos (possivelmente automaticamente) pelo compilador. Este é o problema de compatibilidade que impediu ICollection<T> de implementar IReadOnlyCollection<T> .

Não acho que isso seja claramente abordado por esta resposta do SO, mas há um SO para isso: https://stackoverflow.com/a/14944400 .

@binki "ICollection"corrigido para ICollection, obrigado por apontar meu erro de digitação ..
DotNetStandard i net461. E é aqui que a mudança deve estar. netstandard 2+

Vou repetir ...
Por que uma coleção de leitura / gravação não deve ser uma extensão de uma coleção somente leitura?

E por que alguém precisa lançar uma coleção para, por exemplo, "ToArray ()" apenas para expor menos?

E, obviamente, você pode adicionar novas interfaces em versões superiores sem quebrar as coisas. ICollection já tem os métodos IReadOnlyCollection implementados. A anotação deve quebrar, ...: - /

@ generik0 Parece que isso não quebraria a compatibilidade do código-fonte, que é o que você está pensando [não estava pensando; iria], mas quebraria a compatibilidade binária que funciona com tabelas IL, não C #.

Ok @ jnm2 obrigado.
Vou parar com meu discurso fora do tópico agora, só acho que é uma arquitetura ruim. Obrigado a todos por ouvir / explicar :-)

@ jnm2

@ generik0 Parece que isso não quebraria a compatibilidade do código-fonte, que é o que você está pensando, mas quebraria a compatibilidade binária que funciona com tabelas IL, não C #.

Para criticar (desculpe), também interromperia a compatibilidade da fonte se sua fonte implementasse explicitamente ICollection<T> antes de .net-4.5. A classe começaria a falhar ao compilar devido a uma falha em implementar explicitamente IReadOnlyCollection<T>.Count . Mas a compatibilidade binária é um grande negócio porque o impediria de almejar uma ampla gama de versões .net (você teria que ter binários diferentes para rodar em ≥.net-4.5 do que em <.net-2 independentemente e você tem que visar ambos, em vez de apenas visar a estrutura mais antiga).

Estou me perguntando se, com a adição do suporte de implementação de interface padrão em C # 8, ICollection<T> poderia implementar IReadOnlyCollection<T> para .NET Core 3.0+.

Talvez uma extensão seja necessária, então é pelo menos a mesma coleção. ou seja, se você precisar obter uma coleção inumerável ou ic para uma coleção real apenas ... Alguma ideia?

[editado por comentário @ jnm2 ]
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 Por que não usar enumerable as IReadOnlyCollection<T> ?? throw ... ou (IReadOnlyCollection<T>)enumerable vez de chamar esse método?

@ jnm2 omg. Você está certo. Às vezes você não vê a imagem clara e complica as coisas !!!

Eu ainda chamaria a extensão, apenas para obter a simplicidade ;-)
Obrigado amigo ....

Qual é o status aqui? Eu realmente gostaria dessa interface em lib padrão e fazer com que o HashSet <> a implemente.

Parece que nossa melhor aposta é esperar um pouco mais para que a proposta do Shapes seja (com sorte) implementada. Então você seria capaz de construir qualquer grupo de formas para representar os tipos de Set que você desejasse e fornecer implementações para que os conjuntos existentes, como HashSet<T> estivessem em conformidade com eles.

Uma biblioteca comunitária para fazer exatamente isso poderia então surgir, cobrindo todos os vários tipos de conjuntos (intensional (predicados) e extensional (listado), ordenado, parcialmente ordenado, não ordenado, contável, infinito contável, infinito incontável, domínios possivelmente matemáticos também como Racional , Números naturais, etc.) como formas diferentes, com todos os métodos de união, interseção e cardinalidade definidos para e entre esses conjuntos.

Isso soa para mim como 5 anos na estrada. Por que uma mudança simples que pode ser implementada em um dia deve esperar por um recurso 1000 vezes maior, ainda não especificado, que pode nem mesmo acontecer?

Isso soa para mim como 5 anos na estrada. Por que uma mudança simples que pode ser implementada em um dia deve esperar por um recurso 1000 vezes maior, ainda não especificado, que pode nem mesmo acontecer?

Estou apenas respondendo à falta de progresso em IReadOnlySet<T> - esta edição já está aberta há 4 anos.

O jeito da Microsoft: As coisas mais simples e úteis levam décadas. É alucinante. 5 anos e contando.

O que é ainda mais engraçado é que eles têm aqui
https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.sdk.sfc.ireadonlyset-1

pensamentos @terrajobst ?

  • Geralmente acreditamos que isso realmente é um buraco em nossa hierarquia de interface. Sugerimos focar apenas em adicionar a interface e implementá-la nas implementações de conjunto existentes. Não podemos fazer ISet<T> extend IReadOnlySet<T> (pela mesma razão que não poderíamos fazer para as outras interfaces mutáveis). Podemos adicionar ReadOnlySet<T> em um estágio posterior. Devemos verificar se IReadOnlySet<T> é apenas ISet<T> menos as APIs mutáveis.
  • Devemos também implementar IReadOnlySet<T> em ImmutableSortedSet<T> e ImmutableHashSet<T> (e seus construtores)
  • Devemos procurar outras implementações 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> {
     }
 }

Faça! EU DESAFIO VOCÊ!

@terrajobst

Não podemos fazer ISet<T> extend IReadOnlySet<T> (pela mesma razão que não poderíamos fazer para as outras interfaces mutáveis).

Isso ainda é verdade mesmo com métodos de interface padrão? Isso significa que https://github.com/dotnet/corefx/issues/41409 deve ser fechado?

@terrajobst

Não podemos fazer ISet<T> extend IReadOnlySet<T> (pela mesma razão que não poderíamos fazer para as outras interfaces mutáveis).

Isso ainda é verdade mesmo com métodos de interface padrão? Isso significa que dotnet / corefx # 41409 deve ser fechado?

Nós discutimos isso. Costumávamos pensar que que ofusca iria funcionar, mas quando entramos a solução concluímos que resultaria normalmente em um diamante caco o que resultaria em um jogo ambíguo. No entanto, isso foi questionado recentemente, então acho que tenho que anotá-lo e ter certeza de que está realmente funcionando ou não.

@terrajobst / @danmosemsft Alguém foi designado para fazer isso?

E @terrajobst para esclarecer o trabalho que queremos realizar é:
`` `
Devemos também implementar IReadOnlySetem ImmutableSortedSete ImmutableHashSet(e seus construtores)
Devemos procurar outras implementações de ISet.
`` ``
Além de implementar as interfaces acima em HashSet, SortedSet.

A varredura é apenas procurar por qualquer coisa e apresentá-la se parecer questionável.

Se isso ainda estiver disponível, eu estaria interessado

@Jlalond não, atribuído a você. Obrigado pela oferta.

@danmosemsft @terrajobst
Apenas uma atualização. Estou trabalhando nisso, adicionei a interface ao núcleo privado Lib, apenas tropeçando no meu caminho por ter coleções.genérico e imutável pegando isso.

Última pergunta, se você souber de alguma coisa, Dan, preciso fazer alguma alteração no Mono para isso? Não sou perspicaz sobre onde termina o corefx e começa o mono. Então, se você sabe, isso pode me salvar de algumas pesquisas independentes

@Jlalond você não deve precisar fazer alterações no Mono. Parte do motivo para mover o tempo de execução Mono para este repo é torná-lo perfeito para usar as mesmas bibliotecas exatas com o CoreCLR ou o tempo de execução Mono. Há apenas uma pequena parte da biblioteca principal que diverge:
coreclrsrcSystem.Private.CoreLib vs mononetcoreSystem.Private.CoreLib. (A maior parte da biblioteca central é compartilhada fora de librariesSystem.Private.CoreLib). Portanto, a menos que você toque nisso - você não será afetado.

@danmosemsft Obrigado pelo esclarecimento, espero que isso seja feito em breve.

Ei @danmosemsft, acompanhe isso. CoreLib está construindo nos assemblies src, posso ver as alterações referenciadas. No entanto, os assemblies ref parecem não estar detectando nenhuma alteração. Isso é tudo o que está me segurando, mas não consigo encontrar nenhuma informação nos documentos. Qualquer pessoa ou indicação que você puder me dar para que eu possa fazer isso (quero dizer, 5 anos depois)

Endereçado por # 32488

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

btecu picture btecu  ·  3Comentários

jchannon picture jchannon  ·  3Comentários

nalywa picture nalywa  ·  3Comentários

EgorBo picture EgorBo  ·  3Comentários

chunseoklee picture chunseoklee  ·  3Comentários