Runtime: Добавьте интерфейс IReadOnlySet и сделайте так, чтобы HashSet, SortedSet реализовали его.

Созданный на 9 июн. 2015  ·  104Комментарии  ·  Источник: dotnet/runtime

Оригинал

После добавления IReadOnlyList соотношение между наборами и списками снизилось. Было бы здорово восстановить его.

Его использование было бы независимым от реализации способом сказать: «вот эта доступная только для чтения коллекция, в которой элементы уникальны».

Понятно, что многим это нужно:

SQL Server: https://msdn.microsoft.com/en-us/library/gg503096.aspx
Рослин: https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/InternalUtilities/IReadOnlySet.cs
Некоторые комментарии парня: http://blogs.msdn.com/b/bclteam/archive/2013/03/06/update-to-immutable-collections.aspx

Я нашел это обсуждение, когда работал над реальной проблемой, когда я хотел использовать набор ключей словаря для операций с наборами только для чтения. Чтобы поддержать этот случай, вот API, который я предлагаю.

Редактировать

Обоснование

Предлагаемый API

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

Открытые вопросы

  • Допустимо ли добавление этих методов в Dictionary<TKey, TValue>.KeyCollection учетом стоимости размера кода для обычно создаваемого универсального типа? Смотрите здесь .
  • Следует ли реализовать IReadOnlySet<T> в других Dictionary KeyCollection таких как SortedDictionary или ImmutableDictionary ?

Обновления

  • Удалено TryGetValue как его нет в ISet<T> что может предотвратить потенциально когда-либо перебазирование ISet<T> для реализации IReadOnlySet<T> .
  • Добавлен класс ReadOnlySet<T> который похож на ReadOnlyCollection<T> .
api-approved area-System.Collections up-for-grabs

Самый полезный комментарий

Предлагаемый дизайн API:

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

Это основано на ISet<> API (кроме, очевидно, методов мутации).

Жалко, что Comparer не влезает в это, но тогда ни ISet<> ни IReadOnlyDictionary<> раскрывают компараторы, так что уже поздно исправлять.

Все 104 Комментарий

Было бы неплохо, если бы у нас была какая-то языковая конструкция, делающая вещи неизменяемыми? Тогда у нас не было бы этих волшебных интерфейсов.

@whoisj На каком языке? В CLR их десятки.

Даже в качестве потенциальной языковой функции для этого потребуется представление метаданных. В этом случае интерфейс маркера (или поведенческий интерфейс) ничем не хуже других. Попытка передать неизменность типа коллекции через новую запись метаданных не кажется уместной, поскольку CLR не должна делать предположений относительно того, как произвольный класс функционирует внутри (а в CLR вообще нет концепции классов коллекций, кроме массивы).

@whoisj Я думаю, что это, по крайней мере, рассматривается в одной из будущих версий C #. Но это решение не влияет на необходимость симметричных интерфейсов для всех коллекций. Кроме того, я могу представить сценарии, в которых список изменяемых элементов, доступный только для чтения, может быть полезен, например, в играх, которые заботятся как о качестве кода, так и о производительности.

Также уже доступны неизменяемые коллекции:

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

Чтобы создать полностью неизменяемую коллекцию, нам просто нужен способ определения immutable T а затем использовать его для объявления коллекции Immutable...<T> .

@HaloFour мы уже шли по этому пути раньше заразен, поэтому любой дескриптор, полученный из неизменяемого дескриптора, также является неизменяемым (включая this ) ".

@dsaf абсолютно! В другом выпуске я предложил, чтобы у нас был записываемый анти-термин для readdonly, чтобы разрешить использование только readable коллекции записываемых элементов. Что-то вроде readonly Bag<writable Element> .

Я предложил, чтобы любая ссылка, отмеченная символом & , рассматривалась компилятором как неизменяемая. Я по-прежнему считаю, что это должна быть только проверка времени компиляции, и она обязательно должна выполняться самой CLR, поскольку я в основном ищу проверку логики во время компиляции, а не гарантии времени выполнения. Это будет охватывать любую ссылку, которую разработчики хотели сделать неизменной, но для каждого вызова.

@whoisj Возможно, но это довольно косвенно, и это превращает этот запрос от чего-то, что dsaf может ветвиться / PR сегодня днем, во что-то, что требует усилий трех разных команд.

Вы также относитесь к этому как к проблеме компилятора. На этом этапе компилятор не задействован (помимо JIT-компилятора), и только верификатор может попытаться предотвратить выполнение «неправильного» кода. Даже существующие механизмы неизменяемости среды выполнения, поля initonly , могут быть легко побеждены, если проверка пропущена (или через отражение).

Я согласен с тем, что было бы неплохо, если бы язык C # и компилятор могли лучше поддерживать "чистые" методы. Атрибут PureAttribute уже существует, но он используется время от времени, и для него действительно нет языковой поддержки. И даже если C # поддерживал его из-за ошибок компилятора (чистый может называть только чистый и т. Д.), Его очень легко победить, используя другой язык. Но эти методы должны заявить о себе и обеспечить себя, поскольку после компиляции в IL все ставки в основном отключены, и ни один из компиляторов не может изменить то, как выполняется существующая сборка.

@HaloFour ярмарка.

Предполагая, что у нас нет общего способа поддержки "чистых" или "константных" ссылок, я полагаю, что предложенная альтернатива - лучшая.

