Reputation: 3467
Before immutability, IEnumerable was the go-to interface in many APIs since this had the advantage that the API was insensitive to the actual type of the passed object.
public void DoSomeEnumerationsWithACollection(IEnumerable<Thing> collectionOfThings)
{
foreach (var thing in collectionOfThings) doSomethingWith(thing);
foreach (var thing in collectionOfThings) doSomethingElseWith(thing);
}
Of course there are at least two downsides to this:
The code behind the API can't rely on the immutability of collectionOfThings and may encounter a "collection modified" exception or hit other other subtle issues.
We don't know whether collectionOfThings is real collection or simply a deferred query. If we assume it's a real collection and it isn't we run the risk of degrading performance by running multiple enumerations. If we assume it's a deferred query and it's actually a real collection then turning it into a local list or other frozen collection incurs unnecessary cost although it does help protect us against the first problem (there's still a race condition whilst performing the "ToList" operation). Obviously we can write a small amount of code to check for this and try to do the "right thing" but this is annoying extra clutter.
I must admit I have never found a satisfactory pattern to address this other than using naming conventions. The pragmatic approach seemed to be that IEnumerable was the lowest friction approach for passing around collections, despite the downsides.
Now, with immutable collections, the situation is much improved...
public void DoSomeEnumerationsWithACollection(ImmutableList<Thing> collectionOfThings)
{
There is no longer a risk of collection modification and there's no ambiguity about the performance impact of multiple enumerations.
However, we have apparently lost flexibility on the API since we now have to pass in an ImmutableList. If our client had some other kind of enumerable immutable collection, it would have to be copied into an ImmutableList in order to be consumed even though all we want to do is enumerate it.
Ideally we'd be able to use an interface like
public void DoSomeEnumerationsWithACollection(IImmutableEnumerable<Thing> collectionOfThings)
but of course, an interface can't enforce semantics like immutability except by convention.
Using a base class might work
public void DoSomeEnumerationsWithACollection(ImmutableEnumerableBase<Thing> collectionOfThings)
except that it's considered bad form to create unsealed immutable classes lest a subclass introduce mutability. And in any case, this hasn't been done in the BCL.
Or we could just keep using IEnumerable in the API and using a naming convention to make it clear our code relies upon an immutable collection to be passed in.
So... my question is what patterns are considered best when passing around immutable collections? Is ImmutableList the "new IEnumerable" once we start using immutablity or is there a better way?
Update
IReadOnlyCollection (suggested by Yuval Itzchakov below ) is a distinct improvement over IEnumerable but still does not fully protect the consumer against uncontrolled changes in the collection. It's notable that the Roslyn codebase makes heavy use of immutability (mainly via ImmutableArray) and appears to use explicit typing when passing these into other methods though there are a couple of locations where ImmutableLists are passed into methods that accept IEnumerable.
Upvotes: 17
Views: 2007
Reputation: 3467
After a few years of using immutable collections heavily, I have settled on the convention of using ImmutableArray almost everywhere. This doesn't address the original desire to allow flexibility in the API but in practice it's rare that I use ImmutableList or another list-like structure and when I do, it's usually not much overhead to call ToImmutableArray to get the data across the interface.
Upvotes: 5
Reputation: 149538
what patterns are considered best when passing around immutable collections?
I think the answer to your question is IReadOnlyCollection<T>
, which will be spreading in .NET 4.6. By passing a read only collection, you can both keep immutability, and still work with regular collections implementing that interface.
Upvotes: 1