7

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

我看到这个主题的主题

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

但在接受的答案中,他们以此为例,有人在 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>

而是在这里。似乎更直接..

4

10 回答 10

22

如果您构建并返回一个 List(假设它有 100 万个元素),那将是一大块内存,而且创建它也需要大量工作。

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

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

当来电者说:

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

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

于 2008-12-21T12:34:41.933 回答
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);
于 2008-12-21T12:38:46.827 回答
4

您可以使用它yield来构建任何迭代器。这可能是一个懒惰评估的系列(例如,从文件或数据库中读取行,而不是一次读取所有内容,这可能太多而无法保存在内存中),或者可能正在迭代现有数据,例如List<T>.

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

我最近还写了一篇关于使用智能蛮力算法的博客。yield

对于惰性文件阅读器的示例:

    static IEnumerable<string> ReadLines(string path) {
        using (StreamReader reader = File.OpenText(path)) {
            string line;
            while ((line = reader.ReadLine()) != null) {
                yield return line;
            }
        }
    }

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

请注意,LINQ-to-Objects广泛使用了迭代器块 ( yield)。例如,Where扩展本质上是:

   static IEnumerable<T> Where<T>(this IEnumerable<T> data, Func<T, bool> predicate) {
        foreach (T item in data) {
            if (predicate(item)) yield return item;
        }
    }

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

于 2008-12-21T12:35:46.270 回答
2

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

于 2008-12-21T12:38:58.137 回答
1

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

想象一下类似的东西(伪代码):

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

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

于 2008-12-21T12:43:06.740 回答
1

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

但是使用产量也有好处。

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

  • Yield 在幕后构建了一个状态机,通过不必编写通用生成器的状态 => 更简洁/简单的代码,这可以节省您分配的工作量。

  • Yield 自动构建优化和线程安全的迭代器,让您省去如何构建它们的细节。

  • Yield 比第一眼看上去要强大得多,并且可以用于构建简单的迭代器之外的其他用途,观看此视频以了解Jeffrey Richter 和他的 AsyncEnumerator以及如何使用 yield 使使用异步模式的编码变得容易。

于 2008-12-21T13:15:56.440 回答
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;
        }

}
于 2008-12-21T12:33:55.290 回答
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 线程中,而不会影响响应能力,因此我不必担心任何线程问题。

于 2008-12-21T13:04:04.933 回答
0

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

我用这个:

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,但这是不可能的,因为 yield 在匿名方法块中不起作用。

编辑:

更好的是,使用通用列表克隆器:

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}
于 2009-09-30T09:50:40.393 回答
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());
于 2010-02-17T07:14:50.177 回答