LMB
LMB

Reputation: 1135

What is the best way to expose a List<T> as readonly?

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

Answers (8)

Dave Cousineau
Dave Cousineau

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

Mariusz Jamro
Mariusz Jamro

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

Furqan Safdar
Furqan Safdar

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

Guillaume
Guillaume

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

Kirill Polishchuk
Kirill Polishchuk

Reputation: 56162

.NET Framework 4.5 introduces two new interfaces: IReadOnlyCollection<T> and IReadOnlyList<T>. List<T> implements both interfaces.

Upvotes: 0

Paul Phillips
Paul Phillips

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

Dai
Dai

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

Sergey Kalinichenko
Sergey Kalinichenko

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

Related Questions