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

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

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


当前回答

它试图带来一些Ruby的善良:) 概念:这是一些示例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并没有完全做到这一点。c#似乎已经与IEnumerable耦合了yield,在某种程度上迫使你在调用者中写一个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]; 
}

其他回答

关于Yield关键字的一个主要观点是惰性执行。现在我所说的惰性执行是指在需要时执行。更好的表达方式是举个例子

示例:不使用Yield,即没有Lazy Execution。

public static IEnumerable<int> CreateCollectionWithList()
{
    var list =  new List<int>();
    list.Add(10);
    list.Add(0);
    list.Add(1);
    list.Add(2);
    list.Add(20);

    return list;
}

示例:使用Yield,即惰性执行。

public static IEnumerable<int> CreateCollectionWithYield()
{
    yield return 10;
    for (int i = 0; i < 3; i++) 
    {
        yield return i;
    }

    yield return 20;
}

当我调用两个方法时。

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();

你会注意到listItems里面有5个项目(调试时将鼠标悬停在listItems上)。 而yieldItems将只有一个对方法的引用,而不是对项目的引用。 这意味着它没有在方法中执行获取项的过程。一种只在需要时获取数据的非常有效的方法。 yield的实际实现可以在ORM中看到,如Entity Framework和NHibernate等。

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

c#的yield关键字,简单地说,允许多次调用一段被称为迭代器的代码,它知道如何在完成之前返回,并且当再次调用时,继续它离开的地方——也就是说,它帮助迭代器在连续调用中返回的序列中的每一项都具有透明的状态。

在JavaScript中,同样的概念被称为生成器。

下面是理解这个概念的简单方法: 其基本思想是,如果您想要一个可以使用“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.

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