I’ve always liked the idea behind websites like wtfjs, gathering together a collection of funny, counterintuitive features of a language to poke some good-natured fun at it. The sentiment is expressed well in its about page:
JavaScript is a language we love despite it giving us so much to hate.
Although I rarely write JavaScript anymore for my day job, this still mirrors my fondness for the language in the face of (or sometimes due to) all its funny quirks.
The first blog in this genre of language eccentricites I encountered was one for PHP after stumbling upon a real-life PHP WTF in the wild several years ago. I’m now regretting not keeping a record of what the specific WTF was in that case, and if I’m able to dig it up I’ll edit it in here.
Ever since then the huge majority of my work has been in the dotnet ecosystem, all the while thinking that there ought to be a WTF C# for cataloging its esoterica and pitfalls. Well, I’ve finally found something I consider worthy of the WTF.
Guids and IEnumerables
Consider the following:
IEnumerable<Guid> a = Enumerable.Range(1, 3)
.Select(_ => Guid.NewGuid());
IEnumerable<Guid> b = a
.Select(_ => _);
// 8b879243-9bea-49bc-a3ac-4828b1f6d5bc
// ee5fe3ef-69f2-4fda-983b-7c3b8c38148c
// 43c9fbdf-6b3e-4237-bb52-57ab42bb8d92
Console.WriteLine(string.Join(
Environment.NewLine,
a.Select(_ => _)));
// c16fe1af-197d-45a9-a5d1-2c2df9df4292
// 2e1dff3f-a3fa-4d33-8131-988032f53284
// ba0dce46-6b6d-4d73-a32e-04f38fbe2a70
Console.WriteLine(string.Join(
Environment.NewLine,
b.Select(_ => _)));
My intuition was that since the Guids in IEnumerable b
are being assigned directly from IEnumerable a
that they’d have the same values, but this is not the case.
The Guid.NewGuid()
call that happens in the first Select
call happens for every time the collection is enumerated.
A couple similar cases
This is due to multiple enumeration, not Guids in particular. To illustrate the principle:
DateTime start = DateTime.Now;
IEnumerable<int> c = Enumerable.Range(1, 3)
.Select(_ => (int)(DateTime.Now - start).TotalMilliseconds);
IEnumerable<int> d = c
.Select(_ => _);
// 15,15,15
Console.WriteLine(
string.Join(',', c.Select(_ => _)));
await Task.Delay(100);
// 117,117,117
Console.WriteLine(
string.Join(',', d.Select(_ => _)));
As you might expect, the same happens when using DateTime.Now
, since again the call happens each time the collection is iterated.
Maybe even more obvious is this:
int counter = 0;
IEnumerable<int> e = Enumerable.Range(1, 3)
.Select(_ => ++counter);
IEnumerable<int> f = e
.Select(_ => _);
// 1,2,3
Console.WriteLine(
string.Join(',', e.Select(_ => _)));
// 4,5,6
Console.WriteLine(
string.Join(',', f.Select(_ => _)));
Takeaway
The typical rule of thumb is to use the least specific interface suitable for a given purpose. In this case it seems like all we care about doing is iterating over the collection, so the IEnumerable
seems reasonable.
However, be wary of cases in which values are assigned in an iteration in a way which might change from call to call.
When in doubt, just enumerate the collection on the first call with something like .ToImmutableList()
.