What's wrong with this code?
// C#:
int Sum(IEnumerable<int> items)
{
int total = 0;
foreach(var item in items)
{
total += item;
}
return total;
}
// F#
let sum (items: seq<int>) =
let mutable total = 0
for item in items do
total <- total + item
total
If you said, "calling this function may hang forever", you'd be right. Here's a valid value for IEnumerable<int>
(seq<int>
in F#) that we could pass into our Sum
function:
// C#
IEnumerable<int> GenerateValues()
{
while (true)
{
yield return 1;
}
}
// F#
let generateValues () =
Seq.initInfinite id
Now, Sum(GenerateValues())
hangs forever.
What's wrong with this code?
// C#
interface IConfigValuesProvider
{
IEnumerable<ConfigValue> ConfigValues { get; }
}
var values = myConfigValueProvider.ConfigValues;
if (values.Count() > 0)
{
foreach(var v in values) { ... }
}
// F#
type IConfigValuesProvider =
abstract ConfigValues : seq<ConfigValues>
let values = myConfigValueProvider.ConfigValues
if Seq.length values > 0 then
for v in values do
...
Well, nothing, probably, but you can't really be sure. Since ConfigValues
is an IEnumerable<T>
, it could technically be infinite, causing Count
/Seq.length
to hang, although, granted, that's unlikely. What sounds more plausible is that ConfigValues
is implemented as the output of calls to Where
or Select
, and that iterating it is computationally expensive. Note that we're potentially causing it to be iterated twice here (calling Count
and then doing a foreach
), which would be bad if it was computationally expensive. Of course, the caller expects this to behave like an Array-like collection, but as a matter of fact, IEnumerable<T>
makes no such promises. A potential solution here would be calling ConfigValue.ToArray()
and using the result of that, but that's going to create a pointless copy if ConfigValues
is actually just a collection. We can't know, so either we make unsafe assumptions, or we code defensively and waste resources.
IEnumerable<T>
/seq<T>
does not represent a collection of items, so it should not be used to represent collections of items.
So what should we use? Is there another interface that all collections implement and provide a guarantee that it's actually a collection? Yes, there is, and it's called IReadOnlyCollection<T>
, introduced in .NET 4.5, August 15th, 2012. If you want to be more specific and get true array-like behavior (with indexing support), IReadOnlyList<T>
has got your back. I'm leaning towards IReadOnlyList<T>
for everything that has an index (arrays, List<T>
, ImmutableArray<T>
, etc.) and IReadOnlyCollection<T>
for everything else (LinkedList<T>
, Queue<T>
, Stack<T>
, etc.) There might be cases where concrete immutable collections like ImmutableArray<T>
are better though, especially as a return type. Returning a concrete type seems to provide greater value. I haven't found great, clear guidance on e.g. IReadOnlyList<T>
vs ImmutableArray<T>
; if you have an opinion on that, please share!
Finally, if IEnumerable<T>
does not represent collections, and we have a perfect replacement for it, what use does it have?
IEnumerable<T>
is anything that supports enumeration: collections, infinite sequences, computations of successive values. It is the perfect type to define mappings over arbitrary, non-finite sequences of data. Think of LINQ operators Where
, Select
, which also return IEnumerable<T>
: those make perfect sense and could not use a better type. Do not think of scalar-returning functions like Enumerable.Sum
or Enumerable.Count
which IMO shouldn't exist, as they don't make sense (and won't work) over anything but finite collections.
I suspect that the late addition of IReadOnlyCollection<T>
and friends to .NET is largely to blame for the widespread use of IEnumerable<T>
as a read-only view of a collection. After all, IEnumerable<T>
arrived in .NET 2, early 2006, and System.Linq
arrived in .NET 3.5, late 2007. F#'s Seq
module, probably designed around the same time, is full of the same issues as System.Linq
: Seq.sum
, Seq.length
, Seq.append
, Seq.toList
, Seq.toArray
, etc. are all very practical, but definitely not sound.
Top comments (0)