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

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

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


当前回答

乍一看,yield return是一个。net糖返回一个IEnumerable。

如果没有yield,集合中的所有项都是一次性创建的:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

同样的代码使用yield,它逐项返回:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

使用yield的优点是,如果使用数据的函数只需要集合的第一个项,则不会创建其余的项。

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()方法有另一个调用,那么它的状态将被重置。

下面是理解这个概念的简单方法: 其基本思想是,如果您想要一个可以使用“foreach”的集合,但是由于某些原因将项收集到集合中成本很高(例如从数据库中查询它们),并且通常不需要整个集合,那么您可以创建一个函数,每次构建一个项集合并将其返回给消费者(然后消费者可以提前终止收集工作)。

Think of it this way: You go to the meat counter and want to buy a pound of sliced ham. The butcher takes a 10-pound ham to the back, puts it on the slicer machine, slices the whole thing, then brings the pile of slices back to you and measures out a pound of it. (OLD way). With yield, the butcher brings the slicer machine to the counter, and starts slicing and "yielding" each slice onto the scale until it measures 1-pound, then wraps it for you and you're done. The Old Way may be better for the butcher (lets him organize his machinery the way he likes), but the New Way is clearly more efficient in most cases for the consumer.

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

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

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

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