How to allow iteration over a private collection but not change it?

If I have the following class member:

private List<object> obs; 

and I want to allow traversal of this list as part of the class interface, how would I do it?

Publishing will not work because I do not want the list to be directly modified.

+6
iterator c # design-patterns
source share
8 answers

You would expose it as IEnumerable<T> , but not just return it directly:

 public IEnumerable<object> Objects { get { return obs.Select(o => o); } } 

Since you indicated that you only want to traverse the list, that’s all you need.

You might be tempted to return the List<object> directly as IEnumerable<T> , but that would be wrong because you could easily check the IEnumerable<T> at runtime, determine that it is a List<T> , and pass it so mutate the contents.

However, using return obs.Select(o => o); , you are returning an iterator over List<object> , not a direct link to List<object> .

Some might think that this qualifies as a “degenerate expression” in accordance with section 7.15.2.5 of the C # Language Specification. However, Eric Lippert talks in detail about why this forecast is not optimized .

In addition, people suggest using the AsEnumerable extension method . This is incorrect because the reference identifier of the source list is saved. In the "Notes" section of the documentation:

The AsEnumerable<TSource>(IEnumerable<TSource>) method has no effect other than changing the type of the compilation time source from a type that implements IEnumerable<T> to IEnumerable<T> .

In other words, all he does is specify the original parameter IEnumerable<T> , which does not protect referential integrity, the original link is returned and can be dropped to List<T> and used to change the list.

+12
source share

You can use ReadOnlyCollection or make a copy of List and return it instead (given copy performance limitation). You can also use List<T>.AsReadOnly .

+11
source share

This has already been said, but I do not see a single answer, like superclear.

The easiest way is to simply return ReadOnlyCollection

 private List<object> objs; public ReadOnlyCollection<object> Objs { get { return objs.AsReadOnly(); } } 

The disadvantage of this is that if you want to change your implementation later, some callers may already depend on the fact that the collection provides random access. So a safer definition is to just show IEnumerable

 public IEnumerable<object> Objs { get { return objs.AsReadOnly(); } } 

Note that you do not need to call AsReadOnly () to compile this code. But if you don't, the caller just returned the return value to the list and changed your list.

 // Bad caller code var objs = YourClass.Objs; var list = objs as List<object>; list.Add(new object); // They have just modified your list. 

The same potential problem with this solution

 public IEnumerable<object> Objs { get { return objs.AsEnumerable(); } } 

Therefore, I would definitely recommend that you call AsReadOnly () in your list and return that value.

+3
source share

You can do this in two ways:

  • Or by converting the list to a Readonly collection:

     new System.Collections.ObjectModel.ReadOnlyCollection<object>(this.obs) 
  • Or, returning IEnumerable from elements:

     this.obs.AsEnumerable() 
+2
source share

Add the following method signature to your interface: public IEnumerable TraverseTheList ()

It is implicated as follows:

 public IEnumerable<object> TraverseTheList() { foreach( object item in obj) { yield return item; } } 

which allows you to do the following:

 foreach(object item in Something.TraverseTheList()) { // do something to the item } 

Return returns tells the compiler to create an enumerator for you.

+2
source share

Open ReadOnlyCollection<T>

+1
source share

An interesting publication and dialogue on this very topic: http://davybrion.com/blog/2009/10/stop-exposing-collections-already/ .

+1
source share

Have you considered getting a class from System.Collections.ReadOnlyCollectionBase?

0
source share

All Articles