views:

135

answers:

5

I'm writing an interface which has a collection property which I want to be read only. I don't want users of the interface to be able to modify the collection. The typical suggestion I've found for creating a read only collection property is to set the type of the property to IEnumerable like this:

private List<string> _mylist;
public IEnumerable<string> MyList
{
get
    {
        return this._mylist;
    }
}

Yet this does not prevent the user from casting the IEnumerable back to a List and modifying it.

If I use a Yield keyword instead of returning _mylist directly would this prevent users of my interface from being able to modify the collection. I think so because then I'm only returning the objects one by one, and not the actual collection.

 private List<string> _mylist;
public IEnumerable<string> MyList
{
get
    {
        foreach(string str in this._mylist)
        {
            yield return str;
        }
    }
}
A: 

Yes, the resulting sequence will be read-only. It also has the advantage of deferred execution, so the loop won't actually be evaluated until some client code enumerates the items in the sequence (although this can have unexpected consequences if the internal collection is changed before the client code tries to enumerate it).

Will Vousden
Here, deferred execution is nothing but overhead.
SLaks
Sometimes I *force* evaluation just to avoid delayed side-effects (ick!!!)
pst
+4  A: 

Yes, it is immutable.

However, you should use a ReadOnlyCollection<string> instead.

For example:

MyClassName() {  //(Constructor)
    MyList = new ReadOnlyCollection<string>(_myList);
}

private List<string> _mylist;
public ReadOnlyCollection<string> MyList { get; private set; }
SLaks
Yes, but note that part of the "immutability" comes from the contained objects, in this case `string` which is also immutable. In general the caller could not change the membership of the collection but could change the content (for ref types). Sometimes this is desirable and sometimes not.
Ben Voigt
@Ben: That's true with any collection of ref types, including IEnumerable<T> in general, unless you clone every element...
Reed Copsey
My case the collection contains immutable objects (a struct with private set accessors) so this is not a concern.
Eric Anastas
+2  A: 

Yes, that works. It's not the only way though. Here's another way that works in .NET 3.5:

public IEnumerable<string> MyList
{
    get { return _myList.Skip(0); }
}

Taken from this question which also shows some other ways you could do it.

(Found using Google).

Mark Byers
+5  A: 

This is a reasonable way to prevent users of your class from casting back to a List and accessing members. Another option is to use _mylist.AsReadOnly(), which returns a ReadOnlyCollection<T>.

However, I would recommend not worrying about how users can "break" your API by casting to other, internal types. If you put a public API of IEnumerable<T>, this is what an end user will (or should) use - not trying to cast to a List<T>. Trying to prevent a user from doing something inappropriate and breaking your API like this is problematic at best, especially since you can't prevent all inappropriate usages.

Reed Copsey
When using partial trust sandboxes (in .NET or Java) your last sentence is no longer true.
Ben Voigt
@Ben: True. In general, though, I find people try to prevent casting in cases where there is really very little reason to do so. However, in a partial trust sandbox, you're probably not going to be directly providing access to anything where you'd have to worry about this in any case...
Reed Copsey
+1  A: 

If the code using your class has full trust, it could always Reflect and access the private field anyway. It would be rather impolite, but casting the return value is only marginally better. If a user casts your property and corrupts state, it's their own fault for doing so.

One advantage of returning the list instance directly as IEnumerable is that LINQ can optimize access for methods like ElementAt() or Count(), without violating the fundamental contract of IEnumerable (since either could also be accomplished by enumeration).

Dan Bryant