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

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

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


当前回答

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

其他回答

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

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

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

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

现在你可以在异步流中使用yield关键字。

c# 8.0引入了异步流,它对流数据源进行了建模。数据流通常异步检索或生成元素。异步流依赖于。net Standard 2.1中引入的新接口。. net Core 3.0及更高版本支持这些接口。它们为异步流数据源提供了自然的编程模型。 来源:微软文档

在下面的例子

using System;
using System.Collections.Generic;               
using System.Threading.Tasks;

public class Program
{
    public static async Task Main()
    {
        List<int> numbers = new List<int>() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        
        await foreach(int number in YieldReturnNumbers(numbers))
        {
            Console.WriteLine(number);
        }
    }
    
    public static async IAsyncEnumerable<int> YieldReturnNumbers(List<int> numbers) 
    {
        foreach (int number in numbers)
        {
            await Task.Delay(1000);
            yield return number;
        }
    }
}

直观地,关键字从函数中返回一个值而不离开它,即在您的代码示例中,它返回当前项的值,然后继续循环。更正式地说,它被编译器用来为迭代器生成代码。迭代器是返回IEnumerable对象的函数。MSDN上有几篇关于它们的文章。

屈服有两大用处,

它有助于在不创建临时集合的情况下提供自定义迭代。 它有助于进行有状态迭代。

为了更清楚地解释以上两点,我制作了一个简单的视频,你可以在这里观看