Если вам это нужно сейчас, моя библиотека Commons (Commons.Collections https://github.com/yanggujun/commonsfornet/tree/master/src/Commons.Collections/Set) имеет поддержку только для чтения. Администратор, пожалуйста, удалите этот пост, если он воспринимается как реклама ... Я предлагаю поискать какую-нибудь реализацию с открытым исходным кодом.

@yanggujun Спасибо за предложение, это кажется хорошей библиотекой, но я буду использовать свою, чтобы избежать дополнительных зависимостей.

Я предлагаю поискать какую-нибудь реализацию с открытым исходным кодом.

Это обходной путь, фундаментальные интерфейсы, такие как IReadOnlySet, действительно должны быть частью самой .NET Framework.

Нужен ли для этого спеклет, чтобы стать «готовым к обзору API»?

И пока мы занимаемся этим, подумайте о том, чтобы назвать его иначе, чем "ReadOnly" (см. Интересный пост: http://stackoverflow.com/questions/15262981/why-does-listt-implement-ireadonlylistt-in-net-4- 5)
"Читаемый" кажется нормальным.

@GiottoVerducci: Нет. Я бы предпочел сохранить последовательный шаблон именования, даже если он несовершенный. Однако вы можете поднять отдельный вопрос, чтобы переименовать существующие интерфейсы.

Предлагаемый дизайн API:

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

Это основано на ISet<> API (кроме, очевидно, методов мутации).

Жалко, что Comparer не влезает в это, но тогда ни ISet<> ни IReadOnlyDictionary<> раскрывают компараторы, так что уже поздно исправлять.

    bool Contains(T item);

Разве это не должно быть в IReadOnlyCollection<T> поскольку ICollection<T> имеет Contains(T item) ?

Пакет неизменяемых коллекций был исключен из списка nuget, пока он еще был бета-версией.
Я думаю, что это довольно распространенный вариант использования, и его следует обрабатывать в стандартных библиотеках.

Есть ли здесь еще работа над API, как подсказывает тег? Я счастлив потратить некоторое время на это, если это будет полезно и кто-нибудь сможет указать, что нужно.

Предлагаемый API @ashmind выглядит великолепно.

Можно ли заставить ISet<T> продлить IReadOnlySet<T> ? Этого не произошло IList<T> / IReadOnlyList<T> ?

Если нет, то я полагаю, что другие изменения, которые следует учитывать, - это добавление IReadOnlySet<T> в список интерфейсов для всех реализаций ISet<T> в corefx, включая HashSet<T> , SortedSet<T> и их неизменяемые аналоги в System.Collections.Immutable .

Я должен согласиться с @GiottoVerducci . Использование такого имени, как IReadOnlySet<T> , не объявляет возможности контрактов, оно объявляет ограничения контрактов. Использование одного и того же контракта в сочетании с другим, противоречащим этим ограничениям, сбивает с толку. Я считаю, что название контракта должно описывать положительное утверждение, относящееся к тому, что поддерживает разработчик. Имя вроде IReadableSet<T> , по общему признанию, не очень хорошее, но оно, по крайней мере, лучше описывает то, что делает разработчик.

@HaloFour Я в принципе согласен, но сейчас такая же ситуация с IReadOnlyList<T> . ИМХО, здесь сохранение последовательности важнее повышения точности.

@drewnoakes

Я понимаю, и последовательность важна. Я думаю, это также отвечает на вопрос, почему ISet<T> не следует расширять IReadOnlySet<T> .

ИМХО, здесь сохранение последовательности важнее повышения точности.

Я думаю, это также отвечает на вопрос, почему ISet<T> не следует расширять IReadOnlySet<T> .

Я думаю, вы упускаете суть. Это причина того, что IList<T> , ICollection<T> , IDictionary<TKey, TValue> должны, помимо ISet<T> , также быть исправлены для реализации интерфейсов просмотра только для чтения. В противном случае, работая над неинтуитивно понятным дизайном BCL, все будут сбиты с толку .

@binki

Я не согласен. Что мне не нравится в этом, так это наличие контракта, который предусматривает поведение только для чтения, расширяемое контрактом, который предусматривает поведение чтения-записи. Неправильное название и неправильная композиция. Но вот и мы. Я хотел бы проголосовать за изменение обоих, но я сомневаюсь, что такое есть.

@HaloFour

Когда вы получаете интерфейс к чему-то, это _view_ на что-то. Само представление доступно только для чтения. Предполагая, что вы написали типобезопасный код и не будете заниматься апкастингом, если вы получите что-то, что предназначено только для чтения, это для всех намерений и целей только для чтения. Это не гарантия того, что данные не изменятся. Это похоже на открытие файла только для чтения. Файл, открытый только для чтения, может быть изменен другим процессом. Или как доступ только для чтения к страницам на веб-сайте, где администратор может просматривать данные для чтения и записи и изменять их из-под вас.

Я не уверен, почему термин «только чтение» здесь неправильный. Только для чтения _не_ означает неизменность. Существует целый пакет nuget / другой API (где добавление / удаление генерирует новый объект, а текущий экземпляр гарантированно никогда не будет изменяться - таким образом, он является неизменным) для этого, если это то, что вам нужно.

Я думал о чем-то похожем. «Только для чтения» в .NET также является довольно слабой гарантией для полей. Учитывая переделку, я уверен, что все это будет иметь больше смысла. А пока стоит проявить прагматичность.

В общем, если метод принимает IReadOnlySomething<T> вы можете предположить, что он не будет его изменять. Нет гарантии, что принимающий метод не будет преобразовывать ссылку, и нет гарантии, что реализация интерфейса не будет внутренне изменяться при доступе.

В C ++ const_cast ослабляет гарантии const , что тоже досадно (особенно в настоящее время с модификатором mutable ), но на практике это не снижает полезности const - это функция. Вам просто нужно знать, с чем вы имеете дело.

@binki делает хорошее различие. _Immutable_ в названии подразумевает твердую гарантию стабильности во времени для всех участников.

Есть ли у кого-нибудь авторитетный источник, почему IList<T> не продлевает IReadOnlyList<T> ?

@binki

Интерфейс - это не представление, это контракт. В этом контракте декларируются возможности исполнителя. Если разработчик на самом деле не реализует эти возможности, я буду считать это нарушением контракта. Этот класс List<T> утверждает, что он "является" IReadOnlyList<T> , но это не так. У него нет такой возможности.

По этому поводу существует множество точек зрения. Я явно принадлежу к школе, где наследование интерфейсов более строго следует отношениям «is-a» между типами. Я, безусловно, поддерживаю более детальный подход к композиции с интерфейсами и думаю, что List<T> и его родственники, вероятно, могли бы выиграть от реализации некоторых 3-4 дополнительных интерфейсов (чтение, запись, добавление и т. Д.). Но я определенно думаю, что имя интерфейса должно описывать, что тип _can_ делать, а не то, что он _can't_ делать. Утверждения об отрицательных возможностях не имеют большого смысла для контрактов.

@drewnoakes

А пока стоит проявить прагматичность.

Я согласен. Мы там, где находимся. Если IList<T> нужно было изменить на расширение IReadOnlyList<T> тогда имеет смысл изменить ISet<T> на расширение IReadOnlySet<T> и т. Д.

Не слишком ли излишне настаивать на том, чтобы интерфейсы IReadableX<T> , IWritableX<T> и т. Д. Жили вместе с IReadOnlyX<T> ?

Есть ли у кого-нибудь авторитетный источник, почему IList<T> не расширяет IReadOnlyList<T> ?

По-видимому, это будет серьезное изменение ABI при загрузке сборок, которые были скомпилированы для старых .NET-фреймворков. Поскольку при реализации интерфейса большинство компиляторов автоматически генерируют явные реализации интерфейса, когда исходный код полагается на неявную реализацию интерфейса, если вы скомпилировали свой класс, реализующий IList<T> против BCL, у которого нет IList<T> реализуя IReadOnlyList<T> , компилятор не будет автоматически создавать явные реализации IReadOnlyList<T> . Если я правильно это понимаю: http://stackoverflow.com/a/35940240/429091

@HaloFour Поскольку List<> и HashSet<> реализуют ICollection<> и IReadOnlyCollection<> , мы уже приняли путь, где IReadOnly относится к доступу, а не возможности. Исходя из этого, имеет смысл иметь IAnything extend IReadOnlyAnything . Я согласен с тем, что IReadable лучше, чем IReadOnly но на данный момент все понимают IReadOnly в _mean_ IReadable и используют его как таковое. Фактически, я намеренно закрепляю это в своей кодовой базе, потому что, по моему мнению, наличие двух способов мышления - это большая когнитивная нагрузка, чем что-либо еще.

Мы застряли в названии, но концепция, лежащая в его основе, достаточно сильна, и я искренне хотел бы, чтобы все интерфейсы могли расширять IReadOnly будущем, как мы это делаем с конкретными классами.

@ashmind Я думаю, что это прекрасно, что ни один из методов не использует компараторы. В наборах и словарях компараторы нельзя легко заменить, потому что они определяют структуру всего объекта. Кроме того, не имеет смысла передавать средство сравнения в CaseInsensitiveStringCollection или любую коллекцию, подразумевающую определенное сравнение.

(В случае странной коллекции, которая реализует Contains(T, IEqualityComparer<T>) более эффективно, чем уже доступный метод расширения , это, вероятно, будет одноразовым методом класса. Трудно представить себе Contains(T, IEqualityComparer<T>) обычным достаточно, чтобы оказаться в специализированном интерфейсе, но даже этому ничто не мешает.)

@ jnm2

Я думаю, что это прекрасно, что ни один из методов не использует компараторы.

Чтобы уточнить, я хотел сказать, что он должен открывать компаратор, а не брать его. Поскольку каждый набор или словарь должен иметь некоторый алгоритм равенства, это могло быть показано в интерфейсе. Но я не помню свой вариант использования для этого сейчас - что-то вроде создания набора с использованием того же компаратора, что и во внешнем.

Хотя это обсуждение поднимает много интересных моментов, похоже, оно далеко отклоняется от простого и очевидного предложения, с которого началась эта ветка. И это обескураживает, потому что я действительно хотел бы, чтобы эта проблема была решена.

Как сказано в OP, неспособность поддерживать паритет между типами коллекций, когда IReadOnlyList был добавлен без IReadOnlySet, вызывает сожаление, и многие люди реализовали свои собственные версии интерфейса IReadOnlySet в качестве обходных путей (у моей собственной команды есть аналогичный обходной путь). Эти обходные интерфейсы не идеальны, потому что классы corefx не могут их реализовать. Это ключевая причина для предоставления этого в рамках: если у меня есть HashSet, я хотел бы иметь возможность использовать его как IReadOnlySet без копирования или обертывания объекта, который у меня уже есть. По крайней мере, это часто бывает желательно для производительности.

Название интерфейса должно быть IReadOnlySet. Согласованность важнее любых проблем с именами IReadOnlyXXX. Этот корабль отплыл.

Ни один из существующих интерфейсов (IReadOnlyCollection) нельзя изменить. Требования обратной совместимости для .NET не допускают подобных изменений. К сожалению, компараторы не отображаются в существующих интерфейсах IReadOnlyXXX (я тоже сталкивался с этим), но снова корабль отплыл.

Единственный вопрос, который, кажется, остается с практической точки зрения, - это вопрос между этими двумя потенциальными определениями интерфейса.

Ранее предложено @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);
}

