How to allow iteration over a private collection but not modification?
If I have the following class member:
private List<object> obs;
and I want to allow traversal 开发者_StackOverflowof this list as part of the class' interface, how would I do it?
Making it public won't work because I don't want to allow the list to be modified directly.
You would expose it as an IEnumerable<T>
, but not just returning it directly:
public IEnumerable<object> Objects { get { return obs.Select(o => o); } }
Since you indicated you only wanted traversal of the list, this is all you need.
One might be tempted to return the List<object>
directly as an IEnumerable<T>
, but that would be incorrect, because one could easily inspect the IEnumerable<T>
at runtime, determine it is a List<T>
and cast it to such and mutate the contents.
However, by using return obs.Select(o => o);
you end up returning an iterator over the List<object>
, not a direct reference to the List<object>
itself.
Some might think that this qualifies as a "degenerate expression" according to section 7.15.2.5 of the C# Language Specification. However, Eric Lippert goes into detail as to why this projection isn't optimized away.
Also, people are suggesting that one use the AsEnumerable extension method. This is incorrect, as the reference identity of the original list is maintained. From the Remarks section of the documentation:
TheAsEnumerable<TSource>(IEnumerable<TSource>)
method has no effect other than to change the compile-time type of source from a type that implementsIEnumerable<T>
toIEnumerable<T>
itself.
In other words, all it does is cast the source parameter to IEnumerable<T>
, which doesn't help protect referencial integrity, the original reference is returned and can be cast back to List<T>
and be used to mutate the list.
You can use a ReadOnlyCollection
or make a copy of the List
and return it instead (considering the performance penalty of the copy operation). You can also use List<T>.AsReadOnly
.
This has already been said, but I don't see any of the answers as being superclear.
The easiest way is to simply return a ReadOnlyCollection
private List<object> objs;
public ReadOnlyCollection<object> Objs {
get {
return objs.AsReadOnly();
}
}
The drawback with this is, that if you want to change your implementation later on, then some callers may already be dependent on the fact, that the collection provides random access. So a safer definition would be to just expose an IEnumerable
public IEnumerable<object> Objs {
get {
return objs.AsReadOnly();
}
}
Note that you don't have to call AsReadOnly() to compile this code. But if you don't, the caller my just cast the return value back to a List and modify 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 is potential problem also exists with this solution
public IEnumerable<object> Objs {
get {
return objs.AsEnumerable();
}
}
So I would definately recommend that you call AsReadOnly() on you list, and return that value.
To your Interface add the following method signature: public IEnumerable TraverseTheList()
Implimented as so:
public IEnumerable<object> TraverseTheList()
{
foreach( object item in obj)
{
yield return item;
}
}
that will allow you to do the following:
foreach(object item in Something.TraverseTheList())
{
// do something to the item
}
The yield return tells the compiler to build an enumerator for you.
You can do this in two ways:
Either By converting the list into a Readonly collection:
new System.Collections.ObjectModel.ReadOnlyCollection<object>(this.obs)
Or by returning an IEnumerable of the items:
this.obs.AsEnumerable()
Expose a ReadOnlyCollection<T>
Interesting post and dialog on this very issue: http://davybrion.com/blog/2009/10/stop-exposing-collections-already/.
Have you considered deriving a class from System.Collections.ReadOnlyCollectionBase?
Just return an IReadOnlyCollection
.
private List<object> obs;
IReadOnlyCollection<object> GetObjects()
{
return obs;
}
精彩评论