C#中使用的yield关键字是什么?

时间:2020-03-05 18:46:26  来源:igfitidea点击:

在"如何仅显示IList <>的片段"问题中,答案之一具有以下代码片段:

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

yield关键字在那里做什么?我已经在几个地方提到过它,还有另一个问题,但是我还没有弄清楚它的实际作用。我习惯于从一个线程向另一个线程屈服的角度来考虑yield,但这在这里似乎无关紧要。

解决方案

回答

迭代。它在"隐藏"状态下创建一个状态机,该状态机会记住我们在该函数的每个其他循环中的位置,然后从那里开始。

回答

这是为对象创建枚举的非常简单的方法。编译器将创建一个包装方法的类,并实现IEnumerable <object>。如果没有yield关键字,则必须创建一个实现IEnumerable <object>的对象。

回答

它产生了不可计数的序列。它实际上是在创建本地IEnumerable序列并将其作为方法结果返回

回答

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

回答

yield关键字实际上在这里做了很多工作。该函数返回一个实现IEnumerable接口的对象。如果调用函数开始在该对象上进行遍历,则再次调用该函数,直到"屈服"为止。这是C2.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博客中的示例特别好,显示了如何编写异步安全代码)。

回答

它正在尝试带来一些Ruby Goodness :)
概念:这是一些示例Ruby代码,可打印出数组的每个元素

rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

数组的每个方法实现都将控制权交给调用者(" puts x"),而数组的每个元素都以x整齐地表示。然后,调用者可以执行x所需的任何操作。

但是,.Net并非一路走来。.似乎将yield与IEnumerable耦合在一起,以某种方式迫使我们在调用方中编写一个foreach循环,如Mendelt的响应所示。少一点优雅。

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}