Минимальное предложение:

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

Лично я предпочитаю это минимальное предложение, поскольку другие методы могут быть получены; в идеале была бы стандартная реализация этих методов расширения через интерфейс IReadOnlySet, поэтому разработчикам IReadOnlySet не нужно их предоставлять. Я также считаю, что это минимальное предложение больше соответствует другим минимальным интерфейсам IReadOnlyXXX.

@ aaron-meyers Единственное, что меня беспокоит, это то, что IsSubsetOf и друзья не могут быть получены _эффективным_ способом из Contains . Например, когда это две хеш-таблицы, полагаясь на Contains заставляет реализацию использовать вложенные циклы, а не сопоставление хешей.

Возможно, новый интерфейс IComparableSet<T> может содержать заданные операции.

У нас уже есть методы расширения на IEnumerable<T> для нескольких операций над наборами.

@ jnm2 Для реализации этих методов, используемых HashSet, требуется только Contains и перечисление другой коллекции (которую IReadOnlySet получит, унаследовав IReadOnlyCollection). Однако необходимо знать, что другой набор использует тот же компаратор. Возможно, стоит добавить свойство Comparer в IReadOnlySet, чтобы эти операции можно было эффективно реализовать в методах расширения. К сожалению, IReadOnlyDictionary также не предоставляет KeyComparer, но, возможно, стоит добавить его в IReadOnlySet, даже если он не совсем согласован. Есть веские причины, по которым он должен был быть включен в IReadOnlyDictionary в первую очередь, как описано здесь.

