【问题标题】:Read only list of lists c#只读列表列表c#
【发布时间】:2016-06-14 09:05:24
【问题描述】:

一般来说,我正在制作的程序涉及存储少量可以分类的条目(在任何给定时间可能少于 30 个)。我希望允许看到这些条目,但不要在课堂外使用它们进行更改。我创建了一个可以修改的名为 Entry 的类和另一个名为 ReadOnlyEntry 的类,它是一个 Entry 对象的包装器。组织这些条目对象的最简单方法似乎是创建一个List<List<Entry>>,其中每个List<Entry> 是一个类别。但随后以只读方式公开这些数据变得混乱和复杂。我意识到我必须拥有以下每种类型的一个对象:

List<List<Entry>> data;
List<List<ReadOnlyEntry>>  //  Where each ReadOnlyEntry is a wrapper for the Entry in the same list and at the same index as its Entry object.
List<IReadOnlyCollection<ReadOnlyEntry>>  //  Where each IReadOnlyCollection is a wrapper for the List<ReadOnlyEntry> at the same index in data.
IReadOnlyCollection<IReadOnlyCollection<ReadOnlyList>> readOnlyList  //  Which is a wrapper for the first item I listed.

列表中的最后一项将公开。第一个让我更改条目,第二个让我添加或删除条目,第三个让我添加或删除类别。每当数据发生变化时,我都必须保持这些包装器的准确性。这对我来说似乎很复杂,所以我想知道是否有更好的方法来处理这个问题。

编辑 1: 澄清一下,我知道如何使用 List.asReadOnly(),我上面建议做的事情将解决我的问题。我只是有兴趣听到更好的解决方案。让我给你一些代码。

class Database
{
    //  Everything I described above takes place here.

    //  The data will be readable by this property:
    public IReadOnlyCollection<IReadOnlyCollection<ReadOnlyList>> Data
    {
        get
        {
            return readOnlyList;
        }
    }

    //  These methods will be used to modify the data.
    public void AddEntry(stuff);
    public void DeleteEntry(index);
    public void MoveEntry(to another category);
    public void AddCategory(stuff);
    public void DeleteCategory(index);
}

【问题讨论】:

标签: c# covariance readonly-collection


【解决方案1】:

您可以使用List&lt;T&gt;.AsReadOnly() 来返回ReadOnlyCollection&lt;T&gt;

此外,您正在折磨List&lt;T&gt; 类以您的方式存储数据。构建您自己的类层次结构来存储您的个人列表。

【讨论】:

  • 我正在使用 List.asReadOnly(),但返回 ReadOnlyCollection 仍然允许用户修改集合中的对象。我考虑制作额外的类来表示一个类别,但得出的结论是不值得这样做,因为类别中唯一的东西就是条目列表。我还不如只拥有一个 List 而根本不为它上课。在编写类时我能看到的唯一好处是它使语法更容易一些。
  • 您可以在这里应用许多好的设计原则。 KISSSRP 等。如果它看起来很复杂,那么这清楚地表明您需要将其分解为类。这是面向对象编程的根本原因之一。
【解决方案2】:

.NET 集合应该支持covariance,但它们本身不支持(相反,某些接口支持协方差https://msdn.microsoft.com/ru-ru/library/dd233059.aspx)。协方差意味着List&lt;Conctrete&gt; 的行为类似于List&lt;Base&gt; 的子类,如果ConcreteBase 的子类。您可以使用接口协变或像这样使用强制转换:

using System.Collections.Generic;

namespace MyApp
{
    interface IEntry
    {
    }

    class Entry : IEntry
    {
    }

    class Program
    {
        private List<List<Entry>> _matrix = null;

        public List<List<IEntry>> MatrixWithROElements
        {
            get 
            {
                return _matrix.ConvertAll(row => row.ConvertAll(item => item as IEntry));
            }
        }

        public IReadOnlyList<List<IEntry>> MatrixWithRONumberOfRows
        {
            get 
            {
                return _matrix.ConvertAll(row => row.ConvertAll(item => item as IEntry));
            }
        }

        public List<IReadOnlyList<IEntry>> MatrixWithRONumberOfColumns
        {
            get 
            {
                return _matrix.ConvertAll(row => row.ConvertAll(item => item as IEntry) as IReadOnlyList<IEntry>);
            }
        }

        public IReadOnlyList<IReadOnlyList<IEntry>> MatrixWithRONumberOfRowsAndColumns
        {
            get 
            {
                return _matrix.ConvertAll(row => row.ConvertAll(item => item as IEntry));
            }
        }

        public void Main(string[] args)
        {    
        }
    }
}

感谢 Matthew Watson 指出我之前的答案版本中的错误。

【讨论】:

  • List&lt;T&gt; 不是这样协变的。 public List&lt;List&lt;IEntry&gt;&gt; matrixWithROElements =&gt; _matrix; 行将无法编译。
  • 具体来说,List&lt;T&gt; 类型定义为List&lt;out T&gt;,因此它与T 没有协变。
  • 你们两个完全正确。这是最明智的做法。我完全忘记了接口的协方差。谢谢您的帮助!我已将此标记为最佳答案。
【解决方案3】:

你可以为Entry 创建一个只包含getter 的接口;您将通过此接口公开元素以提供只读访问权限:

public interface IEntry
{
    int Value { get; }
}

可写的实现很简单:

public sealed class Entry : IEntry
{
    public int Value { get; set; }
}

现在您可以利用这样一个事实,即您可以将List&lt;List&lt;Entry&gt;&gt; 作为IReadOnlyCollection&lt;IReadOnlyCollection&lt;IEntry&gt;&gt; 返回,而无需做任何额外的工作:

public sealed class Database
{
    private readonly List<List<Entry>> _list = new List<List<Entry>>();

    public Database()
    {
        // Create your list of lists.

        List<Entry> innerList = new List<Entry>
        {
            new Entry {Value = 1},
            new Entry {Value = 2}
        };

        _list.Add(innerList);
    }

    public IReadOnlyCollection<IReadOnlyCollection<IEntry>> Data => _list;
}

注意Data 属性的实现是多么简单。

如果您需要将新属性添加到 IEntry,您还必须将它们添加到 Entry,但您不需要更改 Database 类。

如果您使用的是 C#5 或更早版本,Data 将如下所示:

public IReadOnlyCollection<IReadOnlyCollection<IEntry>> Data
{
    get { return _list; }
}

【讨论】:

  • 是的!这是一个界面的优秀应用。我知道我错过了什么!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-23
  • 2015-05-21
  • 1970-01-01
  • 1970-01-01
  • 2013-07-02
  • 2010-12-01
  • 2011-11-04
相关资源
最近更新 更多