Кращі практики, що повертаються лише для читання


11

У мене є питання "найкращих практик" щодо OOP в C # (але це начебто стосується всіх мов).

Подумайте про наявність бібліотечного класу з об'єктом, який повинен піддаватися впливу загальнодоступних, скажімо, через доступ до ресурсу, але ми не хочемо, щоб громадськість (люди, що використовують цей клас бібліотеки) змінювати його.

class A
{
    // Note: List is just example, I am interested in objects in general.
    private List<string> _items = new List<string>() { "hello" }

    public List<string> Items
    {
        get
        {
            // Option A (not read-only), can be modified from outside:
            return _items;

            // Option B (sort-of read-only):
            return new List<string>( _items );

            // Option C, must change return type to ReadOnlyCollection<string>
            return new ReadOnlyCollection<string>( _items );
        }
    }
}

Очевидно, що найкращий підхід - це "варіант C", але дуже мало об'єктів мають варіант ReadOnly (і, звичайно, не визначені користувачем класи).

Якщо ви були користувачем класу A, ви очікуєте змін

List<string> someList = ( new A() ).Items;

розповсюдження на оригінальний (A) об'єкт? Або добре повернути клона за умови, що це було написано в коментарях / документації? Я думаю, що цей клонний підхід може призвести до досить важко відслідковувати помилки.

Я пам’ятаю, що в C ++ ми могли повернути об’єкти const, і ви могли лише викликати методи, позначені як const на ньому. Я думаю, що в C # немає такої особливості / візерунка? Якщо ні, то чому б вони не включили його? Я вважаю, це називається коректністю коректності.

Але знову ж таки, моє головне питання - про те, «що б ви очікували» або як впоратися з варіантом A проти B.


2
Подивіться цю статтю про цей попередній перегляд нових незмінних колекцій для .NET 4.5: blogs.msdn.com/b/bclteam/archive/2012/12/18/… Ви вже можете отримати їх через NuGet.
Крістоф Клайс

Відповіді:


10

Окрім відповіді @ Jesse, що є найкращим рішенням для колекцій: загальна ідея полягає в тому, щоб розробити свій публічний інтерфейс A таким чином, щоб він повертався лише

  • типи значень (наприклад, int, double, структура)

  • незмінні об'єкти (наприклад, "рядок" або визначені користувачем класи, які пропонують лише методи для читання, як і ваш клас A)

  • (лише якщо потрібно): об'єктні копії, як демонструє ваш варіант "В"

IEnumerable<immutable_type_here> незмінна сама по собі, тому це лише окремий випадок із вищезазначеного.


19

Також зауважте, що вам слід запрограмувати інтерфейси, і загалом, те, що ви хочете повернути з цього властивості, є IEnumerable<string>. А List<string>це трохи ні-ні, тому що він взагалі мінливий, і я піду так далеко, щоб сказати, що ReadOnlyCollection<string>як виконавець ICollection<string>відчуває, що він порушує Принцип заміщення Ліскова.


6
+1, використовуючи IEnumerable<string>саме те, що хочеться тут.
Док Браун

2
У .Net 4.5 ви також можете використовувати IReadOnlyList<T>.
svick

3
Примітка для інших зацікавлених сторін. Якщо розглядати аксесуар властивості: якщо ви просто повернете IEnumerable <string> замість списку <string>, зробивши return _list (+ неявна передача в IEnumerable), цей список можна повернути до списку <string> та змінити, і зміни будуть розповсюджуються на головний об’єкт. Ви можете повернути новий Список <string> (_list) (+ наступний неявний викид до IEnumerable), який захистить внутрішній список. Більш докладно про це можна знайти тут: stackoverflow.com/questions/4056013 / ...
NeverStopLearning

1
Чи краще вимагати, щоб будь-який одержувач колекції, який потребує доступу до нього за індексом, повинен бути готовий скопіювати дані у тип, який полегшує це, або краще вимагати, щоб код, що повертає колекцію, надав її у формі, що доступний за допомогою індексу? Якщо, швидше за все, постачальник не матиме його у формі, яка не полегшує доступ за індексами, я вважаю, що значення звільнення одержувачів від необхідності виготовлення власної зайвої копії перевищить значення звільнення постачальника від необхідності постачання. форма з випадковим доступом (наприклад, лише для читання IList<T>)
supercat

2
Одна річ, яка мені не подобається в IEnumerable, - це те, що ти ніколи не знаєш, працює вона як посібник чи це лише об’єкт даних. Якщо він працює як програмне забезпечення, це може спричинити за собою неповторні помилки, тому це добре знати іноді. Ось чому я, як правило, віддаю перевагу ReadOnlyCollection або IReadOnlyList тощо
Стів Вермеулен

3

Певний час назад і внизу я зіткнувся з подібною проблемою, але це справді працює лише в тому випадку, якщо ви керуєте також бібліотекою:


Почніть зі свого класу A

public class A
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

Потім отримайте інтерфейс з цього лише з отриманою частиною властивостей (та будь-якими методами читання) та майте A, що це реалізує

public interface IReadOnlyA
{
    public int N { get; }
}
public class A : IReadOnlyA
{
    private int _n;
    public int N
    {
        get { return _n; }
        set { _n = value; }
    }
}

Тоді, звичайно, коли ви хочете використовувати версію лише для читання, достатньо простого складу. Це саме те, що ви вирішили на C, але насправді я подумав, що запропоную метод, яким я його досяг


2
Проблема з цим полягає в тому, що можна повернути його до змінної версії. І його порушення LSP, оскільки стан, що спостерігається за допомогою методів базового класу (в даному випадку інтерфейсу), може бути змінено новими методами з підкласу. Але це принаймні поєднує наміри, навіть якщо воно не виконує його.
user470365

1

Для кожного, хто знайде це питання і все ще зацікавлений у відповіді - зараз є ще один варіант, який є викриттям ImmutableArray<T>. Ось кілька моментів, чому я думаю, що тоді краще IEnumerable<T>або ReadOnlyCollection<T>:

  • в ньому чітко зазначено, що воно незмінне (виразний API)
  • в ній чітко зазначено, що колекція вже сформована (іншими словами, вона не лініалізована, і це добре, щоб повторити її кілька разів)
  • якщо кількість предметів відома, це не приховує, що так IEnumerable<T>знає
  • оскільки клієнт не знає, що таке реалізація, IEnumerableабо IReadOnlyCollectвін / вона не може припустити, що він ніколи не змінюється (іншими словами, немає гарантії, що елементи колекції не були змінені, а посилання на цю колекцію залишається незмінною)

Також якщо APi потрібно повідомити клієнта про зміни в колекції, я виставлю подію як окремий учасник.

Зауважте, що я не кажу, що IEnumerable<T>це взагалі погано. Я все одно використовую його при передачі колекції в якості аргументу або поверненні ліниво ініціалізованої колекції (у цьому конкретному випадку є сенс приховати кількість елементів, оскільки це ще не відомо).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.