public 只读列表<T> 是糟糕的设计吗?

Is a public readonly List<T> bad design?

我目前正在编写一个应用程序,它与某种服务建立连接,以一些 DataTable 对象的形式获取数据,然后将其显示给用户。
为了存储我得到的数据,我做了一个名为 DataStorage 的 class,它有一个 List<DataTable>。其他 classes 需要能够编辑此列表,例如添加需要的对象或在用户发现不需要时删除它们。我也必须能够清除它,我是否需要一组全新的数据。
我可以为此提供 DataStorage 方法,但由于 List<T> 已经提供了这些方法,所以我认为没有必要像那样封装它。所以我将其设置为 readonly 以确保没有人尝试分配新对象 - 甚至更糟,null - 并设置了访问修饰符 public.
这种设计是否可以接受,或者我应该始终保护字段不被直接访问,无论如何?

一般来说,您应该始终采用最通用的类​​型以减少任何紧密耦合并仅提供您实际需要访问的那些成员。话虽如此,在某些情况下,使用 ICollection 可能会更好,它提供对 AddRemoveClear 等基本方法的访问。

然而,使集合 readonly 或更好的只获取 属性 可能是个好主意,没有什么可以反对的。

我们应该非常小心 public 这意味着 public - 无论如何。 你让 class 有这样的行为吗?

  public class Offender {
    ...
    public void Offend(YourClass value) {
      ...
      // In the middle of my routine I've ruined your class
      value.Data.Clear();
      value.Data.Add(someStuff);
      ...
    }
  }

我建议将 完全访问权限 限制为 Data 仅限受信任的 classes:

  public class YourClass {
    // Approved classes (i.e. from your routine) can write, delete, clear... 
    internal readonly List<Data> m_Data = new List<Data>();

    // All the other can only read 
    public IReadOnlyList<Data> Data {
      get {
        return m_Data;
      }
    }
    ...
    // If you let (in some cases) add, delete, clear to untrusted class 
    // just add appropriate methods:
    public void Add(Data data) {
      // Here you can validate the provided data, check conditions, state etc.
    }
  }

如果你想发布一个具有add/remove能力的合集,最好覆盖Collection<T>(或ObservableCollection<T>)class,非常类似于 List<T>,但您可以覆盖并因此控制 add/remove/替换操作。是的,您可以通过 get-only 属性.

实现 public
public class MyClass
{
    private MyCollection myCollection = new MyCollection();

    public IList<MyElement> Collection { get { return myCollection; } }
}

internal class MyCollection: Collection<MyElement>
{
    // by overriding InsertItem you can control Add and Insert
    protected override InsertItem(int index, MyElement item)
    {
        if (CheckItem(item))
            base.InsertItem(index, item);
        throw new ArgumentException("blah");
    }

    // similarly, you can override RemoveItem to control Remove and RemoveAt,
    // and SetItem to control the setting by the indexer (this[])
}