Reputation: 1135
I've been around this design problem many times, and never found a killer solution.
I want to expose a collection that is editable in the owner class
scope, but only readable for other public scopes.
Trial 1:
public class MyClass
{
private List<object> _myList = new List<object>();
public IEnumerable<object> MyList { get { return _myList; } }
}
The problem with this is a external code can just cast it back to List
and edit, like this:
var x = ((List<object>)MyList);
Trial 2:
public class MyClass
{
private List<object> _myList = new List<object>();
public IEnumerable<object> MyList { get { return _myList.ToList(); } }
}
This way we prevent external modification, but create a unnecessary overhead of copying the List
many times.
Trial 3:
public class MyClass
{
private List<object> _myList = new List<object>();
private ReadOnlyCollection<object> _roList =
new ReadOnlyCollection<object>(_myList)
public IEnumerable<object> MyList { get { return _roList; } }
}
This is the standard solution, wich I use currently, but ReadOnlyCollection
is about 30% slower:
Debug Trace:
Use normal foreach on the ReadOnlyCollection
Speed(ms): 2520,3203125
Result: 4999999950000000
use<list>.ForEach
Speed(ms): 1446,1796875
Result: 4999999950000000
Use normal foreach on the List
Speed(ms): 1891,2421875
Result: 4999999950000000
Is there a 'perfect' way of doing this? Thanks.
Upvotes: 6
Views: 3598
Reputation: 13148
With Collection Expression syntax you can now do something like this:
public sealed class MyClass {
readonly List<object> _myList = [];
public IReadOnlyList<object> MyList => [.._myList];
}
The underlying object will be of type <>z__ReadOnlyArray<object>
which is a compiler generated type.
Note that this will enumerate the collection and create a new result with every read. Often I would either find a way to cache this or else if it has to enumerate on every read then I would make it a Get...
method instead of a property to indicate that it is doing some significant work every time you call it.
Upvotes: -1
Reputation: 31633
List<T>
implements IReadOnlyList<T>
:
public class MyClass
{
private List<object> _myList = new List<object>(); // Modifiable
public IReadOnlyList<object> MyList => _myList; // Read only
}
Upvotes: 3
Reputation: 16698
Why don't you use List<T>.AsReadOnly
method, see the code snippet below:
public class MyClass
{
private List<object> _myList = new List<object>();
public IList<object> MyList { get { return _myList.AsReadOnly(); } }
}
Upvotes: 0
Reputation: 1792
The solution I usually use and like a lot because it is simple is the following one:
public IEnumerable<object> MyList { get { return _myList.Select(x => x); } }
However, it requires you to use a version of .NET that supports Linq
For looping over it with a foreach
it's actually faster: less than 1ms for the Select
and 0.1 second for the ReadOnlyCollection
. For the ReadOnlyCollection
, I used:
public IEnumerable<object> MyROList { get { return new ReadOnlyCollection<object>(_myList); } }
Upvotes: 1
Reputation: 56162
.NET Framework 4.5 introduces two new interfaces: IReadOnlyCollection<T>
and IReadOnlyList<T>
. List<T>
implements both interfaces.
Upvotes: 0
Reputation: 6259
Have you tried returning the enumerator?
public class MyClass
{
private List<object> _myList = new List<object>();
public IEnumerable<object> MyList { get { yield return _myList.GetEnumerator(); } }
}
This doesn't create a copy, is readonly, and cannot be cast back to list.
Edit: this only works with yield return. It is lazy evaluated this way, I do not know whether that is an issue for you or not.
Upvotes: 5
Reputation: 155065
Use ReadOnlyCollection<T>
as a wrapper - it uses your List<T>
collection internally (using the same reference, so it doesn't copy anything, and any changes made to the List are also reflected in the ReadOnlyCollection).
It's better than just offering an IEnumerable property because it also offers indexing and count information.
Upvotes: 0
Reputation: 726509
You can use List<T>.AsReadOnly
method to expose a thin read-only wrapper of your list. There will be no additional copying, and the caller will "see" changes to the original array done inside your method instantaneously:
public ReadOnlyCollection<object> MyList { get { return _myList.AsReadOnly(); } }
Upvotes: 5