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

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

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


当前回答

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

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

其他回答

它产生了可枚举的序列。它所做的实际上是创建本地IEnumerable序列,并将其作为方法结果返回

现在你可以在异步流中使用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;
        }
    }
}

屈服有两大用处,

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

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

列表或数组实现立即加载所有项,而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:创建联系人 你好,鲍勃

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

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

这里还有更简单的例子

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