views:

153

answers:

8

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?

Making it public won't work because I don't want to allow the list to be modified directly.

A: 

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

BlueMonkMN
+10  A: 

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:

The AsEnumerable<(Of <(TSource>)>)(IEnumerable<(Of <(TSource>)>)) method has no effect other than to change the compile-time type of source from a type that implements IEnumerable<(Of <(T>)>) to IEnumerable<(Of <(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.

casperOne
Note that we would never optimize away an *explicit* call to Select if that appeared in your code. We would optimize away the implicit call to Select when transforming a query expression into method calls. If you say "from x in y where b select x" there is no Select call, just a Where. But if you say "from x in y select x" then we do not just generate "y", we generate a select call on it to ensure immutability of the result.
Eric Lippert
+11  A: 

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.

bruno conde
answer is incomplete. code please.
Brian Leahy
+1  A: 

Expose a ReadOnlyCollection<T>

Mitch Wheat
how? poor answer
Brian Leahy
It's not exactly a problem to make the questioner do a little hunting around. The first result on Google makes it obvious how to expose a `ReadOnlyCollection<T>`.
sixlettervariables
sixlettervariables you obviously miss the point of the site.
Brian Leahy
I'll concede only that @Mitch Wheat should have included a link to the MSDN page. Any more and we'd just be duplicating the content on the MSDN page.
sixlettervariables
+2  A: 

By converting the list into a Readonly collection:

new System.Collections.ObjectModel.ReadOnlyCollection<object>(this.obs)

or by returnin an IEnumerable of the items:

this.obs.AsEnumerable()
Obalix
+1  A: 

Interesting post and dialog on this very issue: http://davybrion.com/blog/2009/10/stop-exposing-collections-already/.

brian
+2  A: 

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.

Brian Leahy
a different angle; excellent.
Patrick Karcher
+2  A: 

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.

Pete
great combination of simple + safe.
Patrick Karcher