I'm trying to get the idea, what would be the best way to publish a Readonly List of objects as a public method? From Eric Lippert's Blog, Arrays are kinda bad, because someone could easily add a new Entry. So one would have to pass a new Array every time the method is called. He suggests, to pass IEnumerable<T>
, since this is per definition read only (no add, remove methods), which I practiced for quite sometime. But in our new project, people even started to create Arrays of these IEnumerables
, because they don't know the DataSource behind, so they get a : Handling warning for possible multiple enumeration of IEnumerable
I'm interested in a technical approach, how one would solve this puzzle. The only solution I came up so far would be to use a IReadOnlyCollection
, but this would be way more explicit than an IEnumerable
.
What is best practice to publish such lists, which shouldn't be changed, but should be declared as In-Memory Lists?
IEnumerable is a behavior while Array is a data structure(Contiguous collection of elements with fixed size, facilitating accessing elements by indexes) .
IEnumerable is a deferred execution while List is an immediate execution. IEnumerable will not execute the query until you enumerate over the data, whereas List will execute the query as soon as it's called. Deferred execution makes IEnumerable faster because it only gets the data when needed.
So it isn't that IEnumerable<T> is more efficient than list in a "performance" or "runtime" aspect. It's that IEnumerable<T> is a more efficient design construct because it's a more specific indication of what your design requires. (Though this can lead to runtime gains in specific cases.)
IEnumerable interface is used when we want to iterate among our classes using a foreach loop. The IEnumerable interface has one method, GetEnumerator, that returns an IEnumerator interface that helps us to iterate among the class using the foreach loop.
Usually - and since a while - this solved using immutable collections.
Your public properties should be, for example, of type IImmutableList<T>
, IImmutableHashSet<T>
and so on.
Any IEnumerable<T>
can be converted to an immutable collection:
someEnumerable.ToImmutableList();
someEnumerable.ToImmutableHashSet();
This way you can work with private properties using mutable collections and provide a public surface of immutable collections only.
For example:
public class A { private List<string> StringListInternal { get; set; } = new List<string>(); public IImmutableList<string> StringList => StringListInternal.ToImmutableList(); }
There's also an alternate approach using interfaces:
public interface IReadOnlyA { IImmutableList<string> StringList { get; } } public class A : IReadOnlyA { public List<string> StringList { get; set; } = new List<string>(); IImmutableList<string> IReadOnlyA.StringList => StringList.ToImmutableList(); }
Check that IReadOnlyA
has been explicitly-implemented, thus both mutable and immutable StringList
properties can co-exist as part of the same class.
When you want to expose an immutable A
, then you return your A
objects upcasted to IReadOnlyA
and upper layers won't be able to mutate the whole StringList
in the sample above:
public IReadOnlyA DoStuff() { return new A(); } IReadOnlyA a = DoStuff(); // OK! IReadOnly.StringList is IImmutableList<string> IImmutableList<string> stringList = a.StringList;
It should be a possible solution to avoid converting the source list into immutable list each time immutable one is accessed.
If type of items overrides Object.Equals
and GetHashCode
, and optionally implements IEquatable<T>
, then both public immutable list property access may look as follows:
public class A : IReadOnlyA { private IImmutableList<string> _immutableStringList; public List<string> StringList { get; set; } = new List<string>(); IImmutableList<string> IReadOnlyA.StringList { get { // An intersection will verify that the entire immutable list // contains the exact same elements and count of mutable list if(_immutableStringList.Intersect(StringList).Count == StringList.Count) return _immutableStringList; else { // the intersection demonstrated that mutable and // immutable list have different counts, thus, a new // immutable list must be created again _immutableStringList = StringList.ToImmutableList(); return _immutableStringList; } } } }
I do not think immutable is the way to go
int[] source = new int[10000000];//uses 40MB of memory var imm1 = source.ToImmutableArray();//uses another 40MB var imm2 = source.ToImmutableArray();//uses another 40MB
List behaves the same way. If I want to make full copy every time, I do not have to care about what user does with that array. Making it immutable does not protect content of objects in the collection either, they can be changed freely. @HansPassant suggestion seems to be best
public class A { protected List<int> list = new List<int>(Enumerable.Range(1, 10000000)); public IReadOnlyList<int> GetList { get { return list; } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With