Why use the yield keyword, when I could just use a

2019-01-29 15:26发布

Given this code:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}

Why should I not just code it this way?:

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

I sort of understand what the yield keyword does. It tells the compiler to build a certain kind of thing (an iterator). But why use it? Apart from it being slightly less code, what's it do for me?

标签: c# yield
8条回答
【Aperson】
2楼-- · 2019-01-29 15:49

Using yield makes the collection lazy.

Let's say you just need the first five items. Your way, I have to loop through the entire list to get the first five items. With yield, I only loop through the first five items.

查看更多
淡お忘
3楼-- · 2019-01-29 15:56

why use [yield]? Apart from it being slightly less code, what's it do for me?

Sometimes it is useful, sometimes not. If the entire set of data must be examined and returned then there is not going to be any benefit in using yield because all it did was introduce overhead.

When yield really shines is when only a partial set is returned. I think the best example is sorting. Assume you have a list of objects containing a date and a dollar amount from this year and you would like to see the first handful (5) records of the year.

In order to accomplish this, the list must be sorted ascending by date, and then have the first 5 taken. If this was done without yield, the entire list would have to be sorted, right up to making sure the last two dates were in order.

However, with yield, once the first 5 items have been established the sorting stops and the results are available. This can save a large amount of time.

查看更多
闹够了就滚
4楼-- · 2019-01-29 15:57

You can use yield to return items that aren't in a list. Here's a little sample that could iterate infinitely through a list until canceled.

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

This writes

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

...etc. to the console until canceled.

查看更多
你好瞎i
5楼-- · 2019-01-29 15:57

The best real world example I've seen for the use of yield would be to calculate a Fibonacci sequence.

Consider the following code:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

This will return:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

This is nice because it allows you to calculate out an infinite series quickly and easily, giving you the ability to use the Linq extensions and query only what you need.

查看更多
我欲成王,谁敢阻挡
6楼-- · 2019-01-29 16:04
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

When the above code is used to loop through FilteredList() and assuming item.Name == "James" will be satisfied on 2nd item in the list, the method using yield will yield twice. This is a lazy behavior.

Where as the method using list will add all the n objects to the list and pass the complete list to the calling method.

This is exactly a use case where difference between IEnumerable and IList can be highlighted.

查看更多
乱世女痞
7楼-- · 2019-01-29 16:05

The benefit of iterator blocks is that they work lazily. So you can write a filtering method like this:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
                                   Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

That will allow you to filter a stream as long as you like, never buffering more than a single item at a time. If you only need the first value from the returned sequence, for example, why would you want to copy everything into a new list?

As another example, you can easily create an infinite stream using iterator blocks. For example, here's a sequence of random numbers:

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

How would you store an infinite sequence in a list?

My Edulinq blog series gives a sample implementation of LINQ to Objects which makes heavy use of iterator blocks. LINQ is fundamentally lazy where it can be - and putting things in a list simply doesn't work that way.

查看更多
登录 后发表回答