Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does linq not implement full-laziness?

Tags:

c#

linq

I have following code, I thought that it will work:

static int ComputerFailsOnTrue(bool flag)
{
    if (flag)
        throw new Exception();  
    return 10; // not relevant
}

static IEnumerable<double> StartComputer()
{
    yield return ComputerFailsOnTrue(true);
    yield return ComputerFailsOnTrue(false);
    yield return ComputerFailsOnTrue(false);
}

static public void Main()
{
    foreach (var item in StartComputer().Skip(1))
    {
        Console.WriteLine($"Hey {item}");
    }
    Console.ReadLine();
}

But it fails(i'll get the exception), because first element of collection will be computed. Why does moveNext method of enumerator from given collection always compute current element? Is it assumption, that computation of current can be dependant of previous state?

like image 605
LmTinyToon Avatar asked Jan 28 '26 04:01

LmTinyToon


1 Answers

It's not a failing of LINQ, per se. It's an issue related to two things - the IEnumerator<T> interface and C#'s iterator methods.

IEnumerator<T> only has two interesting members - MoveNext and Current. The only way to implement Skip atop such an interface1 is to call MoveNext as many times as the number of items you wish to skip, and of course, any implementation of MoveNext is free to run arbitrary code on each method call. What it can do is avoid accessing Current.

In C#'s implementation of iterators, which "automatically" generate implementations of IEnumerable<T> and IEnumerator<T>, the MoveNext and Current are intimately linked - you only write one method, and each time that method gains control (for a MoveNext), it also has to compute the next Current value.

If you were implementing IEnumerator<T> by hand, you would be free to place some logic in your MoveNext method and some logic within your Current property, including making the evaluation of Current lazy. In such an implementation, if the call to compute(true) was part of your implementation of Current, your code would work as you had expected.


1There may be some specializations within LINQ which bypasses using the enumerator for built-in collection classes, but in general, this is the interface that is used.

like image 148
Damien_The_Unbeliever Avatar answered Jan 29 '26 19:01

Damien_The_Unbeliever