Some help understanding “yield”

前端 未结 8 1141
挽巷
挽巷 2020-11-30 01:41

In my everlasting quest to suck less I\'m trying to understand the \"yield\" statement, but I keep encountering the same error.

The body of [someMetho

相关标签:
8条回答
  • 2020-11-30 02:14

    It's a tricky topic. In a nutshell, it's an easy way of implementing IEnumerable and its friends. The compiler builds you a state machine, transforming parameters and local variables into instance variables in a new class. Complicated stuff.

    I have a few resources on this:

    • Chapter 6 of C# in Depth (free download from that page)
    • Iterators, iterator blocks and data pipelines (article)
    • Iterator block implementation details (article)
    0 讨论(0)
  • 2020-11-30 02:17

    List implements Ienumerable.

    Here's an example that might shed some light on what you are trying to learn. I wrote this about 6 months

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace YieldReturnTest
    {
        public class PrimeFinder
        {
            private Boolean isPrime(int integer)
            {
                if (0 == integer)
                    return false;
    
                if (3 > integer)
                    return true;
    
                for (int i = 2; i < integer; i++)
                {
                    if (0 == integer % i)
                        return false;
                }
                return true;
            }
    
            public IEnumerable<int> FindPrimes()
            {
                int i;
    
                for (i = 1; i < 2147483647; i++)
                {
                    if (isPrime(i))
                    {
                        yield return i;
                    }
                }
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                PrimeFinder primes = new PrimeFinder();
    
                foreach (int i in primes.FindPrimes())
                {
                    Console.WriteLine(i);
                    Console.ReadLine();
                }
    
                Console.ReadLine();
                Console.ReadLine();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 02:18

    I highly recommend using Reflector to have a look at what yield actually does for you. You'll be able to see the full code of the class that the compiler generates for you when using yield, and I've found that people understand the concept much more quickly when they can see the low-level result (well, mid-level I guess).

    0 讨论(0)
  • 2020-11-30 02:20

    @Ian P´s answer helped me a lot to understand yield and why it is used. One (major) use case for yield is in "foreach" loops after the "in" keyword not to return a fully completed list. Instead of returning a complete list at once, in each "foreach" loop only one item (the next item) is returned. So you will gain performance with yield in such cases. I have rewritten @Ian P´s code for my better understanding to the following:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace YieldReturnTest
    {
        public class PrimeFinder
        {
            private Boolean isPrime(int integer)
            {
                if (0 == integer)
                    return false;
    
                if (3 > integer)
                    return true;
    
                for (int i = 2; i < integer; i++)
                {
                    if (0 == integer % i)
                        return false;
                }
                return true;
            }
    
            public IEnumerable<int> FindPrimesWithYield()
            {
                int i;
    
                for (i = 1; i < 2147483647; i++)
                {
                    if (isPrime(i))
                    {
                        yield return i;
                    }
                }
            }
    
            public IEnumerable<int> FindPrimesWithoutYield()
            {
                var primes = new List<int>();
                int i;
                for (i = 1; i < 2147483647; i++)
                {
                    if (isPrime(i))
                    {
                        primes.Add(i);
                    }
                }
                return primes;
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                PrimeFinder primes = new PrimeFinder();
    
                Console.WriteLine("Finding primes until 7 with yield...very fast...");
                foreach (int i in primes.FindPrimesWithYield()) // FindPrimesWithYield DOES NOT iterate over all integers at once, it returns item by item
                {
                    if (i > 7)
                    {
                        break;
                    }
                    Console.WriteLine(i);
                    //Console.ReadLine();
    
                }
    
                Console.WriteLine("Finding primes until 7 without yield...be patient it will take lonkg time...");
                foreach (int i in primes.FindPrimesWithoutYield()) // FindPrimesWithoutYield DOES iterate over all integers at once, it returns the complete list of primes at once
                {
                    if (i > 7)
                    {
                        break;
                    }
                    Console.WriteLine(i);
                    //Console.ReadLine();
                }
    
                Console.ReadLine();
                Console.ReadLine();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 02:23

    To understand yield, you need to understand when to use IEnumerator and IEnumerable (because you have to use either of them). The following examples help you to understand the difference.

    First, take a look at the following class, it implements two methods - one returning IEnumerator<int>, one returning IEnumerable<int>. I'll show you that there is a big difference in usage, although the code of the 2 methods is looking similar:

    // 2 iterators, one as IEnumerator, one as IEnumerable
    public class Iterator
    {
        public static IEnumerator<int> IterateOne(Func<int, bool> condition)
        {
            for(var i=1; condition(i); i++) { yield return i; }     
        }
        public static IEnumerable<int> IterateAll(Func<int, bool> condition)
        {
            for(var i=1; condition(i); i++) { yield return i; }     
        }
    }
    

    Now, if you're using IterateOne you can do the following:

        // 1. Using IEnumerator allows to get item by item
        var i=Iterator.IterateOne(x => true); // iterate endless
        // 1.a) get item by item
        i.MoveNext(); Console.WriteLine(i.Current);
        i.MoveNext(); Console.WriteLine(i.Current);
        // 1.b) loop until 100
        int j; while (i.MoveNext() && (j=i.Current)<=100) { Console.WriteLine(j); }
    

    1.a) prints:

    1
    2

    1.b) prints:

    3
    4
    ...
    100

    because it continues counting right after the 1.a) statements have been executed.

    You can see that you can advance item by item using MoveNext().


    In contrast, IterateAll allows you to use foreach and also LINQ statements for bigger comfort:

        // 2. Using IEnumerable makes looping and LINQ easier   
        var k=Iterator.IterateAll(x => x<100); // limit iterator to 100
        // 2.a) Use a foreach loop
        foreach(var x in k){ Console.WriteLine(x); } // loop
        // 2.b) LINQ: take 101..200 of endless iteration
        var lst=Iterator.IterateAll(x=>true).Skip(100).Take(100).ToList(); // LINQ: take items
        foreach(var x in lst){ Console.WriteLine(x); } // output list
    

    2.a) prints:

    1
    2
    ...
    99

    2.b) prints:

    101
    102
    ...
    200


    Note: Since IEnumerator<T> and IEnumerable<T> are Generics, they can be used with any type. However, for simplicity I have used int in my examples for type T.

    This means, you can use one of the return types IEnumerator<ProductMixHeader> or IEnumerable<ProductMixHeader> (the custom class you have mentioned in your question).

    The type List<ProductMixHeader> does not implement any of these interfaces, which is the reason why you can't use it that way. But Example 2.b) is showing how you can create a list from it.

    If you're creating a list by appending .ToList() then the implication is, that it will create a list of all elements in memory, while an IEnumerable allows lazy creation of its elements - in terms of performance, it means that elements are enumerated just in time - as late as possible, but as soon as you're using .ToList(), then all elements are created in memory. LINQ tries to optimize performance this way behind the scenes.

    DotNetFiddle of all examples

    0 讨论(0)
  • 2020-11-30 02:28

    "yield" creates an iterator block - a compiler generated class that can implement either IEnumerable[<T>] or IEnumerator[<T>]. Jon Skeet has a very good (and free) discussion of this in chapter 6 of C# in Depth.

    But basically - to use "yield" your method must return an IEnumerable[<T>] or IEnumerator[<T>]. In this case:

    public IEnumerable<AClass> SomeMethod() {
        // ...
        foreach (XElement header in headersXml.Root.Elements()){
            yield return (ParseHeader(header));                
        }
    }
    
    0 讨论(0)
提交回复
热议问题