c# – Yield does not return data

Question:

When executing the method call, an Enumerable of HTML components should be returned.

I'm using the HTML Agility Pack to read an HTML file. The same method works as expected when removing the yield and manually adding to a list

    HtmlNode slideCineAll = GetNodeById(cinema, "slide-cine-all");
    HtmlNode section = GetNodeByName(slideCineAll, "section");
    IEnumerable<HtmlNode> articles = GetNodesByName(section, "article");

    private static IEnumerable<HtmlNode> GetNodesByName(HtmlNode root, string node)
    {
        foreach (HtmlNode link in root.ChildNodes)
        {
            if (link.Name.Equals(node))
            {
                yield return link;
            }
        }
    }

    private static List<HtmlNode> GetNodesByNameList(HtmlNode root, string node)
    {
        List<HtmlNode> nodes = new List<HtmlNode>();
        foreach (HtmlNode link in root.ChildNodes)
        {
            if (link.Name.Equals(node))
            {
                nodes.Add(link);
            }
        }
        return nodes;
    }

This is the result stored in the variable when executing the method.

{ConsoleApplication1.Program.GetNodesByName}
node: null
root: null
System.Collections.Generic.IEnumerator<HtmlAgilityPack.HtmlNode>.Current: null
System.Collections.IEnumerator.Current: null

Expected result

values
Count = 20
[0]: Name: "article"}
.
.
.
values[0]
_attributes: {HtmlAgilityPack.HtmlAttributeCollection}
_childnodes: {HtmlAgilityPack.HtmlNodeCollection}
_endnode: Name: "article"}
.
.
.

This is the structure I'm going through, through the GetNodesByName or GetNodesByNameList method I can retrieve a list of any node of the html structure

<div id="slide-cine-all">
<section>
    <article>
        <!--mais elementos-->
    </article>
    <article>
        <!--mais elementos-->
    </article>
    <article>
        <!--mais elementos-->
    </article>
    <article>
        <!--mais elementos-->
    </article>
    <article>
        <!--mais elementos-->
    </article>
    <article>
        <!--mais elementos-->
    </article>
</section>
</div>

As described at the beginning, the GetNodesByNameList method returns all items, in this case of type article found in the file structure, but the same doesn't happen when I use yield.

Answer:

See the yield return documentation . He doesn't do what appears to be what you expect of him. The return part is important. When it gets to this line, it finishes executing the method. So your code only returns one element from the existing nodes in your XML.

There is more to return. You need to call the method more often. Each call will play a new line where it left off. The yield creates something called generator ( in English ). It controls execution through a hidden state that determines where in an enumeration the program is executing, so the call can pick up where it left off. Note that it returns an enumerable type and not the type you want in itself. This enumerable structure is what controls the continuity of execution where it left off.

So your problem is that you take a single node link and then try to search inside it as if there were other elements of that node. Of course there are no other elements, you haven't read them yet. This is where the problem arises.

In the other method that works by not having the yield , the loop runs completely and scans all nodes and returns a more complete tree which can then be searched without a problem. Everything you need is there.

It was not possible to understand the problem as a whole, but I think that in this case the yield is getting in the way. And I advise using it only when you fully understand how it works. It is excellent but it is not the solution to all problems. I'm not saying this problem can't benefit from it (generating efficiency by not scanning the entire structure but just what's needed at the moment) but I would need to change some things in the code that consumes this method. In practice when you use a yield , roughly speaking, you'll have another external loop to scan the entire structure you're searching for (of course you can do it manually also repeatedly).

I suggest inspecting the data and following the execution in the debug to better understand what is happening with the code. It might help to learn about how yield work and see your problem clearly, and maybe even find a better solution. Or else explain the problem in another way that I couldn't see any better than this.

To help understand run the following code taken from this answer on the OS :

public void Consumer() {
    foreach(int i in Integers()) {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers() {
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

Or this one from this OS answer :

// Display powers of 2 up to the exponent 8:
foreach (int i in Power(2, 8)) {
    Console.Write("{0} ", i);
}

public static IEnumerable<int> Power(int number, int exponent) {
    int counter = 0;
    int result = 1;
    while (counter++ < exponent) {
        result = result * number;
        yield return result;
    }
}

I put it on GitHub for future reference .

Did you notice that the consumer code always ends up having to repeat the calls, in a way the repetitions existing in the generator code (of the yield )? The big advantage of yield for most situations is that it creates better abstractions.

See a quick explanation of what the command's internals looks like . And a fuller explanation .

Scroll to Top