1

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;
        }
    }
}
Eric Anastas
  • 21,675
  • 38
  • 142
  • 236

5 Answers5

5

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
  • 554,122
  • 78
  • 1,158
  • 1,373
  • When using partial trust sandboxes (in .NET or Java) your last sentence is no longer true. – Ben Voigt May 03 '10 at 23:30
  • @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 May 03 '10 at 23:34
4

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
  • 868,454
  • 176
  • 1,908
  • 1,964
  • 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 May 03 '10 at 23:32
  • 1
    @Ben: That's true with any collection of ref types, including IEnumerable in general, unless you clone every element... – Reed Copsey May 03 '10 at 23:33
  • My case the collection contains immutable objects (a struct with private set accessors) so this is not a concern. – Eric Anastas May 03 '10 at 23:46
3

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).

Community
  • 1
  • 1
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
1

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
  • 27,329
  • 4
  • 56
  • 102
0

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
  • 32,488
  • 9
  • 84
  • 95