收益率关键字增值?

时间:2008-12-21 12:24:06

标签: c# iterator ienumerable yield

仍然试图找到在实际情况下我会在哪里使用“yield”关键字。

我在主题上看到了这个主题

What is the yield keyword used for in C#?

但是在接受的答案中,他们将此作为一个例子,其中某人正在迭代Integers()

public IEnumerable<int> Integers()
{
yield return 1;
yield return 2;
yield return 4;
yield return 8;
yield return 16;
yield return 16777216;
}

但为什么不使用

list<int>

在这里。似乎更直接..

10 个答案:

答案 0 :(得分:23)

如果你构建并返回一个List(比如它有100万个元素),那就是一大块内存,也是创建它的工作。

有时调用者可能只想知道第一个元素是什么。或者他们可能希望在获取文件时将它们写入文件,而不是在内存中构建整个列表,然后将其写入文件。

这就是为什么使用收益率回报更有意义。它与构建整个列表并返回它看起来并没有什么不同,但它是非常不同的,因为在调用者可以查看其中的第一个项目之前,不必在内存中创建整个列表。

来电者说:

foreach (int i in Integers())
{
   // do something with i
}

每次循环需要一个新的i时,它会在Integers()中运行更多的代码。该函数中的代码在遇到yield return语句时会“暂停”。

答案 1 :(得分:9)

Yield允许您构建生成数据的方法,而无需在返回之前收集所有内容。把它想象成沿途返回多个值。

以下是一些说明要点的方法

public IEnumerable<String> LinesFromFile(String fileName)
{
    using (StreamReader reader = new StreamReader(fileName))
    {
        String line;
        while ((line = reader.ReadLine()) != null)
            yield return line;
    }
}

public IEnumerable<String> LinesWithEmails(IEnumerable<String> lines)
{
    foreach (String line in lines)
    {
        if (line.Contains("@"))
            yield return line;
    }
}

这两种方法都不会将文件的全部内容读入内存,但您可以像这样使用它们:

foreach (String lineWithEmail in LinesWithEmails(LinesFromFile("test.txt")))
    Console.Out.WriteLine(lineWithEmail);

答案 2 :(得分:4)

您可以使用yield构建任何迭代器。这可能是一个懒惰的评估系列(例如,从文件或数据库读取行,而不是一次性读取所有内容,这可能在内存中保留太多),或者可能在现有数据上进行迭代,例如{{1} }。

C# in Depth有一个关于迭代器块的免费章节(6)

我最近也blogged使用List<T>进行智能强力算法。

有关惰性文件阅读器的示例:

yield

这完全是“懒惰”;在您开始枚举之前,没有被读取,并且只有一行被保存在内存中。

请注意,LINQ到对象使扩展使用迭代器块( static IEnumerable<string> ReadLines(string path) { using (StreamReader reader = File.OpenText(path)) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } } )。例如,yield扩展名基本上是:

Where

再次,完全懒惰 - 允许您将多个操作链接在一起,而不必强制将所有内容加载到内存中。

答案 3 :(得分:2)

yield允许您处理可能无限大小的集合,因为整个集合永远不会一次性加载到内存中,这与基于List的方法不同。例如,IEnumerable&lt;&gt;所有素数都可以通过适当的算法来找回素数,而列表方法的大小总是有限的,因此不完整。在此示例中,使用yield还允许处理下一个元素,直到需要它为止。

答案 4 :(得分:1)

对我来说真实的情况是,当我想处理一个需要一段时间才能更顺畅地填充的集合时。

想象一下(PSuedo代码):

public IEnumberable<VerboseUserInfo> GetAllUsers()
{
    foreach(UserId in userLookupList)
    {
        VerboseUserInfo info = new VerboseUserInfo();

        info.Load(ActiveDirectory.GetLotsOfUserData(UserId));
        info.Load(WebSerice.GetSomeMoreInfo(UserId));

        yield return info;
    }
}

在我开始处理其中的项目之前,不必等待集合填充。我将能够立即启动,然后在发生时向用户界面报告。

答案 5 :(得分:1)

