2

I am having trouble understanding some syntax and inner workings of C#. I have a class with a read-only property Bars set in the constructor.

If I do that using the InitializeWithYield method that yield returns something, the method is not called fom the constructor. It is instead called every time(!) the property is called. Not what I expected.

If I do it it using a 'plain' method, it works as I expected: initialize the property once and be done.

Clearly, I am not fully understanding the use of yield. My question is: why is the InitializeWithYield method not called from the constructor, but every time the property is called?

class Foo
{
    public Foo(int length)
    {
        // toggle these to see the issue
        Bars = InitializeWithYield(length);
        //Bars = InitializeCalledFromConstructor(length);
    }

    public void DoSomething()
    {
        IBar firstBar = Bars.FirstOrDefault(); // calls InitializeWithYield if in constructor 
        IList<IBar> listOfBar = Bars.ToList(); // calls InitializeWithYield if in constructor
        Console.Write($"First Bar has value {b.Value}");
    }

    IEnumerable<IBar> Bars { get; }

    // shorter syntax, but gets called every time the Bars property is used
    // also, not called from constructor
    IEnumerable<IBar> InitializeWithYield(int number)
    {
        for (int i = 0; i < number; i++)
        {
            yield return new Bar(i);
        }
    }

    // this only gets called from the constructor, which is what I want
    IEnumerable<IBar> InitializeCalledFromConstructor(int number)
    {
        IList<IBar> result = new List<IBar>();
        for (int i = 0; i < number; i++)
        {
            result.Add(new Bar(i));
        }
        return result;
    }
}
Rno
  • 784
  • 1
  • 6
  • 16
  • Call `.ToList()` or `.ToArray()` on `InitializeWithYield(length)` to actually fetch those items returned by the iterator method - they are not executed until you actually iterate over them (which ToList/ToArray do). I suggest stepping through it with a debugger or print output to understand how the methods are run. Maybe a better approach here would be to not offer a `Bars` property, and use `InitializeWithYield` method instead everywhere, renamed to `Bars`, with "_length" being a class member, but I don't know the actual meaning of this example class. – Ray Oct 19 '19 at 21:24
  • 1
    Thank you. I can see *how* your suggestion of adding `.ToList()` solves my issue, but to be honest I do not fully understand it. Why doesn't `InitializeWithYield` 'simply' run? I probably should look more deeply into 'iterator method'. – Rno Oct 19 '19 at 21:29
  • 1
    A good example of using iterator functions is filtering lists with custom logic. The caller can iterate over the collection which is returned element-by-element by the iterator function until he found the item he needs and then abort the iteration, _before_ the whole collection is iterated. Then, there is no need to reserve memory for the whole list which may have been returned. In fact, `System.Linq` methods work exactly like that. – Ray Oct 19 '19 at 23:04
  • That is in fact exactly what I am doing in my real-world application, except that I caught off-guard initializing the collection to work with. – Rno Oct 19 '19 at 23:34
  • You do need to keep the base collection you want to filter stored anywhere. That is typically in a property of type `ICollection` or `IList`. `IEnumerable` is _not_ a stored collection. It is only something that _can_ be enumerated _upon request_. I think if you make that intent clear by changing the property to `IList Bars`, you'll also see that you can't just assign it the "result" of the iterator method, as that is just an `IEnumerable`. – Ray Oct 20 '19 at 00:39
  • @Ray thank you. An interesting insight. I try to avoid Ilist and ICollection if I can (They are very useful, but why not store something in an IEnumerable if all you want is some gereric 'list' of something. The rationale behind that being if I do not need the overhead, why use it? That may be silly (or not). I am not a veteran C# programmer, it just seems to make sense to me. I might be wrong. – Rno Oct 21 '19 at 00:02
  • Using the least specific interface to combat breaking changes is correct, but it's also a question of what is expected from the class (s. all answers here https://stackoverflow.com/questions/3228708/what-should-i-use-an-ienumerable-or-ilist). For your sample, it looks like a generator of `Bar` objects. I'd personally put the `InitializeWithYield` code directly into the getter of `Bars`, and only store the `length` given in the constructor as a class readonly member to access it in there (as it seems to constantly limit the number of `Bar` creatable). – Ray Oct 21 '19 at 01:04

3 Answers3

2

It's deferred execution with lazy evaluation. Please see Deferred Execution Example (C#).

Deferred Execution and Lazy Evaluation in LINQ to XML (C#) is a great article explaining Deferred Execution and Eager vs. Lazy Evaluation.

I encourage you to read the whole short article, but here are two quotes from it.

Here's one about Deferred execution:

Deferred execution means that the evaluation of an expression is delayed until its realized value is actually required.

Here's one about Eager vs. Lazy Evaluation:

  • In lazy evaluation, a single element of the source collection is processed during each call to the iterator. This is the typical way in which iterators are implemented.
  • In eager evaluation, the first call to the iterator will result in the entire collection being processed. A temporary copy of the source collection might also be required. For example, the OrderBy method has to sort the entire collection before it returns the first element.

Here is another example to display laziness of IEnumerable.

    public static IEnumerable<DateTime> GetNow()
    {
        while (true) { yield return DateTime.Now; }
    }


    public static async Task Main()
    {
        foreach(var now in GetNow())
        {
            Console.WriteLine(now);
            await Task.Delay(TimeSpan.FromSeconds(1));
        }
    }

This will fetch the current time one by one, each second.

20/10/2019 08:00:45
20/10/2019 08:00:46
20/10/2019 08:00:47
20/10/2019 08:00:48
20/10/2019 08:00:49
tymtam
  • 31,798
  • 8
  • 86
  • 126
1

Enumerators behave quite differently than normal arrays, as evidenced by your code. Their values are lazily loaded or loaded on demand. This is different than a normal array where all values are loaded at once.

So then InitializeWithYield() gets called for each uninitialized value.

What is the yield keyword used for in C#?

live627
  • 397
  • 4
  • 18
0

Here is a quick/short answer, but I would recommend reading more at https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield

The yield/return statement in c# results into a state machine being built in the background and collection being navigated every time the value is accessed. A pointer is maintained and every time the accessor is being accessed the next value is returned.