Runtime: Agregue la interfaz IReadOnlySet y haga que HashSet, SortedSet lo implemente

Creado en 9 jun. 2015  ·  104Comentarios  ·  Fuente: dotnet/runtime

Original

Desde que se agregó IReadOnlyList la paridad entre conjuntos y listas ha disminuido. Sería genial restablecerlo.

Usarlo sería una forma independiente de la implementación de decir: "aquí está esta colección de solo lectura donde los elementos son únicos".

Claramente, muchas personas lo necesitan:

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
Algunos comentarios de Guy In: http://blogs.msdn.com/b/bclteam/archive/2013/03/06/update-to-immutable-collections.aspx

Encontré esta discusión cuando trabajaba en un problema del mundo real en el que quería usar la colección de claves de un diccionario para operaciones de conjuntos de solo lectura. Para respaldar ese caso, aquí está la API que propongo.

Editar

Razón fundamental

API propuesta

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

Preguntas abiertas

  • ¿Es aceptable agregar estos métodos a Dictionary<TKey, TValue>.KeyCollection dado el costo del tamaño del código para un tipo genérico comúnmente instanciado? Vea aquí .
  • ¿Debería implementarse IReadOnlySet<T> en otros Dictionary KeyCollection s como SortedDictionary o ImmutableDictionary ?

Actualizaciones

  • Se eliminó TryGetValue porque no está en ISet<T> y, como tal, evitaría potencialmente volver a basar ISet<T> para implementar IReadOnlySet<T> .
  • Se agregó la clase ReadOnlySet<T> que es similar a ReadOnlyCollection<T> .
api-approved area-System.Collections up-for-grabs

Comentario más útil

Diseño de API propuesto:

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

Esto se basa en ISet<> API (excepto los métodos de mutación, obviamente).

Es una pena que Comparer no encajen en esto, pero ni ISet<> ni IReadOnlyDictionary<> exponen los comparadores, por lo que es demasiado tarde para arreglarlo ahora.

Todos 104 comentarios

¿No sería bueno si tuviéramos alguna construcción de lenguaje para hacer las cosas inmutables? Entonces no tendríamos que tener estas interfaces mágicas.

@whoisj ¿Qué idioma? El CLR tiene docenas de ellos.

Incluso como característica del lenguaje potencial, requeriría una representación de metadatos. En este caso, una interfaz de marcador (o interfaz de comportamiento) es tan buena como cualquier otra. Tratar de transmitir la inmutabilidad de un tipo de colección a través de una nueva entrada de metadatos no parece apropiado ya que el CLR no debería hacer suposiciones sobre cómo funciona internamente una clase arbitraria (y el CLR no tiene ningún concepto de clases de colección aparte de matrices).

@whoisj Creo que esto al menos se tiene en cuenta para una de las futuras versiones de C #. Pero esa decisión no afecta la necesidad de interfaces simétricas en todas las colecciones. Además, puedo imaginar escenarios en los que una lista de solo lectura de elementos mutables podría ser útil, por ejemplo, en juegos que se preocupan tanto por la calidad del código como por el rendimiento.

También las colecciones inmutables ya están disponibles:

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

Para lograr una colección completamente inmutable, solo necesitamos una forma de definir un immutable T y luego usarlo para declarar una colección Immutable...<T> .

@HaloFour ya hemos pasado por este camino antes: smirk: pero sigo creyendo que el CLR necesita una forma de decir "aquí hay un identificador, lea de él, pero todas las acciones que causen cualquier tipo de escritura fallarán; oh, y esta inmutabilidad es contagioso, por lo que cualquier identificador alcanzado desde el identificador inmutable también es inmutable (incluido this ) ".

@dsaf absolutamente! En otro número propuse que tenemos un anti-término de escritura para readdonly para permitir el uso de una colección de elementos de escritura de solo lectura. Algo parecido a readonly Bag<writable Element> .

Sugerí que cualquier referencia marcada con & sea ​​tratada como inmutable por el compilador. Sigo sintiendo que solo necesita ser una verificación de tiempo de compilación, y necesariamente ejecutada por el propio CLR, ya que principalmente busco verificación de tiempo de compilación de la lógica y no garantías de tiempo de ejecución. Esto cubriría cualquier referencia que los desarrolladores quisieran que fuera inmutable, pero por llamada.

@whoisj Quizás, pero eso es bastante tangencial y convierte esta solicitud de algo que dsaf podría sucursal / relaciones públicas esta tarde en algo que implica esfuerzo en tres equipos diferentes.

También está tratando esto como una preocupación del compilador. En este punto no hay un compilador involucrado (más allá del compilador JIT) y solo el verificador puede intentar prevenir la ejecución de código "incorrecto". Incluso los mecanismos de inmutabilidad en tiempo de ejecución existentes, los campos initonly , pueden ser fácilmente derrotados si se omite la verificación (o mediante la reflexión).

Estoy de acuerdo en que sería bueno si el lenguaje C # y el compilador pudieran tener un mejor soporte para métodos "puros". El atributo PureAttribute ya existe pero se usa esporádicamente y realmente no hay soporte de idioma para él. E incluso si C # lo admitió a través de errores del compilador (pure solo puede llamar a pure, etc.), es muy fácil de derrotar usando un lenguaje diferente. Pero estos métodos tienen que anunciarse y hacerse cumplir, ya que una vez compilados en IL, todas las apuestas están básicamente desactivadas y ninguno de los compiladores puede modificar la forma en que se ejecuta un ensamblado existente.

@HaloFour feria.

Suponiendo que no tenemos una forma general de admitir referencias "puras" o "const", entonces supongo que la propuesta es la mejor alternativa.

