C#中的"Read only"属性访问器

19

我有以下的类:

class SampleClass
{
   private ArrayList mMyList;

   SampleClass()
   {
       // Initialize mMyList
   }

   public ArrayList MyList
   {
       get { return mMyList;}
   }
}

我希望用户能够获取我的列表(即MyList),因此我通过属性公开了“get”,但我不希望他们所做的更改(例如MyList.Add(new Class());)传回到我的类中。
我猜我可以返回对象的副本,但这可能会很慢,我正在寻找一种方法,它将在编译时提供错误提示,告知用户他们不应期望能够修改从属性返回的值。
这可行吗?

相关问题:https://dev59.com/iHRB5IYBdhLWcg3wQFLu - Joel in Gö
2
在.NET 4.5中,List<T>:ReadOnlyCollection<T>是否有任何更改?http://www.infoq.com/news/2011/10/ReadOnly-WInRT/ - Colonel Panic
8个回答

25

使用ArrayList有一定的限制,因为在BCL中没有只读非泛型集合类。快速且简单的解决方案是返回IEnumerable类型。

   public IEnumerable MyList
   {
       get { return mMyList;}
   }

这并不能完全防止将某个对象转换为ArrayList,但默认情况下也不允许进行编辑。

您可以通过调用ArrayList.ReadOnly来返回一个实际上是只读的列表。但是,它的返回类型仍然是ArrayList,因此用户仍然可以使用.Add进行编译,但会导致运行时错误。


19

使用ArrayList.ReadOnly()方法构建并返回包装列表的只读封装。它不会复制列表,而只是在其周围创建了一个只读封装。为了获得编译时检查,您可能希望像@Jared建议的那样将只读封装作为IEnumerable返回。

public IEnumerable MyList
{
     get { return ArrayList.ReadOnly(mMyList); }
}

只读集合就是一个带有包装器的集合,它防止修改集合。如果对底层集合进行更改,只读集合将反映这些更改。

此方法是O(1)操作。

参考资料


2
我反对这种解决方案,因为它会将本应该在编译时出现的错误变成运行时错误。 - JaredPar
-1,OP:“我正在寻找一种能够在编译时产生错误的方法。” - Daniel LeCheminant
不复制到ReadOnlyCollection(他也说他想避免这种情况),我选择强制只读而不是假装只读。实际上,两者都做可能更好。 - tvanfosson
+1,看起来你已经修复了它,如果他们不进行强制转换,则会产生编译时错误,如果他们进行强制转换,则会产生运行时错误,而不会牺牲O(1)的特性。很好。 - Shavais

9

对于JaredPar的答案,我想进一步解释。正如他所说,返回实际的后备字段并不完全安全,因为用户仍然能够将IEnumerable动态转换回ArrayList

为了确保不对原始列表进行修改,我会编写以下代码:

public IEnumerable MyList
{
    get
    {
         foreach (object o in mMyList)
             yield return o;
    }
}

再者,我可能会使用通用列表(IEnumerable<T>)以完全保证类型安全。


这个是正确的 - 希望我能投两票。循环后不调用 yield break; 会有什么问题吗? - Jay
@Jay:在循环后显式使用 yield break; 是没有必要的。方法的结束意味着 Enumerator 的结束(并且 MoveNext 会正确地返回 false)。 - Tom Lokhorst
使用这种解决方案,用户是否会失去ArrayList的功能,例如快速随机访问和快速计数? - Colonel Panic
1
@ColonelPanic 是的!更糟糕的是,每次访问 IEnummerable(例如执行 obj.MyList.Count())时,都会执行 get 中的代码并重新遍历列表。 - Tom Lokhorst

4

使用 ReadOnlyCollection

return new ReadOnlyCollection<object>(mMyList)

您还可以将 ReadOnlyCollection 作为一个字段,它将自动反映底层列表中的更改。 这是最有效的解决方案。

如果您知道 mMyList 只包含一种类型的对象(例如,它仅包含日期时间),则可以返回 ReadOnlyCollection<DateTime>(或其他类型)以获得附加的类型安全性。 如果您正在使用 C# 3,则可以简单地编写:

return new ReadOnlyCollection<DateTime>(mMyList.OfType<DateTime>().ToArray())

但这不会自动更新底层列表,并且效率也较低(它将复制整个列表)。 最好的选择是将 mMyList 定义为通用的 List<T> (例如,List<DateTime>)。



1

你应该将返回值包装在一个只读集合中。


0

从.NET v2.0开始可用

    public ArrayList MyList { get; private set; }

您仍然可以在现有的列表上使用“添加”功能。 - Amelse Etomer

-3

将属性设置为Sealed(或在VB.NET中为NotInheritable)和只读,就像这样:

   public sealed readonly ArrayList MyList
   {
       get { return mMyList;}
   }

不要忘记将备份字段设置为只读:

   private readonly ArrayList mMyList;

但是为了初始化mMyList的值,你必须仅在构造函数中进行初始化,就像这个例子:

public SampleClass()
{
   // Initialize mMyList
   mMyList = new ArrayList();
}

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