Измененное предложение будет:

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

В качестве альтернативы, Comparer может быть на отдельном интерфейсе, а реализации методов расширения для операций с множеством будут использовать эффективный маршрут, только если оба объекта реализуют интерфейс и имеют одинаковые компараторы. Тот же подход можно применить к IReadOnlyDictionary (на самом деле, возможно, они просто используют один и тот же интерфейс). Что-то вроде ISetComparable. Или рисунок из @drewnoakes может быть IComparableSetно вместо определения операторов набора он просто определяет компаратор:

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

В этом случае IReadOnlySet возвращается к простому определению 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;
    }

}

Мы можем использовать этот подход.
Использование будет выглядеть так:

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

Многие требования выполнены, IReadOnlySetминималистичен. Но GetComparer now - это метод, а не свойство. Но это хороший компромисс.

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

Только что опубликовал этот контракт с помощниками по внедрению в nuget (https://www.nuget.org/packages/ClrCoder.Collections.ReadOnlySet).
Не стесняйтесь использовать его и отправлять вопросы здесь: https://github.com/dmitriyse/ClrCoder/issues
Если он станет немного популярным (возможно, после нескольких раундов доработки), мы можем предложить это улучшение команде CoreFX.

@terrajobst можно ли

@safern есть прецедент в List<T> IReadOnly .

Планируется ли добавление в следующих выпусках .NET framework?

Когда эта работа будет завершена? Какие-нибудь сроки?

https://www.nuget.org/packages/System.Collections.Immutable/
Полный набор неизменяемых коллекций)

OK. Это было здесь слишком долго. Мне это очень нужно, и я хотел бы предложить способ, как к этому подойти.

Вместо того, чтобы разоблачать все это:

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

и заставляя клиентов внедрять эти элементы, давайте просто добавим то, что необходимо иметь:

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

и ничего более. В будущем всегда можно будет добавить больше API, если возникнет такая необходимость. Но его нельзя убрать, отсюда и это предложение.

Затем мы добавили бы этот интерфейс в список интерфейсов, расширенный ISet<T> .

Из документации

ISet<T> : этот интерфейс предоставляет методы для реализации наборов, которые представляют собой коллекции с уникальными элементами и конкретными операциями. HashSetи SortedSetколлекции реализуют этот интерфейс.

Из кода

Интерфейс ISet<T> уже имеет наш метод bool Contains<T>(T item); определенный через ICollection<T> . Также есть int Count { get; } через ICollection<T> .

Так было бы:

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

Мелкие изменения, мало обсуждаемых, огромная выгода.

Пожалуйста, позвольте этому случиться. Сообщите мне, будет ли принят и объединен такой запрос на перенос. Тогда я смогу его создать.

@karelz @terrajobst @safern @ianhays

Я нашел это обсуждение, когда работал над реальной проблемой, когда я хотел использовать набор ключей словаря для операций с наборами только для чтения. Чтобы поддержать этот случай, вот API, который я предлагаю.

Обоснование

Предлагаемый API

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

Открытые вопросы

  • Допустимо ли добавление этих методов в Dictionary<TKey, TValue>.KeyCollection учетом стоимости размера кода для обычно создаваемого универсального типа? Смотрите здесь .
  • Следует ли реализовать IReadOnlySet<T> в других Dictionary KeyCollection таких как SortedDictionary или ImmutableDictionary ?

Обновления

  • Удалено TryGetValue как его нет в ISet<T> что может предотвратить потенциально когда-либо перебазирование ISet<T> для реализации IReadOnlySet<T> .
  • Добавлен класс ReadOnlySet<T> который похож на ReadOnlyCollection<T> .

@TylerBrinkley благодарит за добавление предложения API в тему. Не могли бы вы добавить обоснование и варианты использования? Чтобы я мог пометить его как готовый к рассмотрению и позволить экспертам по API решать?

@TylerBrinkley не забудьте включить свойство EqualityComparer в IReadOnlySetинтерфейс. В настоящее время они отсутствуют в словарях и наборах в CoreFX, но это проблема.

@dmitriyse, какая польза от геттера только для свойства IEqualityComparer<T> ? Что бы вы с ним сделали?

Словари и наборы должны сообщать о своих EqualityComparers, чтобы обеспечить правильное клонирование коллекции.
К сожалению, я забыл, где обсуждался этот вопрос.

