在如何只能暴露IList<>的一个片段问题中,其中一个答案有以下代码片段:

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

yield关键字在这里做什么?我在一些地方看到过它,还有一个问题,但我还没搞清楚它到底是干什么的。我习惯于从一个线程屈服于另一个线程的意义上考虑屈服,但这在这里似乎无关紧要。


当前回答

简单的演示,了解产量

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp_demo_yield {
    class Program
    {
        static void Main(string[] args)
        {
            var letters = new List<string>() { "a1", "b1", "c2", "d2" };

            // Not yield
            var test1 = GetNotYield(letters);

            foreach (var t in test1)
            {
                Console.WriteLine(t);
            }

            // yield
            var test2 = GetWithYield(letters).ToList();

            foreach (var t in test2)
            {
                Console.WriteLine(t);
            }

            Console.ReadKey();
        }

        private static IList<string> GetNotYield(IList<string> list)
        {
            var temp = new List<string>();
            foreach(var x in list)
            {
                
                if (x.Contains("2")) { 
                temp.Add(x);
                }
            }

            return temp;
        }

        private static IEnumerable<string> GetWithYield(IList<string> list)
        {
            foreach (var x in list)
            {
                if (x.Contains("2"))
                {
                    yield return x;
                }
            }
        }
    } 
}

其他回答

c#的yield关键字,简单地说,允许多次调用一段被称为迭代器的代码,它知道如何在完成之前返回,并且当再次调用时,继续它离开的地方——也就是说,它帮助迭代器在连续调用中返回的序列中的每一项都具有透明的状态。

在JavaScript中,同样的概念被称为生成器。

最近Raymond Chen也发表了一系列关于yield关键字的有趣文章。

c#中迭代器的实现及其后果(第1部分) c#中迭代器的实现及其后果(第2部分) c#中迭代器的实现及其后果(第3部分) c#中迭代器的实现及其后果(第4部分)

虽然它名义上用于轻松实现迭代器模式,但可以泛化为状态机。没有必要引用Raymond,最后一部分也链接到其他用法(但Entin博客中的例子特别好,展示了如何编写异步安全代码)。

如果我正确地理解了这一点,下面是我如何从函数实现IEnumerable的角度来表达这一点。

这是一个。 如果你需要另一个,请再打电话。 我会记得我给你的。 你下次打电话来的时候我才知道能不能再给你。

关于Yield关键字的一个主要观点是惰性执行。现在我所说的惰性执行是指在需要时执行。更好的表达方式是举个例子

示例:不使用Yield,即没有Lazy Execution。

public static IEnumerable<int> CreateCollectionWithList()
{
    var list =  new List<int>();
    list.Add(10);
    list.Add(0);
    list.Add(1);
    list.Add(2);
    list.Add(20);

    return list;
}

示例:使用Yield,即惰性执行。

public static IEnumerable<int> CreateCollectionWithYield()
{
    yield return 10;
    for (int i = 0; i < 3; i++) 
    {
        yield return i;
    }

    yield return 20;
}

当我调用两个方法时。

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();

你会注意到listItems里面有5个项目(调试时将鼠标悬停在listItems上)。 而yieldItems将只有一个对方法的引用,而不是对项目的引用。 这意味着它没有在方法中执行获取项的过程。一种只在需要时获取数据的非常有效的方法。 yield的实际实现可以在ORM中看到,如Entity Framework和NHibernate等。

这个链接有一个简单的例子

这里还有更简单的例子

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

注意yield return不会从方法返回。你甚至可以在收益率后面加上一个WriteLine

上面生成了一个4个整数的IEnumerable (4,4,4,4)

这里有一个WriteLine。将向列表中添加4,打印abc,然后向列表中添加4,然后完成方法,从而真正从方法中返回(一旦方法完成,就像没有返回的过程一样)。但它会有一个值,一个IEnumerable int列表,它会在补全时返回。

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

还要注意,当使用yield时,返回的结果与函数的类型不同。它是IEnumerable列表中的元素类型。

将yield与方法的返回类型一起使用IEnumerable。如果方法的返回类型是int或List<int>并且使用yield,那么它将不会被编译。你可以在没有yield的情况下使用IEnumerable方法返回类型,但似乎你不能在没有IEnumerable方法返回类型的情况下使用yield。

为了让它执行,你必须用特殊的方式调用它。

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}