Si lo necesita ahora, mi biblioteca Commons (Commons.Collections https://github.com/yanggujun/commonsfornet/tree/master/src/Commons.Collections/Set) tiene el soporte de solo lectura. Administrador, elimine esta publicación si se considera un anuncio ... Mi sugerencia es buscar alguna implementación de código abierto.

@yanggujun Gracias por la sugerencia, parece una buena biblioteca, pero

Mi sugerencia es buscar alguna implementación de código abierto.

Esta es una solución alternativa, las interfaces fundamentales como IReadOnlySet realmente deberían ser parte de .NET Framework en sí.

¿Se necesita un speclet para estar "listo para la revisión de API"?

Y ya que estamos en eso, considere nombrarlo de manera diferente a "ReadOnly" (vea la publicación interesante: http://stackoverflow.com/questions/15262981/why-does-listt-implement-ireadonlylistt-in-net-4- 5)
"Legible" parece estar bien.

@GiottoVerducci No. Preferiría mantener un patrón de nomenclatura consistente incluso si es imperfecto. Sin embargo, puede plantear un problema por separado para cambiar el nombre de las interfaces existentes.

Diseño de API propuesto:

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

Esto se basa en ISet<> API (excepto los métodos de mutación, obviamente).

Es una pena que Comparer no encajen en esto, pero ni ISet<> ni IReadOnlyDictionary<> exponen los comparadores, por lo que es demasiado tarde para arreglarlo ahora.

    bool Contains(T item);

¿No debería estar en IReadOnlyCollection<T> lugar, ya que ICollection<T> tiene Contains(T item) ?

El paquete de colecciones inmutables no se incluyó en la lista de nuget mientras aún era beta.
Creo que este es un caso de uso bastante común y debería manejarse en bibliotecas estándar.

¿Hay más trabajo por hacer en la API aquí, como sugiere la etiqueta? Me alegra dedicar algo de tiempo a esto si fuera útil y si alguien puede señalar lo que se necesita.

La API que propuso @ashmind se ve muy bien.

¿Se puede hacer que ISet<T> extienda IReadOnlySet<T> ? Esto no sucedió IList<T> / IReadOnlyList<T> ?

De lo contrario, supongo que los otros cambios a considerar son agregar IReadOnlySet<T> a la lista de interfaces para todas las implementaciones de ISet<T> en corefx, incluidos HashSet<T> , SortedSet<T> y sus contrapartes inmutables en System.Collections.Immutable .

Tengo que estar de acuerdo con @GiottoVerducci . Usar un nombre como IReadOnlySet<T> no declara las capacidades de los contratos, declara las limitaciones de los contratos. Entonces, usar ese mismo contrato combinado con otro que contradiga esas limitaciones es confuso. Creo que el nombre del contrato debe describir una afirmación positiva relacionada con lo que apoya el implementador. Un nombre como IReadableSet<T> no es genial, es cierto, pero al menos describe mejor lo que hace el implementador.

@HaloFour Estoy de acuerdo en principio, pero ahora tenemos la misma situación con IReadOnlyList<T> . Mantener la coherencia triunfa sobre el aumento de precisión aquí, en mi humilde opinión.

@drewnoakes

Entiendo y la coherencia es importante. Sin embargo, creo que eso también responde por qué ISet<T> no debería extender IReadOnlySet<T> .

Mantener la coherencia triunfa sobre el aumento de precisión aquí, en mi humilde opinión.

Sin embargo, creo que eso también responde por qué ISet<T> no debería extender IReadOnlySet<T> .

Creo que te estás perdiendo el punto. Esa es la razón por la que IList<T> , ICollection<T> , IDictionary<TKey, TValue> deberían, además de ISet<T> , también ser corregidos para implementar interfaces de vista de solo lectura. De lo contrario, todo el mundo tiene que seguir confundido al trabajar con el diseño poco intuitivo del BCL .

@binki

No estoy en desacuerdo. Lo que no me gusta de eso es tener un contrato que estipula que el comportamiento de lectura _sólo_ se extiende mediante un contrato que estipula el comportamiento de lectura _escritura. El nombre es incorrecto y la composición es incorrecta. Pero aquí estamos. Me encantaría votar para cambiar ambos, pero dudo que eso esté sobre la mesa.

@HaloCuatro

Cuando obtienes una interfaz en algo, es una _vista_ en algo. La vista en sí es de solo lectura. Suponiendo que escribió un código de tipo seguro y no va a realizar la conversión ascendente, si recibe algo que es de solo lectura, es, para todos los efectos, de solo lectura. Eso no garantiza que los datos no cambien. Es como abrir un archivo de solo lectura. Otro proceso puede modificar un archivo abierto de solo lectura. O como el acceso de solo lectura a las páginas de un sitio web donde un administrador tendría una vista de lectura y escritura de los datos y puede cambiarla desde debajo de usted.

No estoy seguro de por qué solo lectura se considera un término incorrecto aquí. Solo lectura _no_ implica inmutable. Hay un paquete completo de nuget / API diferente (donde agregar / eliminar genera un nuevo objeto y se garantiza que la instancia actual nunca mutará, por lo que es inmutable) para eso, si eso es lo que necesita.

Estaba pensando en algo similar. "Solo lectura" en .NET también es una garantía bastante débil para los campos. Si lo repitiera, estoy seguro de que todo esto tendría más sentido. Por ahora vale la pena ser pragmático.

Entonces, en general, si un método acepta un IReadOnlySomething<T> , puede, en general, asumir que no lo modificará. No hay garantía de que el método de recepción no actualice la referencia, y no hay garantía de que la implementación de la interfaz tampoco se modifique internamente cuando se acceda a ella.

En C ++, const_cast debilita las garantías de const , lo cual es una pena (especialmente hoy en día con el modificador mutable ) pero en la práctica no quita lo útil const es una característica. Solo tienes que saber con qué estás lidiando.

@binki hace una buena distinción. _Immutable_ en el nombre implica una dura garantía de estabilidad en el tiempo para todos los involucrados.

¿Alguien tiene una fuente autorizada de por qué IList<T> no extiende IReadOnlyList<T> ?

@binki

Una interfaz no es una vista, es un contrato. Ese contrato declara las capacidades del implementador. Si el implementador no implementa realmente esas capacidades, lo consideraría una violación del contrato. Esa clase List<T> afirma que "es-un" IReadOnlyList<T> , pero no lo es. Carece de esa capacidad.

Existen múltiples escuelas de pensamiento sobre este tema. Claramente pertenezco a la escuela donde la herencia de interfaces sigue más estrictamente las relaciones "es-a" entre tipos. Ciertamente apoyo un enfoque más granular de la composición con interfaces y creo que List<T> y sus parientes probablemente podrían beneficiarse de la implementación de algunas interfaces adicionales de 3 a 4 (leer, escribir, agregar, etc.) Pero ciertamente creo que el El nombre de una interfaz debe describir lo que un tipo _ puede_ hacer, no lo que _ no puede_ hacer. Las afirmaciones de capacidad negativas no tienen mucho sentido para los contratos.

@drewnoakes

Por ahora vale la pena ser pragmático.

Estoy de acuerdo. Estamos donde estamos. Si se cambiara IList<T> para extender IReadOnlyList<T> entonces tiene sentido que se cambie ISet<T> para extender IReadOnlySet<T> , etc.

¿Es demasiado redundante presionar también para que las interfaces IReadableX<T> , IWritableX<T> , etc. vivan junto con IReadOnlyX<T> ?

¿Alguien tiene una fuente autorizada de por qué IList<T> no extiende IReadOnlyList<T> ?

Aparentemente, sería un cambio que rompería la ABI al cargar ensamblados que se compilaron con marcos .net más antiguos. Porque al implementar una interfaz, la mayoría de los compiladores generarán automáticamente implementaciones de interfaz explícitas cuando el código fuente se basa en la implementación de interfaz implícita, si compiló su clase implementando IList<T> contra un BCL que no tiene IList<T> implementando IReadOnlyList<T> , el compilador no creará automáticamente las implementaciones explícitas IReadOnlyList<T> . Si estoy leyendo esto bien: http://stackoverflow.com/a/35940240/429091

@HaloFour Dado que List<> y HashSet<> implementan ICollection<> y IReadOnlyCollection<> , ya hemos adoptado una ruta en la que IReadOnly refiere al acceso y no capacidad. En base a eso, tener IAnything extender IReadOnlyAnything tiene mucho sentido. Estoy de acuerdo en que IReadable es mejor que IReadOnly pero en este punto todos entienden IReadOnly a _mean_ IReadable y lo usan como tal. De hecho, estoy perpetuando eso intencionalmente en mi propia base de código porque tener dos formas de pensar sobre las cosas es más carga cognitiva que cualquier otra cosa en mi opinión.

Estamos atascados con el nombre, pero el concepto detrás de él es lo suficientemente poderoso como para desear realmente que todas las interfaces puedan extender IReadOnly en el futuro, tal como lo hacemos con las clases concretas.

@ashmind Creo que es perfecto que ninguno de los métodos acepte comparadores. En conjuntos y diccionarios, los comparadores no son algo que pueda intercambiar fácilmente porque determinan la estructura de todo el objeto. Además, no tendría sentido pasar un comparador a CaseInsensitiveStringCollection o cualquier colección que implique una cierta comparación.

(En el caso de una colección extraña que implementa Contains(T, IEqualityComparer<T>) más eficiente que el método de extensión que ya está disponible, probablemente sería un método de clase único. Es difícil imaginar que Contains(T, IEqualityComparer<T>) sea ​​común suficiente para terminar en una interfaz especializada, pero nada impide que eso suceda).

@ jnm2

Creo que es perfecto que ninguno de los métodos acepte comparadores.

Solo para aclarar, quería decir que debería exponer al comparador, no tomar uno. Dado que cada conjunto o diccionario debe tener algún algoritmo de igualdad, esto podría haberse expuesto en la interfaz. Pero no recuerdo mi caso de uso para eso ahora, algo así como crear un conjunto usando el mismo comparador que uno proporcionado externamente.

Si bien esta discusión plantea muchos puntos interesantes, parece alejarse mucho de la sugerencia simple y obvia que inició este hilo. Y eso es desalentador porque realmente me gustaría que se abordara este tema.

Como dijo el OP, la imposibilidad de mantener la paridad entre los tipos de colección cuando se agregó IReadOnlyList sin IReadOnlySet es lamentable y muchas personas han implementado sus propias versiones de la interfaz IReadOnlySet como soluciones alternativas (mi propio equipo tiene una solución similar). Esas interfaces de solución alternativa no son ideales porque las clases de corefx no pueden implementarlas. Esta es la razón clave para proporcionar esto en el marco: si tengo un HashSet, me gustaría poder usarlo como un IReadOnlySet sin copiar o envolver el objeto que ya tengo. Para el rendimiento, al menos, esto suele ser deseable.

El nombre de la interfaz debe ser claramente IReadOnlySet. La coherencia supera cualquier preocupación con los nombres IReadOnlyXXX. Ese barco ha zarpado.

Ninguna de las interfaces existentes (IReadOnlyCollection) se puede cambiar. Los requisitos de compatibilidad con versiones anteriores para .NET no permiten cambios como ese. Es lamentable que los Comparers no estén expuestos en las interfaces IReadOnlyXXX existentes (también me he encontrado con esto), pero nuevamente el barco ha zarpado.

La única pregunta que parece permanecer desde un punto de vista práctico es entre estas dos posibles definiciones de la interfaz.

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

Propuesta mínima:

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

Personalmente prefiero esta propuesta mínima ya que los otros métodos pueden derivarse; idealmente, habría una implementación estándar de esos como métodos de extensión sobre la interfaz IReadOnlySet para que los implementadores de IReadOnlySet no necesiten proporcionarlos. También siento que esta propuesta mínima está más en línea con las otras interfaces mínimas de IReadOnlyXXX.

@ aaron-meyers La única preocupación que tendría es que IsSubsetOf y amigos no pueden derivarse de Contains de una manera _eficiente_. Cuando se trata de dos tablas hash, por ejemplo, confiar en Contains obliga a la implementación a utilizar bucles anidados en lugar de la coincidencia de hash.

Quizás una nueva interfaz, IComparableSet<T> podría contener las operaciones establecidas.

Ya tenemos métodos de extensión en IEnumerable<T> para algunas operaciones establecidas.

@ jnm2 La implementación de estos métodos usados ​​por HashSet solo requiere Contains y enumerar la otra colección (que IReadOnlySet obtendría al heredar IReadOnlyCollection). Sin embargo, requiere saber que el otro conjunto usa el mismo comparador. Quizás valga la pena agregar la propiedad Comparer a IReadOnlySet para que estas operaciones se puedan implementar de manera eficiente en los métodos de extensión. Es lamentable que IReadOnlyDictionary no exponga el KeyComparer también, pero puede valer la pena agregarlo a IReadOnlySet aunque no sea del todo coherente. Hay buenas razones por las que debería haberse incluido en IReadOnlyDictionary en primer lugar, como se describe aquí.

La propuesta modificada sería:

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

Alternativamente, el Comparer podría estar en una interfaz separada y las implementaciones del método de extensión de las operaciones de conjunto solo usarían la ruta eficiente si ambos objetos implementan la interfaz y tienen los mismos comparadores. El mismo enfoque podría aplicarse a IReadOnlyDictionary (de hecho, tal vez solo usen la misma interfaz). Algo como ISetComparable. O dibujando de @drewnoakes podría haber un IComparableSetpero en lugar de definir los operadores establecidos, simplemente define el comparador:

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

En este caso, IReadOnlySet vuelve a definir simplemente Contiene:

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 utilizar este enfoque.
El uso se verá así;

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

Se cumplen muchos requisitos, IReadOnlySetes minimalista. Pero GetComparer ahora es el método, no la propiedad. Pero es una buena compensación.

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

Acabo de publicar estos contratos con ayudantes de inyección para nuget (https://www.nuget.org/packages/ClrCoder.Collections.ReadOnlySet).
Siéntase libre de usarlo y enviar problemas aquí: https://github.com/dmitriyse/ClrCoder/issues
Si se vuelve un poco popular (probablemente después de algunas rondas de refinamiento), podemos sugerir esta mejora al equipo de CoreFX.

@terrajobst ¿ está bien que la compatibilidad de las clases existentes implementen nuevas interfaces?

@safern hay un precedente en List<T> obteniendo IReadOnly agregado.

Entonces, ¿se planea agregar en las próximas versiones de .NET Framework?

¿Cuándo aterrizará este trabajo? ¿Alguna línea de tiempo?

https://www.nuget.org/packages/System.Collections.Immutable/
Conjunto completo de colecciones inmutables)

está bien. Esto ha estado aquí durante demasiado tiempo. Lo necesito mucho y me gustaría proponer una forma de abordar esto entonces.

En lugar de exponer todos estos:

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

y obligando a los clientes a implementar estos miembros, agreguemos lo más importante:

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

y nada más. Siempre se pueden agregar más API en el futuro, si es necesario. Pero no se puede eliminar, de ahí esta propuesta.

Luego agregaríamos esta interfaz a la lista de interfaces extendidas por ISet<T> .

De la documentación

ISet<T> : esta interfaz proporciona métodos para implementar conjuntos, que son colecciones que tienen elementos únicos y operaciones específicas. El HashSety SortedSetLas colecciones implementan esta interfaz.

Del código

La interfaz ISet<T> ya tiene nuestro método bool Contains<T>(T item); definido a través de ICollection<T> . También tiene int Count { get; } través de ICollection<T> .

Así sería:

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

Cambio trivial, poco que discutir, gran beneficio.

Por favor, hagámoslo realidad. Avíseme si dicha solicitud de extracción sería aceptada y fusionada. Entonces puedo crearlo.

@karelz @terrajobst @safern @ianhays

Encontré esta discusión cuando trabajaba en un problema del mundo real en el que quería usar la colección de claves de un diccionario para operaciones de conjuntos de solo lectura. Para respaldar ese caso, aquí está la API que propongo.

Razón fundamental

API propuesta

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

Preguntas abiertas

  • ¿Es aceptable agregar estos métodos a Dictionary<TKey, TValue>.KeyCollection dado el costo del tamaño del código para un tipo genérico comúnmente instanciado? Vea aquí .
  • ¿Debería implementarse IReadOnlySet<T> en otros Dictionary KeyCollection s como SortedDictionary o ImmutableDictionary ?

Actualizaciones

  • Se eliminó TryGetValue porque no está en ISet<T> y, como tal, evitaría potencialmente volver a basar ISet<T> para implementar IReadOnlySet<T> .
  • Se agregó la clase ReadOnlySet<T> que es similar a ReadOnlyCollection<T> .

@TylerBrinkley gracias por agregar una propuesta de API en el hilo. ¿Le importaría agregar fundamentos y casos de uso? ¿Para que pueda marcarlo como listo para revisión y dejar que los expertos en API decidan?

@TylerBrinkley no olvide incluir la propiedad EqualityComparer en el IReadOnlySetinterfaz. Actualmente no se encuentran en diccionarios y conjuntos en CoreFX, pero es un problema.

@dmitriyse ¿de qué serviría una propiedad getter solo IEqualityComparer<T> ? ¿Qué harías con eso?

Los diccionarios y conjuntos deben informar a sus EqualityComparers para permitir la clonación correcta de la colección.
Desafortunadamente, olvidé dónde se discutió este tema.

Si está haciendo clonación, ¿no estaría trabajando con un tipo concreto? ¿Por qué la interfaz debería ser compatible con IEqualityComparer<T> ?

Por ejemplo, si ha establecido con el comparador de igualdad de cadenas y mayúsculas y minúsculas, recibirá una excepción al crear un nuevo HashSet sin especificar EqualityComparer correcto. Hay casos en los que no puede saber qué EqualityComparer se utiliza en el conjunto especificado.

No es solo clonación. Creo que el escenario mucho más común es comparar dos conjuntos; necesito saber que ambos usan el mismo comparador para implementar una comparación óptima usando Contains. Creo que hay un ejemplo en este hilo.

Dicho esto, prefiero tener IReadOnlySet solo con el método Contains que nada. Sería bueno poder implementar la comparación de conjuntos de forma genérica, pero no tan común como solo necesitar una referencia de solo lectura a un conjunto.

Obtenga Outlook para iOS https://aka.ms/o0ukef


De: Tyler Brinkley [email protected]
Enviado: jueves 10 de mayo de 2018 a las 6:21:52 a.m.
Para: dotnet / corefx
Cc: Aaron Meyers; Mencionar
Asunto: Re: [dotnet / corefx] Agregue la interfaz IReadOnlySet y haga que HashSet, SortedSet lo implemente (# 1973)

Si está haciendo clonación, ¿no estaría trabajando con un tipo concreto? ¿Por qué la interfaz debería ser compatible con IEqualityComparer?.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en 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 y sdata = xRI27JtyaAwnZ2anY05oTlxmPY5AaGVl% 2BRdXK2uR0% 2F8% 3D y reservado = 0 , o silenciar el hilo https://eur01.safelinks.protection.outlook.com/?url=https%3A% 2F% 2Fgithub.com% 2Fnotifications% 2Funsubscribe-auth% 2FAMuQLmqboBWyHweWHSUoE1YM2OrfHZZxks5txD7wgaJpZM4E9KK- y datos = 02% 7C01% 7C% 7Cc45ea16cd3034ddd69d808d5b678ff33% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% 7C636615553141417289 y sdata = hLtAXEyFNVEgWike6tMwAfUVC% 2BucyjXUDwoLOLDV5gk% 3D y reservado = 0 .

Estoy de acuerdo, la única forma en que puede saber qué tipos de duplicados puede encontrar en el conjunto (distingue entre mayúsculas y minúsculas, no distingue entre mayúsculas y minúsculas, etc.) es exponiendo el comparador.

Estoy empezando a pensar que mi propuesta no sería aceptada, ya que agregar todos esos métodos al Dictionary<TKey, TValue>.KeyCollection tendría un costo de tamaño de código significativo. Vea esta discusión sobre cómo agregar una nueva API a tipos genéricos comúnmente instanciados.

No creo que puedas poner un comparador en IReadOnlySet .

SortedSet toma un IComparer , mientras que HashSet toma un IEqualityComparer .

Buen punto, el comparador podría considerarse un detalle de implementación que no pertenece a una interfaz general.

Obtenga Outlook para iOS https://aka.ms/o0ukef


De: Cory Nelson [email protected]
Enviado: jueves 10 de mayo de 2018 5:04:06 p.m.
Para: dotnet / corefx
Cc: Aaron Meyers; Mencionar
Asunto: Re: [dotnet / corefx] Agregue la interfaz IReadOnlySet y haga que HashSet, SortedSet lo implemente (# 1973)

No creo que pueda poner un comparador en IReadOnlySet.

SortedSet toma un IComparer, mientras que HashSet toma un IEqualityComparer.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en 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 y sdata = PHkDQPiJBEIks8yNyIA7vKODM% 2BkMFI4PRX4lUUBu% 2Bi0% 3D y reservado = 0 , o silenciar el hilo https://eur02.safelinks.protection.outlook.com/?url=https%3A% 2F% 2Fgithub.com% 2Fnotifications% 2Funsubscribe-auth% 2FAMuQLu5JGLcqrpMyGWLygbCsaSQSXFgNks5txNV2gaJpZM4E9KK- y datos = 02% 7C01% 7C% 7C0ef6d84125be4c450fdc08d5b6d2b70a% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% 7C636615938478979295 y sdata = 9pnuMULuDu9HWb7un% 2FWYq6iYdjTKFsjN7nKiToaeHkk% 3D y reservado = 0 .

Puede haber algún valor en agregar interfaces IUnorderedSet y IOrderedSet , pero no me gustaría que eso descarrilara presionando IReadOnlySet .

Me gusta la sugerencia de @pgolebiowski , pero haría que esa interfaz sea aún más básica.

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

Esto también es adecuado para conjuntos incontables como Intervalos (reales). Entre eso y IReadOnlySet , podría encajar en algunas otras interfaces como ICountableSet (también conocido como IEnumerableSet ), IUnorderedSet y IOrderedSet .

Pero esté de acuerdo en que solo IReadOnlySet sería una gran mejora con respecto a lo que está disponible actualmente.

+1 para @NickRedwood

Ha estado abierto por más de 3 años. Haga que esto suceda cambiando el enfoque. La introducción del cambio mencionado por @NickRedwood permite todas las demás mejoras que está discutiendo en este hilo. Todos los enfoques coinciden en esta única cosa trivial. Así que agreguemos esta única cosa trivial que es fundamental y luego creemos otro problema para posibles mejoras que son todas opcionales.

@TylerBrinkley @safern @karelz @terrajobst

Realmente no veo mucho beneficio en una interfaz IReadOnlySet<T> con solo un método Contains . Si bien ese método es útil, ya está incluido en la interfaz ICollection<T> que también tiene una propiedad IsReadOnly destinada a indicar si los métodos de modificación de ICollection<T> son compatibles. Simplemente implemente ICollection<T> y haga IsReadOnly return true . La única otra funcionalidad que se esperaría que admita sería una propiedad Count , que es enumerable, y un método CopyTo que no parece demasiado limitante.

Me alegraría mucho que el IReadOnlySet<T> se implemente en función de IReadOnlyCollection<T> ; sería una gran mejora con respecto a lo que está disponible actualmente. ( @TylerBrinkley : suponga que se refería a IReadOnlyCollection<T> lugar de ICollection<T> .

Supongo que me equivoco del lado de interfaces más básicas, y si hubiera una para Set, sería una interfaz de método único con un método Contains . Tiene una buena base matemática y es muy limpio. Sin embargo, al adaptarme a clases existentes, acepto que actualizar menos es mejor, y si solo se reequipara una interfaz, entonces IReadOnlySet<T> basado en IReadOnlyCollection<T> debería serlo.

IReadOnlyCollection<> no tiene .Contains porque eso evitaría que sea covariante.

Buen punto y he corregido mi publicación anterior para que sea contravariante. IReadOnlySet<T> necesitaría ser invariante a menos que se use el enfoque publicado al principio del hilo, y no estoy seguro de ese.

@TylerBrinkley personalmente no soy fanático de la propiedad IsReadOnly , preferiría que esta información se conozca en tiempo de compilación en lugar de en tiempo de ejecución. Si se refería a la interfaz IReadOnlyCollection<T> , sigo pensando que sería útil para la persona que llama o la persona que llama saber que la búsqueda será rápida en lugar de una posible iteración a través de una colección, aunque no estoy seguro de si un "conjunto" implica eso.

Tener solo un método Contains no define lo que debe hacer un Set , solo lo que debe hacer un Collection . Quiero decir que Contains ni siquiera es miembro de ISet sino de ICollection cual ISet hereda. Los otros miembros de ISet deberían estar obligados a definir lo que debería hacer un Set . Entiendo que la mayoría de la gente probablemente usa conjuntos exclusivamente para su método Contains , pero hay mucho más en los conjuntos que solo eso.

@TylerBrinkley ¿ Pero es posible definir IReadOnlyCollection<T>.Contains(T) en este punto? Supuse que no. Es por eso que la única opción es introducir IReadOnlySet<T> y, cuando se introduzca, asegurarse de que se declare IReadOnlySet<T>.Contains(T) en él.

@ jnm2 dice:

IReadOnlyCollection<> no tiene .Contains porque eso evitaría que sea covariante.

Este parece ser el mayor problema para mí en este momento. Sería extraño que IReadOnlySet<T> fuera invariante cuando IReadOnlyCollection<T> es covariante. Parece que muchas interfaces IReadOnly* se diseñaron cuidadosamente para ser out T siendo las interfaces de escritura necesariamente invariantes. La mayoría de nosotros querría que IReadOnlySet<T> declarara al menos un método Contains(T) y, sin embargo, eso evitaría que sea covariante.

Si ICollection es realmente el lugar correcto para definir Contains(T) , ¿es eso posible? ¿Quizás IReadOnlyProbableCollection<T>.Contains(T) que sería invariante e implementado por Collection<T> y HashSet<T> ?

Creo que la sugerencia de @ NickRedwood de ISetCharacteristic<T> parece más limpia tal vez. Eso incluso tiene el beneficio de permitirle no implementar Count que podría ser útil.

personalmente no soy un fanático de la propiedad IsReadOnly, preferiría que esta información se conociera en tiempo de compilación en lugar de en tiempo de ejecución.

Ese hombre habla bien, sírvele más vino.

@binki Estoy de acuerdo en que debe haber un método Contains en IReadOnlySet<T> ya que IReadOnlyCollection<T> no incluyó uno, sin embargo, también creo que todos los demás ISet<T> no mutantes

Además del caso de uso Dictionary.KeyCollection que mencioné anteriormente, ¿qué otros casos de uso se le ocurren para agregar esta interfaz?

OK, parece que el diseño de la API es:

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

Es la única parte común del diseño con la que todos parecen estar de acuerdo.

DIGO QUE AHORA TERMINAMOS EL DISEÑO AQUÍ. Si desea ampliarlo en el futuro, hágalo en una edición diferente. Esta es la funcionalidad más importante que debería estar en prod.

¿Quién está a favor, quién está en contra?

Me gusta la sugerencia de @ashmind . Sería fantástico si Dictionary.Keys pudiera implementar esta interfaz, ya que técnicamente es IReadOnlySet<TKey> .

¿Podría marcar esto como api-ready-for-review ? Acabo de encontrarme necesitando esta interfaz nuevamente y todavía no está allí.

¿Por qué no se ha implementado todavía?

[EDITAR] Disculpas: mi respuesta original se confundió con otra API. No tengo una opinión fuerte sobre este, texto editado. ¡Perdon por la confusion!

¿Por qué no se ha implementado todavía?

La API aún no llamó la atención de los propietarios del área: @safern. No estoy seguro de qué tan alto está en la lista de prioridades de Colecciones. El número de votos a favor es bastante alto.
La verdad es que ahora estamos bloqueando para .NET Core 3.0, por lo que tendrá que esperar a la próxima versión como mínimo, ya que no es crítico para 3.0.

También me sorprende que esto no se proporcione de fábrica.

Las estructuras de datos inmutables pueden ser de gran ayuda para mejorar la capacidad de mantenimiento del código, pero son difíciles de usar sin una interfaz en las bibliotecas estándar que especifique la semántica y permita que diferentes implementaciones trabajen juntas.

Sin esto, cada biblioteca que desee usar un conjunto inmutable como parámetro / valor de retorno debe recurrir al uso de IEnumerable, que es menos eficiente y semánticamente incorrecto, o al uso de sus propias interfaces / clases en sus firmas, lo que es incompatible con el de cualquier otra persona.

Tengo curiosidad por saber si hay soluciones para esto que sean eficientes en términos de búsquedas Contains y eviten especificar el tipo concreto del parámetro / valor de retorno. Lo mejor que se me ocurrió sería usar IEnumerable en la firma y pasar ReadOnlyDictionary.Keys, pero eso parece un poco desagradable, especialmente en una biblioteca. Dado que Enumerable.Contains en Linq usará la implementación de colección de Contains , esto debería ser eficiente aunque compatible, pero no comunica la intención que puede llevar al uso de implementaciones de menor rendimiento.

@ adamt06 Estás buscando ISet<T> .

@scalablecory Tienes razón, con una implementación inmutable, y hay una: ImmutableHashSet<T>

¿Alguien sabe / entiende por qué ICollectionno extiende IReadOnlyCollection??
Pensé que esto estaba un poco relacionado con este hilo. En lugar de que yo abra un nuevo hilo. Quizás solo estoy entendiendo mal algo.

Otro pensamiento, pero completamente fuera de tema, y ​​por qué ReaOnlyCollection no tiene:

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

Oh, veo que quisiste decir que IReadOnlyCollection no los tiene. Es porque, de lo contrario, la interfaz no podría ser covariante .

No estoy seguro, pero probablemente tenga algo que ver con la implementación de la interfaz explícita que causa problemas con la compatibilidad con versiones anteriores. Puedo ver por qué actualizar las interfaces existentes para heredar de otras interfaces de ser un problema, pero no veo por qué crear una nueva interfaz IReadOnlySet<T> y tener HashSet implementar sería un problema.

está bien. Pero todavía no veo ICollectionno debería ser:
c# ICollection<T> : IReadOnlyCollection<out T>
¿Por qué una colección de lectura / escritura no debería ser también una extensión de una colección de solo lectura?

@ generik0

está bien. Pero todavía no veo ICollection no debería ser:

ICollection<out T> : IReadOnlyCollection<out T>

¿Por qué una colección de lectura / escritura no debería ser también una extensión de una colección de solo lectura?

Primero, tenga en cuenta que es imposible declarar ICollection<out T> porque ICollection<T> tiene un miembro .Add(T item) .

En segundo lugar, tenga en cuenta que ICollection<T> aparece en .net-2.0 y IReadOnlyCollection<out T> aparece en .net-4.5 . Si compila el código como implementando ICollection<T> y luego ICollection<T> se cambia para implementar una nueva interfaz, cualquier binario compilado existente se romperá en tiempo de ejecución porque cualquier cosa que solo implemente ICollection<T> no tendría su IReadOnlyCollection<T> ranuras rellenadas (posiblemente automáticamente) por el compilador. Este es el problema de compatibilidad que impidió a ICollection<T> implementar IReadOnlyCollection<T> .

No creo que esto se aborde claramente con esta respuesta SO, pero hay un SO para ello: https://stackoverflow.com/a/14944400 .

@binki "ICollection"fijo en ICollection, gracias por señalar mi error tipográfico ..
DotNetStandard i net461. Y aquí es donde debería estar el cambio. netstandard 2+

Repetiré ...
¿Por qué una colección de lectura / escritura no debería ser también una extensión de una colección de solo lectura?

¿Y por qué debería uno necesitar lanzar una colección a, por ejemplo, "ToArray ()" solo para exponer menos si lo hace?

Y, por supuesto, puede agregar nuevas interfaces en versiones superiores sin romper las cosas. ICollection ya tiene implementados los métodos IReadOnlyCollection. Nota debe romperse, ...: - /

@ generik0 Parece que no rompería la compatibilidad de fuentes, que es en lo que estás pensando [no estaba pensando; lo haría], pero rompería la compatibilidad binaria que funciona con tablas IL, no C #.

Ok @ jnm2 gracias.
Voy a detener mi perorata fuera de tema ahora, solo pienso que es mala arquitectura. Gracias a todos por escuchar / explicar :-)

@ jnm2

@ generik0 Parece que no rompería la compatibilidad de fuentes, que es en lo que está pensando, pero rompería la compatibilidad binaria que funciona con tablas IL, no C #.

Para ser quisquilloso (lo siento), también rompería la compatibilidad de la fuente si su fuente implementara explícitamente ICollection<T> antes de .net-4.5. La clase comenzaría a fallar al compilar debido a una falla al implementar explícitamente IReadOnlyCollection<T>.Count . Pero la compatibilidad binaria es un problema mayor porque evitaría que apuntes a una amplia gama de versiones de .net (tendrías que tener diferentes binarios para ejecutar en ≥.net-4.5 que en <.net-2 independientemente y tienen que apuntar a ambos en lugar de apuntar solo al marco anterior).

Me pregunto si con la adición de soporte de implementación de interfaz predeterminada en C # 8 si ICollection<T> podría implementar IReadOnlyCollection<T> para .NET Core 3.0+.

Tal vez se necesite una extensión, entonces se trata de la misma colección. es decir, si necesita obtener un ienumerable o icollection a un readonlycollection ... ¿Alguna idea?

[editado por @ jnm2 comentario]
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 ¿ enumerable as IReadOnlyCollection<T> ?? throw ... o (IReadOnlyCollection<T>)enumerable lugar de llamar a ese método?

@ jnm2 Dios mío. Tienes razón. ¡A veces no ves la imagen clara y las cosas son demasiado complicadas!

Todavía llamaría a la extensión, solo para obtener la simplicidad ;-)
Gracias amigo....

¿Cuál es el estado aquí? Realmente me gustaría esta interfaz en la biblioteca estándar y que HashSet <> la implemente.

Parece que nuestra mejor apuesta puede ser esperar un poco más para que se implemente (con suerte) la propuesta de Shapes. Luego, podrá construir cualquier grupo de formas para representar los tipos de conjuntos que desee y proporcionar implementaciones para que los conjuntos existentes como HashSet<T> ajusten a ellos.

Entonces podría surgir una biblioteca comunitaria para hacer exactamente esto, cubriendo todos los diversos tipos de conjuntos (intensional (predicados) y extensional (enumerados), ordenados, parcialmente ordenados, desordenados, contables, infinitos contables, infinitos incontables, posiblemente también dominios matemáticos como Rational , Números naturales, etc.) como formas diferentes, con todos los métodos de unión, intersección y cardinalidad definidos para y entre estos conjuntos.

Eso me suena a 5 años en el futuro. ¿Por qué un cambio simple que se puede implementar en un día debe esperar una característica 1000 veces más grande que aún no se ha especificado y que tal vez ni siquiera suceda?

Eso me suena a 5 años en el futuro. ¿Por qué un cambio simple que se puede implementar en un día debe esperar una característica 1000 veces más grande que aún no se ha especificado y que tal vez ni siquiera suceda?

Solo estoy respondiendo a la falta de progreso en IReadOnlySet<T> ; después de todo, este problema ya ha estado abierto 4 años.

Al estilo de Microsoft: las cosas más simples y útiles llevan décadas. Es alucinante. 5 años y contando.

Lo que es aún más divertido es que lo tienen aquí.
https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.sdk.sfc.ireadonlyset-1

@terrajobst pensamientos?

  • En general, creemos que se trata de un agujero en la jerarquía de nuestra interfaz. Sugerimos centrarse en simplemente agregar la interfaz e implementarla en las implementaciones de conjuntos existentes. No podemos hacer que ISet<T> extender IReadOnlySet<T> (por la misma razón que no pudimos hacer con las otras interfaces mutables). Podemos agregar ReadOnlySet<T> en una etapa posterior. Deberíamos comprobar que IReadOnlySet<T> es solo ISet<T> menos las API mutables.
  • También deberíamos implementar IReadOnlySet<T> en ImmutableSortedSet<T> y ImmutableHashSet<T> (y sus constructores)
  • Deberíamos buscar otras implementaciones 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> {
     }
 }

¡Hazlo! TE RETO!

@terrajobst

No podemos hacer que ISet<T> extender IReadOnlySet<T> (por la misma razón que no pudimos hacer con las otras interfaces mutables).

¿Esto sigue siendo cierto incluso con los métodos de interfaz predeterminados? ¿ Eso significa que

@terrajobst

No podemos hacer que ISet<T> extender IReadOnlySet<T> (por la misma razón que no pudimos hacer con las otras interfaces mutables).

¿Esto sigue siendo cierto incluso con los métodos de interfaz predeterminados? ¿ Eso significa que

Hablamos de esto. Estamos acostumbrados a pensar que oscurece el que trabajarían, pero cuando entramos la solución llegamos a la conclusión de que resultaría normalmente en un diamante fragmento que daría lugar a una coincidencia ambigua. Sin embargo, esto fue cuestionado recientemente, así que creo que tengo que escribirlo y asegurarme de que realmente esté funcionando o no.

@terrajobst / @danmosemsft ¿Alguien ha sido asignado a esto?

Y, @terrajobst para aclarar el trabajo que queremos lograr es:
''
También deberíamos implementar IReadOnlySeten ImmutableSortedSety ImmutableHashSet(y sus constructores)
Deberíamos buscar otras implementaciones de ISet.
`` ``
Además de implementar las interfaces anteriores en HashSet, SortedSet.

El escaneo es simplemente buscar cualquier cosa y abrirla si parece cuestionable.

Si esto todavía está en juego, me interesaría

@Jlalond no , asignado a ti. Gracias por la oferta.

@danmosemsft @terrajobst
Solo una actualización. Estoy trabajando en esto, agregué la interfaz al núcleo privado de Lib, simplemente tropezando con el hecho de que las colecciones genéricas e inmutables se den cuenta de esto.

Última pregunta si no sabes lo que piensas Dan, ¿necesito hacer algún cambio en Mono para esto? No sé dónde termina corefx y dónde comienza mono. Entonces, si sabe que podría salvarme de una investigación independiente

@Jlalond no debería necesitar hacer cambios en Mono. Parte de la razón para mover el tiempo de ejecución Mono a este repositorio es hacer que sea perfecto para usar las mismas bibliotecas exactas con CoreCLR o el tiempo de ejecución Mono. Solo hay una pequeña parte de la biblioteca principal que diverge:
coreclrsrcSystem.Private.CoreLib frente a mononetcoreSystem.Private.CoreLib. (La mayor parte de la biblioteca principal se comparte fuera de librariesSystem.Private.CoreLib). Entonces, a menos que lo toque, no se verá afectado.

@danmosemsft Gracias por la aclaración, espero que esto se haga en breve.

Hola @danmosemsft, sigue esto. CoreLib se está construyendo en los ensamblados src. Puedo ver los cambios a los que se hace referencia. Sin embargo, los ensamblados de referencia parecen no detectar ningún cambio. Esto es todo lo que me detiene, pero no puedo encontrar ninguna información en los documentos. Cualquier persona o sugerencia que pueda darme para que pueda hacer esto (quiero decir, 5 años después)

Dirigido por # 32488

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

jzabroski picture jzabroski  ·  3Comentarios

EgorBo picture EgorBo  ·  3Comentarios

bencz picture bencz  ·  3Comentarios

iCodeWebApps picture iCodeWebApps  ·  3Comentarios

Timovzl picture Timovzl  ·  3Comentarios