Maybe that's a silly question... But what's the best (performance and memory wise) way of creating a constant IEnumerable<TSomeType>
...?
If it's not possible to define "the best" way, which are my options? What is your opinion, do you think there is a most appropriate way of doing that?
For instance:
var enumerable = (IEnumerable<TSomeType>) new List<TSomeType> { Value1, Value2, Value3 };
var enumerable = (IEnumerable<TSomeType>) new TSomeType[] { Value1, Value2, Value3 };
Please consider that memory and performance are an issue here - we're talking about a really constrained environment (a small device with .NET installed).
Thanks in advance.
Well, neither List<T>
nor arrays are immutable, so they're out if you're really after immutability - the caller could cast the result and then modify it.
You could create a List<T>
and wrap that in a ReadOnlyCollection<T>
. If nothing has a reference to the original list any more, then it's effectively immutable, barring reflection.
If you don't actually care about immutability - i.e. if you trust all the code not to mess with it - then an array is going to be the most performant approach, almost certainly. There are various CLR-level optimizations which make them work blazingly fast. However, in that case I wouldn't cast to IEnumerable<T>
- I'd just expose it as an array. That will make it faster to iterate over than if the compiler has to call GetEnumerator()
.
If the C# compiler sees a foreach
statement over an array, it generates calls to go straight to the indexer and use the Length
property... and then the CLR will also be able to remove bounds checking, spotting the pattern.
Likewise if you decide to go with List<T>
, leave it as a List<T>
- that way you'll get to use List<T>.Enumerator
- which is a struct - directly, without boxing.
EDIT: Steve Megson brings up the point of using LINQ for this. Actually, you can probably do better than that, because once you've got the enumerator of the underlying list, you can give that back to the caller safely, at least for all collections I'm aware of. So you could have:
public class ProtectedEnumerable<T> : IEnumerable<T>
{
private readonly IEnumerable<T> collection;
public ProtectedEnumerable(IEnumerable<T> collection)
{
this.collection = collection;
}
public IEnumerator<T> GetEnumerator()
{
return collection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
That means there's only a tiny hit when iterating - just the single delegated call to GetEnumerator()
. Compare that with using Enumerable.Select
, which will need to take an extra delegation hit on every call to MoveNext()
(as well as the no-op projection).
While Jon has answered the question on making the list immutable, I would like to also point out that even if the list is immutable, its contained objects are not automatically immutable.
Even if you make the list immutable (for instance by copying its contents into a ReadOnlyCollection<T>
), you can still manipulate properties of the contained objects, if they are not of an immutable type. As soon as you pass out references to the objects contained in the list, calling code can manipulate those objects.
Let's take an example:
class Person
{
public string Name { get; set; }
}
class Group
{
private readonly IEnumerable<Person> _persons;
public Group(IEnumerable<Person> persons)
{
_persons = new ReadOnlyCollection<Person>(persons.ToList());
}
public IEnumerable<Person> Persons
{
get
{
foreach (var person in _persons)
{
yield return person;
}
}
}
}
Then we have the following code:
List<Person> persons = new List<Person>( new[]{new Person { Name = "Fredrik Mörk" }});
Group smallGroup = new Group(persons);
Console.WriteLine("First iteration");
foreach (var person in smallGroup.Persons)
{
Console.WriteLine(person.Name);
person.Name += " [altered]";
}
Console.WriteLine("Second loop");
foreach (var person in smallGroup.Persons)
{
Console.WriteLine(person.Name); // prints "Fredrik Mörk [altered]"
}
As you can see, even though we have make the list effectively immutable, *Person
is not an immutable type. Since the Persons
property of the Group
class passes out references to the actual Person
objects, the calling code can easily manipulate the object.
One way to protect yourself against this is to expose the collection as an IEnumerable<T>
using yield return
and some cloning mechanism to make sure that you don't pass out the original object references. For example, you can alter the Person
class into this:
class Person
{
public string Name { get; set; }
// we add a copy method that returns a shallow copy...
public Person Copy()
{
return (Person)this.MemberwiseClone();
}
}
class Group
{
private readonly IEnumerable<Person> _persons;
public Group(IEnumerable<Person> persons)
{
_persons = new ReadOnlyCollection<Person>(persons.ToList());
}
public IEnumerable<Person> Persons
{
get
{
foreach (var person in _persons)
{
// ...and here we return a copy instead of the contained object
yield return person.Copy();
}
}
}
}
Now, the program above will not alter the name of the Person
instance inside the list, but its own copy. However, note that we now have what can be called shallow immutability: if Person
in turn would have members that are not immutable, the same problem exists for those objects, and so on...
Eric Lippert wrote a 10-part series of blog post on the topic in 2007. The first part is here: Immutability in C# Part One: Kinds of Immutability.
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