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

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

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


当前回答

Yield return与枚举器一起使用。在yield语句的每次调用中,控制权都返回给调用方,但它确保被调用方的状态得到维护。因此,当调用方枚举下一个元素时,它将在yield语句之后立即在被调用方方法from语句中继续执行。

让我们通过一个例子来理解这一点。在这个例子中,对应于每一行,我已经提到了执行流的顺序。

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

此外,还为每个枚举维护状态。假设,我对Fibs()方法有另一个调用,那么它的状态将被重置。

其他回答

迭代。它创建了一个“隐藏的”状态机,它会记住您在函数的每个额外循环中的位置,并从那里开始处理。

这是一个非常简单和容易的方法来创建一个枚举对象。编译器创建了一个类来包装您的方法,并在本例中实现IEnumerable<object>。如果没有yield关键字,则必须创建一个实现IEnumerable<object>的对象。

yield上下文关键字实际上在这里做了很多事情。

该函数返回一个实现IEnumerable<object>接口的对象。如果调用函数开始覆盖该对象,则再次调用该函数,直到它“屈服”。这是c# 2.0中引入的语法糖。在早期的版本中,你必须创建你自己的IEnumerable和IEnumerator对象来做这样的事情。

理解这类代码的最简单方法是输入一个示例,设置一些断点,然后看看会发生什么。试试下面这个例子:

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;
}

当您逐步浏览这个示例时,您将发现对Integers()的第一个调用返回1。第二次调用返回2,并且不再执行yield return 1行。

下面是一个现实生活中的例子:

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

简单的演示,了解产量

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;
                }
            }
        }
    } 
}

列表或数组实现立即加载所有项,而yield实现提供了延迟执行的解决方案。

在实践中,为了减少应用程序的资源消耗,通常需要执行所需的最小工作量。

例如,我们可能有一个处理数据库中数百万条记录的应用程序。当我们在一个基于延迟执行的拉式模型中使用IEnumerable时,可以实现以下好处:

Scalability, reliability and predictability are likely to improve since the number of records does not significantly affect the application’s resource requirements. Performance and responsiveness are likely to improve since processing can start immediately instead of waiting for the entire collection to be loaded first. Recoverability and utilisation are likely to improve since the application can be stopped, started, interrupted or fail. Only the items in progress will be lost compared to pre-fetching all of the data where only using a portion of the results was actually used. Continuous processing is possible in environments where constant workload streams are added.

这里是先构建集合(如列表)与使用yield之间的比较。

列表的例子

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

控制台输出 ContactListStore:创建联系人 ContactListStore:创建联系人 ContactListStore:正在创建联系人 准备遍历集合。

注意:整个集合被加载到内存中,甚至没有请求列表中的任何项

收益率的例子

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

控制台输出 准备遍历集合。

注意:收集根本没有执行。这是由于IEnumerable的“延迟执行”特性。只有在真正需要时才会构造一个项。

让我们再次调用集合,并在取回集合中的第一个联系人时反转行为。

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

控制台输出 准备遍历集合 ContactYieldStore:创建联系人 你好,鲍勃

好了!当客户端从集合中“拉出”项目时,只构造了第一个联系人。