Если вы делаете клонирование, разве вы не работали бы с конкретным шрифтом? Почему интерфейс должен поддерживать IEqualityComparer<T> ?

Например, если вы установили с помощью компаратора равенства строк и нечувствительности к регистру, вы получите исключение при создании нового HashSet без указания правильного EqualityComparer. Бывают случаи, когда вы не можете знать, какой EqualityComparer используется в указанном наборе.

Это не просто клонирование. Я думаю, что гораздо более распространенным сценарием является сравнение двух наборов - мне нужно знать, что они оба используют один и тот же компаратор, чтобы реализовать оптимальное сравнение с использованием Contains. Думаю, в этой теме есть пример.

Тем не менее, я бы предпочел IReadOnlySet только с методом Contains, чем вообще без него. Было бы неплохо иметь возможность реализовать сравнение Set в общем, но не так часто, как просто нужна ссылка только для чтения на Set.

Загрузите Outlook для iOS https://aka.ms/o0ukef


От: Тайлер Бринкли [email protected]
Отправлено: четверг, 10 мая 2018 г., 6:21:52
Кому: dotnet / corefx
Копия: Аарон Мейерс; Упомянуть
Тема: Re: [dotnet / corefx] Добавьте интерфейс IReadOnlySet и сделайте так, чтобы HashSet, SortedSet реализовали его (# 1973)

Если вы делаете клонирование, разве вы не работали бы с конкретным шрифтом? Почему интерфейс должен поддерживать IEqualityComparer.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на 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 & зарезервирован = 0 , или приглушить нить https://eur01.safelinks.protection.outlook.com/?url=https%3A% 2F% 2Fgithub.com% 2Fnotifications% 2Funsubscribe-AUTH% 2FAMuQLmqboBWyHweWHSUoE1YM2OrfHZZxks5txD7wgaJpZM4E9KK- & данные = 02% 7C01% 7C% 7Cc45ea16cd3034ddd69d808d5b678ff33% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% 7C636615553141417289 & SData = hLtAXEyFNVEgWike6tMwAfUVC% 2BucyjXUDwoLOLDV5gk% 3D & зарезервирован = 0 .

Я согласен - единственный способ узнать, какие типы дубликатов вы можете найти в наборе (с учетом регистра, без учета регистра и т. Д.), - это открыть средство сравнения.

Я начинаю думать, что мое предложение не будет принято, поскольку добавление всех этих методов к Dictionary<TKey, TValue>.KeyCollection потребует значительных затрат на размер кода. См. Это обсуждение относительно добавления нового API к обычно создаваемым универсальным типам.

Я не думаю, что вы сможете поставить компаратор на IReadOnlySet .

SortedSet принимает IComparer , а HashSet принимает IEqualityComparer .

Хороший момент: средство сравнения можно рассматривать как деталь реализации, не относящуюся к общему интерфейсу.

Загрузите Outlook для iOS https://aka.ms/o0ukef


От: Кори Нельсон [email protected]
Отправлено: четверг, 10 мая 2018 г., 17:04:06
Кому: dotnet / corefx
Копия: Аарон Мейерс; Упомянуть
Тема: Re: [dotnet / corefx] Добавьте интерфейс IReadOnlySet и сделайте так, чтобы HashSet, SortedSet реализовали его (# 1973)

Я не думаю, что вы сможете разместить на IReadOnlySet средство сравнения.

SortedSet принимает IComparer, а HashSet принимает IEqualityComparer.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на 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 & зарезервирован = 0 , или приглушить нить https://eur02.safelinks.protection.outlook.com/?url=https%3A% 2F% 2Fgithub.com% 2Fnotifications% 2Funsubscribe-AUTH% 2FAMuQLu5JGLcqrpMyGWLygbCsaSQSXFgNks5txNV2gaJpZM4E9KK- & данные = 02% 7C01% 7C% 7C0ef6d84125be4c450fdc08d5b6d2b70a% 7C84df9e7fe9f640afb435aaaaaaaaaaaa% 7C1% 7C0% 7C636615938478979295 & SData = 9pnuMULuDu9HWb7un% 2FWYq6iYdjTKFsjN7nKiToaeHkk% 3D & зарезервирован = 0 .

Может быть некоторая ценность в добавлении интерфейсов IUnorderedSet и IOrderedSet , но я бы не хотел, чтобы это сорвало проталкивание IReadOnlySet .

Мне нравится предложение @pgolebiowski , но я бы сделал этот интерфейс еще более простым.

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

Это также подходит для бесчисленных наборов, таких как Интервалы (реальные). Между этим и IReadOnlySet вы потенциально могли бы вписаться в несколько других интерфейсов, таких как ICountableSet (он же IEnumerableSet ), IUnorderedSet и IOrderedSet .

Но согласитесь, что всего IReadOnlySet было бы большим улучшением по сравнению с тем, что доступно сейчас.

+1 за @NickRedwood

Он открыт уже более 3 лет. Пожалуйста, сделайте это, изменив подход. Введение изменения, упомянутого @NickRedwood, позволяет использовать все другие улучшения, которые вы обсуждаете в этой ветке. Все подходы сходятся во мнении об этой единственной тривиальной вещи. Итак, давайте добавим одну тривиальную вещь, которая является фундаментальной, а затем создадим еще одну проблему для потенциальных улучшений, которые все являются необязательными.

@TylerBrinkley @safern @karelz @terrajobst

Я не вижу особой пользы от интерфейса IReadOnlySet<T> только с методом Contains . Хотя этот метод полезен, он уже включен в интерфейс ICollection<T> который также имеет свойство IsReadOnly предназначенное для указания, поддерживаются ли методы модификации ICollection<T> . Просто реализуйте ICollection<T> и сделайте IsReadOnly return true . Единственная другая функциональность, которая, как ожидается, будет поддерживать, - это свойство Count , которое можно перечислить, и метод CopyTo который не кажется слишком ограничивающим.

Я был бы очень рад, если бы предложенный IReadOnlySet<T> был реализован на основе IReadOnlyCollection<T> - это было бы большим улучшением того, что доступно в настоящее время. ( @TylerBrinkley - предположим, вы имели в виду IReadOnlyCollection<T> а не ICollection<T> .

Думаю, я ошибаюсь в пользу более простых интерфейсов, и если бы он был для Set, то это был бы интерфейс с одним методом с методом Contains . Он имеет хорошую математическую основу и очень чистый. Однако при переоборудовании существующих классов я согласен с тем, что чем меньше переоснащение, тем лучше, и если должен был быть модернизирован только один интерфейс, то им должно быть IReadOnlySet<T> на основе IReadOnlyCollection<T> .

IReadOnlyCollection<> не имеет .Contains потому что это предотвратило бы его ковариантность.

Хороший замечание, и я исправил свой предыдущий пост, чтобы он был контравариантным. IReadOnlySet<T> должен быть инвариантным, если только не используется подход, опубликованный в начале потока, и я не уверен насчет этого.

@TylerBrinkley лично я не поклонник свойства IsReadOnly , я бы предпочел, чтобы эта информация была известна во время компиляции, а не во время выполнения. Если вы имели в виду интерфейс IReadOnlyCollection<T> я все же думаю, что для вызывающего или вызываемого было бы полезно знать, что поиск будет быстрым вместо потенциальной итерации через коллекцию, хотя я не уверен, что "набор" подразумевает это.

Наличие только метода Contains не определяет, что должен делать Set , а только то, что должен делать Collection . Я имею в виду, что Contains даже не является членом ISet а является членом ICollection которого наследуется ISet . Остальные члены ISet должны определять, что должен делать Set . Я понимаю, что большинство людей, вероятно, используют наборы исключительно для метода Contains но наборов гораздо больше.

@TylerBrinkley Но возможно ли вообще определить IReadOnlyCollection<T>.Contains(T) на этом этапе? Я предположил, что нет. Вот почему единственный вариант - ввести IReadOnlySet<T> и, когда он будет введен, убедиться, что в нем объявлено IReadOnlySet<T>.Contains(T) .

@ jnm2 говорит:

IReadOnlyCollection<> не имеет .Contains потому что это предотвратило бы его ковариантность.

Сейчас это кажется мне самой большой проблемой. Было бы странно, если бы IReadOnlySet<T> было инвариантным, когда IReadOnlyCollection<T> ковариантно. Похоже, что многие существующие IReadOnly* интерфейсы были тщательно продуманы как out T при этом доступные для записи интерфейсы обязательно инвариантны. Большинство из нас хотели бы, чтобы IReadOnlySet<T> хотя бы объявлял метод Contains(T) но это помешало бы ему быть ковариантным.

Если ICollection действительно подходящее место для определения Contains(T) , возможно ли это? Может быть, IReadOnlyProbableCollection<T>.Contains(T) который будет инвариантным и реализован Collection<T> и HashSet<T> ?

Я думаю, что предложение @ NickRedwood о ISetCharacteristic<T> кажется более чистым. Это даже имеет то преимущество, что позволяет вам не реализовывать Count что может быть полезно.

лично я не поклонник свойства IsReadOnly, я бы предпочел, чтобы эта информация была известна во время компиляции, а не во время выполнения.

Этот человек говорит хорошо, налейте ему еще вина.

@binki Я согласен там должен быть Contains метод на IReadOnlySet<T> начиная с IReadOnlyCollection<T> не включать в себя один, но я также думаю , что все остальные не-мутирует ISet<T> методы должны быть включены.

Помимо варианта использования Dictionary.KeyCollection я упоминал выше, какие еще варианты использования вы можете придумать для добавления этого интерфейса?

Хорошо, похоже, что дизайн API такой:

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

Это единственная общая часть дизайна, с которой, кажется, все могут согласиться.

Я ГОВОРЯЮ, ЧТО СЕЙЧАС МЫ ЗАКАНЧИВАЕМ ДИЗАЙН ЗДЕСЬ. Если вы хотите расширить его в будущем, сделайте это в другом выпуске. Это самая важная функциональность, которая должна быть в prod.

Кто за, кто против?

Я как @ashmind «s предложение . Было бы здорово, если бы Dictionary.Keys мог реализовать этот интерфейс, поскольку технически это IReadOnlySet<TKey> .

Не могли бы вы отметить это как api-ready-for-review ? Я только что обнаружил, что мне снова нужен этот интерфейс, а его все еще нет.

Почему это еще не реализовано?

[РЕДАКТИРОВАТЬ] Извините - мой исходный ответ был перепутан с другим API. У меня нет твердого мнения об этом, текст отредактирован. Извините за недопонимание!

Почему это еще не реализовано?

API еще не привлек внимание владельцев территорий - @safern. Я не уверен, насколько высоко он находится в списке приоритетов коллекций. Число голосов за довольно высокое.
Дело в том, что сейчас мы блокируем .NET Core 3.0, поэтому ему придется как минимум дождаться следующего выпуска, так как для 3.0 это не критично.

Я тоже удивлен, что это не предусмотрено из коробки.

Неизменяемые структуры данных могут существенно помочь в улучшении ремонтопригодности кода, но их трудно использовать без интерфейса в стандартных библиотеках, который определяет семантику и позволяет различным реализациям работать вместе.

Без этого каждая библиотека, предполагающая использовать неизменяемый набор в качестве параметра / возвращаемого значения, должна прибегать либо к использованию IEnumerable, что менее эффективно и семантически неверно, либо к использованию собственных интерфейсов / классов в своих подписях, что несовместимо с чьими-либо еще.

Мне любопытно, есть ли какие-либо обходные пути для этого, которые эффективны с точки зрения поиска Contains и избегают указания конкретного типа параметра / возвращаемого значения. Лучшее, что я мог придумать, - это использовать IEnumerable в подписи и передавать ReadOnlyDictionary.Keys, но это кажется немного неприятным, особенно в библиотеке. Поскольку Enumerable.Contains в Linq будет использовать реализацию коллекции Contains , это должно быть эффективным, хотя и совместимым, но не сообщает намерение, которое может привести к использованию менее производительных реализаций.

@ adamt06 Вы ищете ISet<T> .

@scalablecory Вы правы, с неизменной реализацией, и она есть: ImmutableHashSet<T>

Кто-нибудь знает / понимает, почему ICollectionне расширяет IReadOnlyCollection??
Я думал, что это немного связано с этой веткой. Вместо меня открываю новую тему. Может, я что-то недопонимаю.

Еще одна мысль, но совершенно не по теме, А почему в ReaOnlyCollection нет:

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

Что-то связанное с критическими изменениями, но подробностей я не помню.

В ReadOnlyCollection есть такие вещи:
https://docs.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.readonlycollection-1.contains
https://docs.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.readonlycollection-1.copyto

О, я вижу, вы имели в виду, что в IReadOnlyCollection их нет. Это потому, что в противном случае интерфейс не мог бы быть ковариантным .

Я не уверен, но, вероятно, это связано с явной реализацией интерфейса, вызывающей проблемы с обратной совместимостью. Я понимаю, почему обновление существующих интерфейсов для наследования от других интерфейсов не является проблемой, но я не понимаю, почему создание нового интерфейса IReadOnlySet<T> и реализация HashSet будет проблемой.

OK. Но до сих пор тоже не вижу ICollectionне должно быть:
c# ICollection<T> : IReadOnlyCollection<out T>
Почему коллекция для чтения / записи не должна быть расширением коллекции только для чтения?

@ generik0

OK. Но все равно ICollection тоже не видеть не должно:

ICollection<out T> : IReadOnlyCollection<out T>

Почему коллекция для чтения / записи не должна быть расширением коллекции только для чтения?

Во-первых, обратите внимание, что невозможно объявить ICollection<out T> потому что ICollection<T> имеет член .Add(T item) .

Во-вторых, обратите внимание, что ICollection<T> отображается в .net-2.0, а IReadOnlyCollection<out T> отображается в .net-4.5 . Если вы скомпилируете код как реализацию ICollection<T> а затем ICollection<T> будет изменен для реализации нового интерфейса, любые существующие скомпилированные двоичные файлы сломаются во время выполнения, потому что все, что реализует только ICollection<T> , не будет иметь своего IReadOnlyCollection<T> слоты, заполняемые (возможно, автоматически) компилятором. Это проблема совместимости, которая помешала ICollection<T> реализовать IReadOnlyCollection<T> .

Я не думаю, что это четко решено в этом SO-ответе, но для этого есть SO: https://stackoverflow.com/a/14944400 .

@binki "ICollection""исправлено в ICollection, спасибо, что указали на мою опечатку ..
DotNetStandard я net461. И здесь должны быть изменения. netstandard 2+

Повторюсь ...
Почему коллекция для чтения / записи не должна быть расширением коллекции только для чтения?

И почему нужно преобразовывать коллекцию, например, в «ToArray ()», чтобы отображать меньше, если это так?

И, конечно же, вы можете добавлять новые интерфейсы в более высокие версии, не нарушая при этом ничего. В ICollection уже реализованы методы IReadOnlyCollection. Заметив, должно сломаться, ...: - /

@ generik0 Похоже, это не нарушит совместимость источников, о чем вы думаете [не думал; это было бы], но это нарушило бы двоичную совместимость, которая работает с таблицами IL, а не с C #.

Хорошо @ jnm2, спасибо.
Я прекращаю свою напыщенную речь, просто думаю, что это плохая архитектура. Спасибо всем за то, что выслушали / объяснили :-)

@ jnm2

@ generik0 Похоже, это не нарушит совместимость исходного кода, о чем вы думаете, но нарушит двоичную совместимость, которая работает с таблицами IL, а не с C #.

В целях придирки (извините), это также нарушит совместимость исходного кода, если ваш источник явно реализовал ICollection<T> из более ранних версий .net-4.5. Класс начал бы не компилироваться из-за неспособности явно реализовать IReadOnlyCollection<T>.Count . Но двоичная совместимость важнее, потому что это помешает вам нацеливаться на широкий диапазон версий .net (в любом случае вам понадобятся разные двоичные файлы для работы на ≥.net-4.5, чем на <.net-2, и вы бы должны быть нацелены на оба, а не на старую структуру).

Мне интересно, может ли с добавлением поддержки реализации интерфейса по умолчанию в C # 8 ICollection<T> реализовать IReadOnlyCollection<T> для .NET Core 3.0+.

Может быть нужно расширение, тогда это тот же сборник. то есть, если вам нужно получить список или коллекцию только в реаадонликоллекцию ... Есть мысли?

[отредактировал @ jnm2 comment]
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 ... или (IReadOnlyCollection<T>)enumerable вместо вызова этого метода?

@ jnm2 omg . Ты прав. Иногда вы не видите четкой картины и усложняете вещи !!!

Я бы все равно назвал расширение, просто для простоты ;-)
Спасибо друг....

Какой здесь статус? Мне бы очень хотелось, чтобы этот интерфейс был в стандартной библиотеке, и я бы реализовал его в HashSet <>.

Похоже, что лучше всего подождать еще некоторое время, пока предложение Shapes будет (надеюсь) реализовано. Тогда вы сможете создать любую группу фигур для представления типов наборов, которые вам нужны, и предоставить реализации, чтобы существующие наборы, такие как HashSet<T> соответствовали им.

Тогда могла бы появиться общественная библиотека для выполнения именно этого, охватывающая все различные типы множеств (интенсиональные (предикаты) и экстенсиональные (перечисленные), упорядоченные, частично упорядоченные, неупорядоченные, счетные, счетно бесконечные, бесчисленные бесконечные, возможно, математические области, такие как Rational , Натуральные числа и т. Д.) Как разные формы, со всеми методами объединения, пересечения, мощности элементов, определенных для этих наборов и между ними.

Для меня это звучит примерно через 5 лет. Почему простое изменение, которое можно реализовать за день, должно ждать появления какой-то в 1000 раз большей, еще не указанной функции, которая может даже не произойти?

Для меня это звучит примерно через 5 лет. Почему простое изменение, которое можно реализовать за день, должно ждать появления какой-то в 1000 раз большей, еще не указанной функции, которая может даже не произойти?

Я просто отвечаю на отсутствие прогресса по IReadOnlySet<T> - этот вопрос уже открыт 4 года назад.

Путь Microsoft: самые простые и полезные вещи занимают десятилетия. Это сногсшибательно. 5 лет и больше.

Что еще смешнее, так это то, что у них это здесь
https://docs.microsoft.com/en-us/dotnet/api/microsoft.sqlserver.management.sdk.sfc.ireadonlyset-1

@terrajobst мысли?

  • Обычно мы считаем, что это действительно дыра в нашей иерархии интерфейсов. Мы предлагаем сосредоточиться только на добавлении интерфейса и его реализации на существующих реализациях набора. Мы не можем заставить ISet<T> extend IReadOnlySet<T> (по той же причине, по которой мы не могли сделать для других изменяемых интерфейсов). Мы можем добавить ReadOnlySet<T> на более позднем этапе. Мы должны дважды проверить, что IReadOnlySet<T> - это всего лишь ISet<T> минус изменяемые API.
  • Мы также должны реализовать IReadOnlySet<T> на ImmutableSortedSet<T> и ImmutableHashSet<T> (и их конструкторы).
  • Мы должны просканировать другие реализации 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> {
     }
 }