您可能并不总是希望使用yield而不是返回列表,并且在您的示例中,您使用yield来实际返回整数列表。根据您是否需要可变列表或不可变序列,您可以使用列表或迭代器(或其他一些muttable / immutable集合)。

但使用产量有好处。

  • Yield提供了一种构建惰性求值迭代器的简便方法。 (意味着只有在调用MoveNext()方法时才会执行顺序获取下一个元素的代码,然后迭代器返回不再执行计算,直到再次调用该方法)

  • Yield构建了一个状态机,它不需要对通用生成器的状态进行编码,从而为您节省了大量的工作量。更简洁/简单的代码。

  • Yield会自动构建优化的和线程安全的迭代器,为您提供有关如何构建它们的详细信息。

  • 产量比初看起来要强大得多,并且不仅可以用于构建简单的迭代器,还可以查看此视频以查看Jeffrey Richter and his AsyncEnumerator以及如何使用yield进行编码异步模式很容易。

答案 6 :(得分:0)

您可能想要遍历各种集合:

public IEnumerable<ICustomer> Customers()
{
        foreach( ICustomer customer in m_maleCustomers )
        {
            yield return customer;
        }

        foreach( ICustomer customer in m_femaleCustomers )
        {
            yield return customer;
        }

        // or add some constraints...
        foreach( ICustomer customer in m_customers )
        {
            if( customer.Age < 16 )
            {
                yield return customer;
            }
        }

        // Or....            
        if( Date.Today == 1 )
        {
            yield return m_superCustomer;
        }

}

答案 7 :(得分:0)

我同意大家在这里所说的关于延迟评估和内存使用的所有内容,并希望添加另一个场景,我发现使用yield关键字有用的迭代器。我遇到过一些情况,我必须对某些数据执行一系列可能很昂贵的处理,其中使用迭代器非常有用。我可以简单地使用这样的迭代器,而不是立即处理整个文件,或者滚动我自己的处理管道:

IEnumerable<double> GetListFromFile(int idxItem)
{
    // read data from file
    return dataReadFromFile;
}

IEnumerable<double> ConvertUnits(IEnumerable<double> items)
{
    foreach(double item in items)
        yield return convertUnits(item);
}

IEnumerable<double> DoExpensiveProcessing(IEnumerable<double> items)
{
    foreach(double item in items)
        yield return expensiveProcessing(item);
}

IEnumerable<double> GetNextList()
{
    return DoExpensiveProcessing(ConvertUnits(GetListFromFile(curIdx++)));
}

这里的优点是通过保持所有函数IEnumerable<double>的输入和输出,我的处理管道是完全可组合的,易于阅读和延迟评估,所以我只需要进行我真正需要的处理去做。这让我几乎可以将所有处理都放在GUI线程中,而不会影响响应性,所以我不必担心任何线程问题。

答案 8 :(得分:0)

我想出了这个来克服.net的缺点,必须手动深度复制List。

我用这个:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

在另一个地方:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

我试图找到执行此操作的oneliner,但由于无法在匿名方法块中工作,因此无法实现。

编辑:

更好的是,使用通用List cloner:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}

答案 9 :(得分:0)

yield通过即时处理项目来节省内存的方法很不错,但实际上它只是语法糖。它已存在很长时间了。在任何具有函数或接口指针(甚至C和汇编)的语言中,您都可以使用回调函数/接口获得相同的效果。

这个花哨的东西:

static IEnumerable<string> GetItems()
{
    yield return "apple";
    yield return "orange";
    yield return "pear";
}

foreach(string item in GetItems())
{
    Console.WriteLine(item);
}

基本上等同于老式的:

interface ItemProcessor
{
    void ProcessItem(string s);
};

class MyItemProcessor : ItemProcessor
{
    public void ProcessItem(string s)
    {
        Console.WriteLine(s);
    }
};

static void ProcessItems(ItemProcessor processor)
{
    processor.ProcessItem("apple");
    processor.ProcessItem("orange");
    processor.ProcessItem("pear");
}

ProcessItems(new MyItemProcessor());