为什么使用yield关键字,当我可以使用普通的IEnumerable时?

Jam*_*ght 171 c# yield

鉴于此代码:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么我不应该这样编码呢?:

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

我理解yield关键字的作用.它告诉编译器构建某种东西(迭代器).但为什么要用呢?除了代码略少之外,它对我有什么影响?

Rob*_*vey 239

使用yield使得集合变得懒惰.

假设您只需要前五项.你的方式,我必须遍历整个列表,以获得前五项.有了yield,我只循环前五项.

  • 请注意,使用`FullList.Where(IsItemInPartialList)`将同样懒惰.只是,它需要少得多的编译器生成的自定义--- gunk ---代码.减少开发人员编写和维护的时间.(当然,这就是这个例子) (14认同)
  • 不要忘记,如果yield return语句永远不会执行,您仍然会得到一个空的集合结果,因此无需担心空引用异常.巧克力洒,收益率很高. (11认同)
  • 那是Linq,不是吗?我想Linq做了一些非常类似的事情. (4认同)

Jon*_*eet 127

迭代器块的好处是它们可以懒惰地工作.所以你可以写一个像这样的过滤方法:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这样您就可以根据需要过滤流,而不是一次缓冲多个项目.例如,如果您只需要返回序列中的第一个值,为什么要将所有内容复制到新列表中?

再举一个例子,您可以使用迭代器块轻松创建无限流.例如,这是一系列随机数:

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

你如何在列表中存储无限序列?

我的Edulinq博客系列提供了LINQ to Objects的示例实现,它大量使用了迭代器块.LINQ从根本上说是懒惰的 - 并且把东西放在列表中根本就不起作用.

  • @SebastianNegraszus:一系列随机数在逻辑上是无限的.例如,您可以轻松创建表示Fibonacci序列的`IEnumerable <BigInteger>`.你可以使用`foreach`,但没有关于`IEnumerable <T>`保证它是有限的. (4认同)

Han*_*ing 42

使用"list"代码,您必须先处理完整列表,然后才能将其传递给下一步."yield"版本将处理后的项目立即传递到下一步.如果"下一步"包含".Take(10)",则"yield"版本将仅处理前10个项目而忘记其余项目."列表"代码将处理所有内容.

这意味着当您需要进行大量处理和/或需要处理很长的项目列表时,您会看到最大的差异.


Jas*_*ted 23

您可以使用它yield来返回不在列表中的项目.这是一个小样本,可以无限遍历列表直到被取消.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这写道

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
Run Code Online (Sandbox Code Playgroud)

...等等.到控制台直到取消.


hum*_*ner 10

object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;
Run Code Online (Sandbox Code Playgroud)

当上面的代码用于遍历FilteredList()并假设item.Name =="James"将在列表中的第二项上得到满足时,使用的方法yield将产生两次.这是一种懒惰的行为.

使用list的方法将所有n个对象添加到列表中并将完整列表传递给调用方法.

这正是IEnumerable和IList之间的区别可以突出显示的用例.


Mid*_*das 7

我见过的最好的现实世界例子yield是计算Fibonacci序列.

请考虑以下代码:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这将返回:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005
Run Code Online (Sandbox Code Playgroud)

这很好,因为它允许您快速,轻松地计算出无限系列,使您能够使用Linq扩展并仅查询您需要的内容.

  • 在Fibonacci序列计算中,我看不到任何"真实世界". (5认同)