Сделай это, смею тебя!

@terrajobst

Мы не можем заставить ISet<T> extend IReadOnlySet<T> (по той же причине, по которой мы не могли сделать для других изменяемых интерфейсов).

Верно ли это даже для методов интерфейса по умолчанию? Означает ли это, что https://github.com/dotnet/corefx/issues/41409 следует закрыть?

@terrajobst

Мы не можем заставить ISet<T> extend IReadOnlySet<T> (по той же причине, по которой мы не могли сделать для других изменяемых интерфейсов).

Верно ли это даже для методов интерфейса по умолчанию? Означает ли это, что dotnet / corefx # 41409 следует закрыть?

Мы это обсуждали. Раньше мы думали, что эти модули DIM будут работать, но когда мы подошли к решению, мы пришли к выводу, что это обычно приводит к алмазному осколку, что приводит к неоднозначному совпадению. Однако недавно это было оспорено, поэтому я думаю, что мне нужно записать это и убедиться, что он действительно работает или не работает.

@terrajobst / @danmosemsft Кто-нибудь был назначен на это?

И @terrajobst, чтобы прояснить работу, которую мы хотим достичь:
`` ''
Мы также должны реализовать IReadOnlySetна ImmutableSortedSetи ImmutableHashSet(и их строители)
Мы должны сканировать другие реализации ISet.
`` ''
Помимо реализации вышеуказанных интерфейсов в HashSet, SortedSet.

