在C#中将列表设置为只读

4
我有这样的示例代码。我的目标是使 "Nums" 值只能使用 "AddNum" 方法进行写入。
namespace ConsoleApplication1
{
    public class Person
    {
        string myName = "N/A";
        int myAge = 0;
        List<int> _nums = new List<int>();

        public List<int> Nums
        {
            get
            {
                return _nums;
            }
        }

        public void AddNum(int NumToAdd)
        {

            _nums.Add(NumToAdd);
        }

        public string Name { get; set; }
        public int Age { get; set; }
    }
}

我已经尝试了很多与AsReadOnly()和readonly关键字有关的内容,但似乎无法实现我想要的效果。

下面是我用于访问属性的代码示例。

Person p1 = new Person();
p1.Nums.Add(25); //access 1
p1.AddNum(37); //access 2

Console.WriteLine("press any key");
Console.ReadLine();

我希望 "access 1" 失败,而 "access 2" 是唯一可以设置值的途径。感谢提前的帮助。


1
你在自己的标签中指向了它:使用ReadOnlyCollection作为你的属性,或者只是将列表复制到其中(这样用户可以更改他们的副本,但不能更改你的内部状态)。 - Random Dev
尝试将Nums变成一个返回数组而不是列表的函数。 - LarsTech
使用"private List<int> Nums"代替"public List<int> Nums"。 - Mahdi Rostami
我认为你不能这样做。但是你可以删除属性“Nums”,并提供一些方法从外部访问它。 - Võ Quang Hòa
https://msdn.microsoft.com/en-us/library/ms132474 - David Peden
这个类的消费者应该能够从 Nums 返回的值中做什么? - Servy
2个回答

11

√ 使用 ReadOnlyCollection,它是 ReadOnlyCollection 的子类,或在某些情况下使用 IEnumerable 表示只读集合的属性或返回值。

引用自这篇文章

您应该使用类似以下的代码:

List<int> _nums = new List<int>();

public ReadOnlyCollection<int> Nums
{
    get
    {
        return _nums.AsReadOnly();
    }
}

2

通常,集合类型不适合作为属性,即使集合被包装在ReadOnlyCollection中,它的含义也不太明确:

IEnumerable<int> nums = myPerson.Nums;
myPerson.AddNum(23);
foreach(int i in nums) // Should the 23 be included!?
  ...

这意味着什么呢?从Nums返回的对象是调用时存在的数字的快照,还是实时视图?

更清晰的方法是添加一个名为GetNumsAsArray的方法,每次调用它都返回一个新的数组;根据调用方想要做什么,可能还需要一个GetNumsAsList变体。有些方法只适用于数组,有些则仅适用于列表,因此如果只提供上述方法中的一个,则有些调用方将不得不调用它,然后将返回的对象转换为所需类型。

如果需要频繁地使用这段代码的性能敏感型调用方,可以提供一个更通用的方法:

int CopyNumsIntoArray(int sourceIndex, int reqCount, ref int[] dest, 
                      int destIndex, CopyCountMode mode);

CopyCountMode是指代码应该如何处理从sourceIndex开始可用的项目数量是否大于或小于reqCount;该方法应该返回可用项的数量,或者如果它违反了调用方的声明,则抛出异常。一些调用方可能会创建并传递一个10个项目的数组,但准备使用更大的数组替换它,如果有超过十个项目需要返回的话;其他人可能期望恰好有23个项目,并且无准备处理任何其他数量。使用参数来指定模式将允许一个方法为许多种类型的调用方提供服务。

尽管许多集合作者不会打扰包含任何符合上述模式的方法,但这些方法可以极大地改善效率,在代码希望处理集合的显着少数情况下(例如50,000个集合中的1,000个项目)。在没有这样的方法的情况下,希望处理这样的范围的代码必须要么请求整个副本(非常浪费)或单独请求数千个项(同样浪费)。允许调用者提供目标数组将提高效率,特别是如果相同的方法进行多个查询,并且目标数组足够大以放置在大对象堆上。


嗨,supercat,我理解了你说的大约40%..当我在我的示例中运行你给出的代码:IEnumerable<int> nums = myPerson.Nums; myPerson.AddNum(23); foreach(int i in nums) // Should the 23 be included!? ... 23被包括进去了你是说它不应该被包括进去吗?还是这是一个问题需要我回答? - stumped221
@stumped221:有些人只看调用代码(但无法看到“Nums”的实现),会期望23被包含在内;有些人则认为它不应该被包含。一个好的API应该尽可能地编写,以便只看调用代码的人能够合理猜测它的含义,但是没有办法让“Nums”属性的行为不会让很多查看客户端代码的人感到惊讶。相比之下,如果代码调用“GetNumsAsArray”,任何看到方法调用的人都会期望它获得一个快照。 - supercat
嗯...你能给我举个例子,说明有人实例化一个对象,明确地为该对象的属性分配或插入一个值,然后不希望或不想要他们刚刚添加的值出现在该对象的实例中的情况吗?我对面向对象编程的世界还比较陌生,所以我只是在教育上提问。 - stumped221
@stumped221:问题在于调用者是否期望Nums是从其接收的对象的实时视图,还是代表与之分离的快照。用类比来说,假设有string foo=myControl.Text; myControl.Text+="*";,人们不会期望第二个语句将星号添加到foo中,因为foo表示在调用它时控件的Text属性包含的快照。 - supercat
@stumped221:假设代码想要允许用户编辑数字列表,但提供还原更改的选项。一种方法是在进行更改之前捕获列表的快照,并在用户选择“还原”时从快照中恢复列表的状态。然而,如果所谓的快照实际上是实时视图,则这种方法将行不通。有时候调用者需要一个快照,偶尔他们可能需要一个实时视图。大多数情况下,调用者实际上都可以接受任何一种方式,但是他们得到的内容的性质仍应该被记录下来。 - supercat
作为澄清的一点,有时返回集合类型的属性可能是允许客户端代码迭代内容的最佳方式,但除非该类型旨在表示实时视图,否则最好设计它以便任何对集合的更改都会明确使视图对象无效,这样任何期望将其用作快照或实时视图的客户端代码将接收到异常,而不是接收可能与客户端意图不同的数据。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接