K>не привлекает в виду получения клиентом прямого доступа к коллекции.
K>Существует ли простой способ сказать то, что свойство Items — возвращает readonly коллекцию? Существует ли такой интерфейс для HashSet<T>?
K>Делать правильно (реализовывать IEnumerable) привлекает, но — лень
public IEnumerable<SomeObject> Items {get {return _items.AsEnumerable(); } }
Здравствуйте, necrostaz, Вы писали:
1. HashSet<T> и так реализует IEnumerable<T>. Совершенно незачем заниматься тавтологией:
public IEnumerable<SomeObject> Items {get {return _items; } }
2. Такая реализация — небезопасна, т.к. клиент всё же может выполнить даункаст и получить доступ к коллекции.
Вот простой и надежный способ индивидуальной защиты:
public IEnumerable<SomeType> Items
{
get
{
foreach (var item in _items) yield return item;
}
}
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>2. Такая реализация — небезопасна, т.к. клиент всё же может выполнить даункаст и получить доступ к коллекции.
как раз для этого AsEnumerable и предназначен
Здравствуйте, Sinclair, Вы писали:
S>2. Такая реализация — небезопасна, т.к. клиент всё же может выполнить даункаст и получить доступ к коллекции. S>Вот простой и надежный способ индивидуальной защиты:
S>public IEnumerable<SomeType> Items
S>{
S> get
S> {
S> foreach (var item in _items) yield return item;
S> }
S>}
Свойство, каждый раз возвращающее новое значение — очень плохо:
var x = o.Items;
…
var y = o.Items;
…
if(x == y) {
// сюда не попадём.
}//if
Это решение для лентяев, которые не могут написать нормальную, удобную обёртку а делов-то на десять минут.
Или же не догадываются переделать свойство на метод.
Работать с такими "свойствами" крайне не удобно. Встречаешь, приходится смотреть код, а там: ёшкин кот! xyz!
The Enumerable.AsEnumerable<TSource> method has no effect other than to change the compile-time type of source from a type that implements IEnumerable<(Of <(T>)>) to IEnumerable<(Of <(T>)>) itself.
то есть возвращается из него ровно то, что передано, и предназначен метод только для изменения статического типа у аргумента (например, когда с экземпляром IQueryable<> надо паботать как с IEnumerable<>) например в linq-выражениях или при вызове методов-расширений.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, necrostaz, Вы писали:
N>синклер прав, но тогда проще сделать .ToArray()
Ну, если вам не жалко памяти, то как бы в путь. Ну и если риск последующей рассинхронизации не пугает, то всё в порядке.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
в связи с вышесказанным _FRED_ .ToArray() тоже делать нельзя, либо делать кеширование с подсчетом изменений(а проще реализовать интерфейс) лию\бо делать метод а не свойство
Здравствуйте, Sinclair, Вы писали: S>Ну, если вам не жалко памяти, то как бы в путь. Ну и если риск последующей рассинхронизации не пугает, то всё в порядке.
а разница между foreach c yield и Array.Copy(...) ? )
Здравствуйте, necrostaz, Вы писали:
N>Здравствуйте, Sinclair, Вы писали: S>>Ну, если вам не жалко памяти, то как бы в путь. Ну и если риск последующей рассинхронизации не пугает, то всё в порядке.
N>а разница между foreach c yield и Array.Copy(...) ? )
Здравствуйте, _FRED_, Вы писали:
_FR>Это решение для лентяев, которые не могут написать нормальную, удобную обёртку а делов-то на десять минут. _FR>Или же не догадываются переделать свойство на метод.
_FR>Работать с такими "свойствами" крайне не удобно. Встречаешь, приходится смотреть код, а там: ёшкин кот! xyz!
Ок, давайте улучшим реализацию:
private EnumeratorAdapter<SomeType> _itemsWrapper;
private IEnumerable<SomeType> Items
{
get
{
return _itemsWrapper ?? (_itemsWrapper = new EnumeratorAdapter<SomeType>(GetItemsEnumerator));
}
}
private IEnumerator<SomeType> GetItemsEnumerator()
{
foreach (var b in _items)
{
yield return b;
}
}
Это в предположении, что поблизости валяется вот такой очевидный адаптер:
public class EnumeratorAdapter<T> : IEnumerable<T>
{
private Func<IEnumerator<T>> _getEnumerator;
public EnumeratorAdapter(Func<IEnumerator<T>> getEnumerator)
{
if (getEnumerator == null)
throw new ArgumentNullException("getEnumerator");
_getEnumerator = getEnumerator;
}
public IEnumerator<T> GetEnumerator()
{
return _getEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
Здравствуйте, necrostaz, Вы писали:
N>а разница между foreach c yield и Array.Copy(...) ? )
Скорее вопрос в том, что между ними общего.
ToArray каждый раз выделит массив размером с количество элементов. Как верно подметил _FRED, это еще и приведет к тому, что значения свойства будут всякий раз отличаться.
А yield return имеет ленивую семантику, и всего лишь создает один экземпляр маленького класса — обертки над тем IEnumerable, который перебирается во внутреннем foreach.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, necrostaz, Вы писали:
N>>а разница между foreach c yield и Array.Copy(...) ? ) S>Скорее вопрос в том, что между ними общего. S>ToArray каждый раз выделит массив размером с количество элементов. Как верно подметил _FRED, это еще и приведет к тому, что значения свойства будут всякий раз отличаться. S>А yield return имеет ленивую семантику, и всего лишь создает один экземпляр маленького класса — обертки над тем IEnumerable, который перебирается во внутреннем foreach.
Здравствуйте, Sinclair, Вы писали:
_FR>>Это решение для лентяев, которые не могут написать нормальную, удобную обёртку а делов-то на десять минут. _FR>>Или же не догадываются переделать свойство на метод. _FR>>Работать с такими "свойствами" крайне не удобно. Встречаешь, приходится смотреть код, а там: ёшкин кот! xyz! S>Ок, давайте улучшим реализацию:
Не-а. В классе очень не сложно получить количество элементов. Почему "снаружи" имеет смысл требовать ради этого пройтись по всей коллекции? Во-вторых, мало ли пригодиться передать это свойство куда-то, что ждёт именно коллекцию? Такой адаптером круто урезает возможности для пользователя класса, без совершенно видимых на это причин. В частности, не позволяя сериализовать данные.
Нет, надо использовать нормальный read-only wrapper для ICollection<T>. Например такой (за название извиняюсь, ничего лучше в гову не пришло):
#region Using's
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
#endregion Using's
[Serializable]
[DebuggerDisplay("Count = {Count}")]
public class ReadOnlyCollection2<TItem> : ICollection<TItem>, IEnumerable<TItem>, IEnumerable
{
#region Fields
private readonly ICollection<TItem> items;
#endregion Fields
#region Constructor(s)
public ReadOnlyCollection2(ICollection<TItem> items) {
if(items == null) {
throw new ArgumentNullException("items");
}//ifthis.items = items;
}
#endregion Constructor(s)
#region Properties
protected ICollection<TItem> Items {
[DebuggerStepThrough]
get { return items; }
}
#endregion Properties
#region Methods
private Exception CreateReadOnlyException() {
return new NotSupportedException("Collection is read-only!");
}
#endregion Methods
#region ICollection<TItem> Members
void ICollection<TItem>.Add(TItem item) {
throw CreateReadOnlyException();
}
void ICollection<TItem>.Clear() {
throw CreateReadOnlyException();
}
public bool Contains(TItem item) {
return Items.Contains(item);
}
public void CopyTo(TItem[] array, int arrayIndex) {
Items.CopyTo(array, arrayIndex);
}
public int Count {
[DebuggerStepThrough]
get { return Items.Count; }
}
bool ICollection<TItem>.IsReadOnly {
[DebuggerStepThrough]
get { return true; }
}
bool ICollection<TItem>.Remove(TItem item) {
throw CreateReadOnlyException();
}
#endregion ICollection<TItem> Members
#region IEnumerable<TItem> Members
public IEnumerator<TItem> GetEnumerator() {
return Items.GetEnumerator();
}
#endregion IEnumerable<TItem> Members
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
#endregion IEnumerable Members
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали: _FR>Нет, надо использовать нормальный read-only wrapper для ICollection<T>. Например такой (за название извиняюсь, ничего лучше в гову не пришло):
Да уж. По части именования стандартных классов команде из Редмонда просто большой привет.
Мучаюсь вопросом: почему они не назвали своё творение ReadOnlyList?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, _FRED_, Вы писали: _FR>>Нет, надо использовать нормальный read-only wrapper для ICollection<T>. Например такой (за название извиняюсь, ничего лучше в гову не пришло): S>Да уж. По части именования стандартных классов команде из Редмонда просто большой привет. S>Мучаюсь вопросом: почему они не назвали своё творение ReadOnlyList?
может потому что ReadOnlyList уже есть ? ) правда в другом наймспейсе и приватный
Здравствуйте, Sinclair, Вы писали:
_FR>>Нет, надо использовать нормальный read-only wrapper для ICollection<T>. Например такой (за название извиняюсь, ничего лучше в гову не пришло): S>Да уж. По части именования стандартных классов команде из Редмонда просто большой привет. S>Мучаюсь вопросом: почему они не назвали своё творение ReadOnlyList?
В ObjectModel есть Collection<> (потому что в Generics уже есть List<>). Ну а это по-аналогии В принциме, ИМХО, самым правильным и непротиворечивым было бы написать ReadOnlyHashSet<> — тогда и с именем не надо мучаться
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>>>Нет, надо использовать нормальный read-only wrapper для ICollection<T>. Например такой (за название извиняюсь, ничего лучше в гову не пришло): S>>Да уж. По части именования стандартных классов команде из Редмонда просто большой привет. S>>Мучаюсь вопросом: почему они не назвали своё творение ReadOnlyList?
_FR>В принциме, ИМХО, самым правильным и непротиворечивым было бы написать ReadOnlyHashSet<> — тогда и с именем не надо мучаться
Работы — чуть, но если надо, то оказывается очень полезно.
#region Using's
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
#endregion Using's
[Serializable]
[DebuggerDisplay("Count = {Count}")]
public class ReadOnlyHashSet<TItem> : ReadOnlyCollection2<TItem>
{
#region Constructor(s)
public ReadOnlyHashSet(HashSet<TItem> items) : base(items) {
}
#endregion Constructor(s)
#region Properties
protected new HashSet<TItem> Items {
[DebuggerStepThrough]
get { return (HashSet<TItem>)base.Items; }
}
public IEqualityComparer<TItem> Comparer {
[DebuggerStepThrough]
get { return Items.Comparer; }
}
#endregion Properties
#region Methods
public void CopyTo(TItem[] array) {
Items.CopyTo(array);
}
public void CopyTo(TItem[] array, int arrayIndex, int count) {
Items.CopyTo(array, arrayIndex, count);
}
public bool IsProperSubsetOf(IEnumerable<T> other) {
return Items.IsProperSubsetOf(other);
}
public bool IsProperSupersetOf(IEnumerable<T> other) {
return Items.IsProperSubsetOf(other);
}
public bool IsSubsetOf(IEnumerable<T> other) {
return Items.IsSubsetOf(other);
}
public bool IsSupersetOf(IEnumerable<T> other) {
return Items.IsSupersetOf(other);
}
public bool Overlaps(IEnumerable<T> other) {
return Items.Overlaps(other);
}
public bool SetEquals(IEnumerable<T> other) {
return Items.SetEquals(other);
}
public new HashSet<TItem>.Enumerator GetEnumerator() {
return Items.GetEnumerator();
}
#endregion Methods
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>В классе очень не сложно получить количество элементов. Почему "снаружи" имеет смысл требовать ради этого пройтись по всей коллекции? Во-вторых, мало ли пригодиться передать это свойство куда-то, что ждёт именно коллекцию? Такой адаптером круто урезает возможности для пользователя класса, без совершенно видимых на это причин. В частности, не позволяя сериализовать данные.
И, самое главное, поиск элемента в "нормальном read-only wrapper-е" будет, скорее всего, более производительный чем в IEnumerable.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Help will always be given at Hogwarts to those who ask for it.
На мой взгляд это плохо. Интерфейс здесь определяет один совершенно конкретный контракт. Реализация занимается своими совершенно посторонними вещами. Ошибка проявится только в run-time. В общем, улучшения не видно.
Здравствуйте, Кэр, Вы писали:
Кэр>Здравствуйте, _FRED_, Вы писали:
Кэр>На мой взгляд это плохо. Интерфейс здесь определяет один совершенно конкретный контракт. Реализация занимается своими совершенно посторонними вещами. Ошибка проявится только в run-time. В общем, улучшения не видно.
Что именно плохо? ИМХО, ты просто не знаешь про Optional Feature Pattern и то, как он используется в коллекциях фреймворка.
Так же можно ознакомиться с документацией к обозначенным методам (здесь, здесь и здесь), и увидеть, что реализация полностью удовлетворяет спецификации интерфейса.
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Что именно плохо? ИМХО, ты просто не знаешь про Optional Feature Pattern и то, как он используется в коллекциях фреймворка.
Плохо то, что если я когда-нибудь получу этот экземпляр как интерфейс, то контракт бесполезен. Если коллекция не поддерживает метода Add, то не должно быть и вариантов вызова этого метода. "Паттерны", которые делают upcast класса опасными — идут в лес. Меньше всего, что ожидает инжинер в ООП языке — что нельзя кастовать к паренту, чтобы не получить изменение поведения. Явная реализация интерфейсов — это исключительно тяжкое наследие COM'а. Это скорее бага языка, чем фича.
_FR>Так же можно ознакомиться с документацией к обозначенным методам (здесь, здесь и здесь), и увидеть, что реализация полностью удовлетворяет спецификации интерфейса.
Да, коллекции спроектированы в .Net отвратительно (вы каждый раз проверяете, а не является ли коллекция ReadOnly перед тем, как с ней пообщаться?). Это не повод говорить, что решения построенные с активным использованием этих "дизайн" изобретений — становятся лучше, чем они есть на самом деле.
Здравствуйте, Кэр, Вы писали:
_FR>>Что именно плохо? ИМХО, ты просто не знаешь про Optional Feature Pattern и то, как он используется в коллекциях фреймворка.
Кэр>Плохо то, что если я когда-нибудь получу этот экземпляр как интерфейс, то контракт бесполезен. Если коллекция не поддерживает метода Add, то не должно быть и вариантов вызова этого метода. "Паттерны", которые делают upcast класса опасными — идут в лес. Меньше всего, что ожидает инжинер в ООП языке — что нельзя кастовать к паренту, чтобы не получить изменение поведения. Явная реализация интерфейсов — это исключительно тяжкое наследие COM'а. Это скорее бага языка, чем фича.
Читай спецификацию интерфейса — не зря я ссылку дал: каждый, кто вызывает Add и не хочет получить в ответ NotSupported должен сначала спросить IsReadOnly. Таков контракт интерфейса ICollection. Теперь пока жи мне и доступно растолкуй, в каком месте я его нарушаю.
И ни апкасты не нужны. Если тебе кажется, что у тебя есть модель коллекций не хуже, чем во фреймворке, твоё личное дело. Какой это имеет отношение к обсуждаемому здесь вопросу?
_FR>>Так же можно ознакомиться с документацией к обозначенным методам (здесь, здесь и здесь), и увидеть, что реализация полностью удовлетворяет спецификации интерфейса.
Кэр>Да, коллекции спроектированы в .Net отвратительно (вы каждый раз проверяете, а не является ли коллекция ReadOnly перед тем, как с ней пообщаться?). Это не повод говорить, что решения построенные с активным использованием этих "дизайн" изобретений — становятся лучше, чем они есть на самом деле.
В ссылке, что я дал, есть объяснение, почему сделано именно так Если ты такой умный, то доказывай свою позицию авторам фреймворка (это можно сделать прям в коментарии к блогу).
Мы сейчас обсуждаем не проектирование коллекций а то, как жить с существующим. Ещё раз: в каком месте я нарушил контракт ICollection<>?
... << RSDN@Home 1.2.0 alpha 4 rev. 1111>>
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, necrostaz, Вы писали:
N>а разница между foreach c yield и Array.Copy(...) ? )
Разница в том, что в случае с foreach нельзя будет изменять элементы в итерируемой коллекции. т.к. большинство енумераторов не допускают изменения коллекции в процессе енумерации
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Здравствуйте, TK, Вы писали:
TK>…большинство енумераторов не допускают изменения коллекции в процессе енумерации
Даже больше: енумераторы, не следящие за изменением коллекции при обходе, нарушают спецификацию:
An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call to MoveNext or Reset throws an InvalidOperationException. If the collection is modified between MoveNext and Current, Current returns the element that it is set to, even if the enumerator is already invalidated.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Sinclair, Вы писали:
S>2. Такая реализация — небезопасна, т.к. клиент всё же может выполнить даункаст и получить доступ к коллекции. S>Вот простой и надежный способ индивидуальной защиты: S>
/// <summary>
/// A readonly version of BindingList(Of T)
/// </summary>
/// <typeparam name="T">Type of item contained in the list.</typeparam>
/// <remarks>
/// This is a subclass of BindingList(Of T) that implements
/// a readonly list, preventing changing, adding and removing of items
/// from the list.
/// </remarks>public class ReadOnlyBindingList<T> : BindingList<T>
{
private const string MSG_NOT_ALLOWED_IN_READONLY_COLLECTION = "Not allowed in read-only collection";
/// <summary>
/// Creates an instance of the object.
/// </summary>public ReadOnlyBindingList(BindingList<T> list) : base(list)
{
this.RaiseListChangedEvents = false;
base.AllowEdit = false;
base.AllowRemove = false;
base.AllowNew = false;
this.RaiseListChangedEvents = true;
list.ListChanged += new ListChangedEventHandler(ReadOnlyBindingList_ListChanged);
}
private void ReadOnlyBindingList_ListChanged(object sender, ListChangedEventArgs e)
{
OnListChanged(e);
}
/// <summary>
/// Prevents clearing the collection.
/// </summary>protected override void ClearItems()
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}
/// <summary>
/// Prevents insertion of items into the collection.
/// </summary>protected override object AddNewCore()
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}
/// <summary>
/// Prevents insertion of items into the collection.
/// </summary>
/// <param name="index">Index at which to insert the item.</param>
/// <param name="item">Item to insert.</param>protected override void InsertItem(int index, T item)
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}
/// <summary>
/// Prevents removing of items into the collection
/// </summary>
/// <param name="index">Index of the item to remove.</param>protected override void RemoveItem(int index)
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}
/// <summary>
/// Prevents replacing the item at the specified index with the
/// specified item
/// </summary>
/// <param name="index">Index of the item to replace.</param>
/// <param name="item">New item for the list.</param>protected override void SetItem(int index, T item)
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}
/// <summary>
/// Gets a value indicating whether items in the list can be edited.
/// </summary>public new bool AllowEdit { get { return false; } }
/// <summary>
/// Gets a value indicating whether you can add items to the list using the AddNew method.
/// </summary>public new bool AllowNew { get { return false; } }
/// <summary>
/// Gets a value indicating whether you can remove items from the collection.
/// </summary>public new bool AllowRemove { get { return false; } }
}
Здравствуйте, _FRED_, Вы писали:
_FR>Читай спецификацию интерфейса — не зря я ссылку дал: каждый, кто вызывает Add и не хочет получить в ответ NotSupported должен сначала спросить IsReadOnly. Таков контракт интерфейса ICollection. Теперь пока жи мне и доступно растолкуй, в каком месте я его нарушаю.
Контрактом класса является в гораздо больше части то что проверяется автоматически. В этом плане комментарии бесполезны абсолютно.
Далее, проверять IsReadOnly в рантайме в 98% бесполезно. Код чаще всего будет выглядеть вот так:
if (collection.IsReadOnly)
{
//get back in time and find another way to solve task
}
Потому как если коллекция ReadOnly — то проверка просто не нужна. Потому что ситуация, когда коллекция сейчас ReadOnly, а через 5 минут — уже нет — настолько редкая, что мне, например, ни разу не встречалась. И разумных примеров в голову тоже не приходит.
_FR>И ни апкасты не нужны. Если тебе кажется, что у тебя есть модель коллекций не хуже, чем во фреймворке, твоё личное дело. Какой это имеет отношение к обсуждаемому здесь вопросу?
Прямое. Я утверждаю, что это решение не является улучшением ситуации. Если уж нужно действительно гарантировать сохраность коллекции — то более жизнеспособным решением является foreach/yield return, либо Array.Copy.
Кэр>>Да, коллекции спроектированы в .Net отвратительно (вы каждый раз проверяете, а не является ли коллекция ReadOnly перед тем, как с ней пообщаться?). Это не повод говорить, что решения построенные с активным использованием этих "дизайн" изобретений — становятся лучше, чем они есть на самом деле.
_FR>В ссылке, что я дал, есть объяснение, почему сделано именно так Если ты такой умный, то доказывай свою позицию авторам фреймворка (это можно сделать прям в коментарии к блогу). _FR>Мы сейчас обсуждаем не проектирование коллекций а то, как жить с существующим. Ещё раз: в каком месте я нарушил контракт ICollection<>?
Я тоже говорю, что нужно смотреть как нужно жить с существующими коллекциями. Так как вы предлагаете проблему лучше не решать. А контракт у ICollection<> не нарушен. Нарушен здравый смысл.