Сканирующее существо просто ищет что-нибудь и поднимает это, если это кажется сомнительным.

Если это все еще доступно, мне было бы интересно

@Jlalond нет, назначено вам. Спасибо за предложение.

@danmosemsft @terrajobst
Просто обновление. Я работаю над этим, добавил интерфейс в частное ядро ​​Lib, просто спотыкаясь о том, что collections.generic и immutable подхватили это.

Последний вопрос, если ты не знаешь, Дэн, нужно ли мне для этого вносить какие-либо изменения в Mono? Я не понимаю, где заканчивается corefx и начинается моно. Так что, если вы знаете, это может спасти меня от независимых исследований

@Jlalond, вам не нужно вносить изменения в Mono. Одна из причин переноса среды выполнения Mono в это репозиторий состоит в том, чтобы упростить использование одних и тех же библиотек с CoreCLR или средой выполнения Mono. Есть только небольшая часть основной библиотеки, которая расходится:
coreclrsrcSystem.Private.CoreLib против mononetcoreSystem.Private.CoreLib. (Большая часть основной библиотеки используется совместно с librarySystem.Private.CoreLib). Так что, если вы не коснетесь этого - вы не пострадаете.

@danmosemsft Спасибо за разъяснения, я надеюсь, что это будет сделано в ближайшее время.

Привет, @danmosemsft, продолжай это. CoreLib строится в сборках src. Я вижу упомянутые изменения. Однако сборки ref, похоже, не обнаруживают никаких изменений. Это все, что меня сдерживает, но я не могу найти никакой информации в документации. Любые люди или указатели, которые вы можете дать мне, чтобы я мог это сделать (я имею в виду, 5 лет спустя)

Адресс # 32488.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги