The first code is eager:
public static IEnumerable<object[]> Data() {
return new List<object[]>
{
new object[] { 1, 2, 3 },
new object[] { -1, 1, 0 }
};
}
As you can see, it constructs a list object, and returns it. The list is complete before any use of it. Plus, consumer code can cast it to List, at virtually no cost.
On the other hand, the second code is lazy:
public static IEnumerable<object[]> Data2() {
yield return new object[] { 1, 2, 3 };
yield return new object[] { -1, 1, 0 };
}
Iterating over this will create and return each item at a time. The compiler will rewrite this method as what is essentially a co-routine (See the Wikipedia article Generator (computer programming)). For this case, that is of little relevance. However, if there were many items (infinite, even), or allocating them was expensive, this has added value.
If you are interested in how the code is rewritten. You may have fun with the above code in Sharplab. See also Behind the scenes of the C# yield keyword.
Furthermore, when you start composing enumerators (via functions that take an enumerator and return another enumerator, such as the ones you find in Linq), lazy evaluation becomes even more important. For once, it allows to separate the concern of where to stop iterating.
Also, the final logic for each item can become arbitrarily complex via this kind of composition. With the eager approach, you would finish each step for all items before moving to the next step. With the lazy approach, you would finish all the steps for the first item before moving to the next item… And no, that is not necessarily a gain in performance, yet it might be a gain in responsiveness.
I strongly recommend the conference talk Øredev 2013 - Jessica Kerr - Functional Principles for Object-Oriented Developers. In particular, you would want the title "Declarative Style" after the minute 33.
I'll lift part of what is mentioned in that talk here:
Laziness is essential, because, without laziness, as soon as I say filter
it would have to go over every line in the log file (which haven't been all written yet), and as soon as I say transform
it would have to go on all of those. But no, it just pulls through one at a time, and then that limit
function at the end, at 40 it just says "oh, work's done".