IEnumerable vs IReadonlyCollection vs ReadonlyCollection для отображения элемента списка

Я потратил немало часов на размышления о предмете разоблачения членов списка. В подобном вопросе мой, Джон Скит дал отличный ответ. Пожалуйста, не стесняйтесь смотреть.

ReadOnlyCollection или IEnumerable для экспонирования коллекций участников?

Я обычно довольно параноидален для разоблачения списков, особенно если вы разрабатываете API.

Я всегда использовал IEnumerable для отображения списков, так как он вполне безопасен и дает такую ​​гибкость. Позвольте мне привести пример здесь

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IEnumerable<WorkItem> WorkItems
    {
        get
        {
            return this.workItems;
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

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

По этой причине многие разработчики используют ReadOnlyCollection для раскрытия члена. Это вполне безопасно, поскольку он никогда не может быть возвращен в список. Для меня я предпочитаю IEnumerable, поскольку он обеспечивает большую гибкость, должен ли я когда-либо реализовать что-то отличное от списка.

Я придумал новую идею, которую мне больше нравится. Использование IReadOnlyCollection

public class Activity
{
    private readonly IList<WorkItem> workItems = new List<WorkItem>();

    public string Name { get; set; }

    public IReadOnlyCollection<WorkItem> WorkItems
    {
        get
        {
            return new ReadOnlyCollection<WorkItem>(this.workItems);
        }
    }

    public void AddWorkItem(WorkItem workItem)
    {
        this.workItems.Add(workItem);
    }
}

Я чувствую, что это сохраняет некоторую гибкость IEnumerable и инкапсулируется довольно красиво.

Я разместил этот вопрос, чтобы внести свой вклад в мою идею. Вы предпочитаете это решение для IEnumerable? Как вы думаете, лучше использовать конкретное возвращаемое значение ReadOnlyCollection? Это довольно дискуссия, и я хочу попытаться выяснить, какие преимущества/недостатки мы все можем придумать.

Заранее благодарим за вход.

ИЗМЕНИТЬ

Прежде всего, спасибо всем вам за то, что вы так много способствовали обсуждению. Я, конечно, узнал тонну от каждого и хотел бы искренне поблагодарить вас.

Я добавляю несколько дополнительных сценариев и информации.

Есть некоторые распространенные ошибки с IReadOnlyCollection и IEnumerable.

Рассмотрим пример ниже:

public IReadOnlyCollection<WorkItem> WorkItems
{
    get
    {
        return this.workItems;
    }
}

Приведенный выше пример может быть возвращен в список и изменен, даже если интерфейс только для чтения. Интерфейс, несмотря на то, что тезка не гарантирует неизменность garuantee. Это зависит от вас, чтобы обеспечить непреложное решение, поэтому вы должны вернуть новый ReadOnlyCollection. Создавая новый список (копия по существу), состояние вашего объекта безопасно и звучит.

Richiban говорит, что это лучше всего в его комментарии: интерфейс гарантирует только то, что что-то может сделать, а не то, что он не может сделать

Ниже приведен пример.

public IEnumerable<WorkItem> WorkItems
{
    get
    {
        return new List<WorkItem>(this.workItems);
    }
}

Вышеупомянутые могут быть заброшены и мутированы, но ваш объект по-прежнему остается неизменным.

Другим вне оператора коробки будут классы коллекции. Рассмотрим следующее:

public class Bar : IEnumerable<string>
{
    private List<string> foo;

    public Bar()
    {
        this.foo = new List<string> { "123", "456" };
    }

    public IEnumerator<string> GetEnumerator()
    {
        return this.foo.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

В приведенном выше классе могут быть методы для мутирования foo так, как вы хотите, но ваш объект никогда не может быть передан в список любого рода и мутирован.

Carsten Führmann дает фантастический пример о операторах return return в IEnumerables.

Еще раз спасибо.

Ответ 1

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

Все о неизменной коллекции... До того, как появились только непременные и расширенные массивы, была огромная задача, поэтому .net решила включить в рамки что-то другое, изменяемую коллекцию, которая реализует уродливые вещи для вас, но IMHO они не дали вам правильного направления для неизменяемых, которые чрезвычайно полезны, особенно в сценарии с высоким concurrency, где совместное использование изменяемого материала всегда является PITA.

Если вы проверяете другие сегодняшние языки, например objective-c, вы увидите, что на самом деле правила полностью инвертированы! Они довольно часто обмениваются неизменяемой коллекцией между разными классами, другими словами интерфейс демонстрирует просто неизменяемость, а внутри они используют изменчивую коллекцию (да, конечно, они есть), вместо этого они выставляют правильные методы, если они хотят, чтобы посторонние меняли коллекцию ( если класс является классом с сохранением состояния).

Итак, этот небольшой опыт, который у меня есть с другими языками, заставляет меня думать, что список .net настолько мощный, но неизменная коллекция была там по какой-то причине:)

В этом случае не нужно помогать вызывающему интерфейсу, чтобы он не менял весь код, если вы меняете внутреннюю реализацию, как это происходит с IList vs List, но с IReadOnly * вы защищаете себя, своего класса, к тому, чтобы его использовали неправильно, чтобы избежать ненужного кода защиты, кода, который иногда вы также не могли написать (в прошлом в каком-то фрагменте кода мне приходилось возвращать клон полного списка, чтобы избежать эта проблема).

Ответ 2

Один из важных аспектов, по-видимому, отсутствует в ответах:

Когда возвращается IEnumerable<T> вызывающему, они должны учитывать возможность того, что возвращаемый объект является "ленивым потоком", например. сборник, построенный с "доходностью возврата". То есть ограничение производительности для создания элементов IEnumerable<T> может быть оплачено вызывающим абонентом для каждого использования IEnumerable. (Инструмент производительности "Resharper" на самом деле указывает на это как запах кода.)

В отличие от этого, IReadOnlyCollection<T> сигнализирует вызывающему, что не будет никакой ленивой оценки. (Свойство Count, в отличие от метода Count IReadOnlyCollection, сигнализирует об отсутствии ленивости. И так же, как представляется, нет ленивых реализаций IReadOnlyCollection.)

Это также справедливо для входных параметров, поскольку запрос IReadOnlyCollection<T> вместо IEnumerable<T> сигнализирует о том, что метод должен повторять несколько раз над сборкой. Конечно, метод может создать свой собственный список из IEnumerable<T> и перебрать его, но поскольку у вызывающего уже может быть загруженная коллекция под рукой, имеет смысл воспользоваться ею, когда это возможно. Если вызывающий абонент имеет только IEnumerable<T>, ему нужно только добавить .ToArray() или .ToList() к параметру.

То, что IReadOnlyCollection делает не, не позволяет вызывающему абоненту передать какой-либо другой тип коллекции. Для такой защиты нужно использовать класс ReadOnlyCollection<T>.

Вкратце, единственное, что IReadOnlyCollection<T> делает относительно IEnumerable<T>, добавляет свойство Count и, таким образом, сигнализирует о том, что никакой ленивости не задействован.

Ответ 3

Кажется, вы можете просто вернуть соответствующий интерфейс:

...
    private readonly List<WorkItem> workItems = new List<WorkItem>();

    // Usually, there no need the property to be virtual 
    public virtual IReadOnlyList<WorkItem> WorkItems {
      get {
        return workItems;
      }
    }
...

Так как поле workItems на самом деле List<T>, поэтому естественная идея IMHO заключается в том, чтобы выставить наиболее широкий интерфейс, который IReadOnlyList<T> в случае