产生关键词附加值?

发布于 2024-07-11 10:52:44 字数 488 浏览 12 评论 0原文

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

我看到这个主题的主题

yield 关键字用于什么在 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>

在这里使用。 看起来更简单..

still trying to find where i would use the "yield" keyword in a real situation.

I see this thread on the subject

What is the yield keyword used for in C#?

but in the accepted answer, they have this as an example where someone is iterating around Integers()

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

but why not just use

list<int>

here instead. seems more straightforward..

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

余罪 2024-07-18 10:52:44

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

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

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

当调用者说:

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

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

If you build and return a List (say it has 1 million elements), that's a big chunk of memory, and also of work to create it.

Sometimes the caller may only want to know what the first element is. Or they might want to write them to a file as they get them, rather than building the whole list in memory and then writing it to a file.

That's why it makes more sense to use yield return. It doesn't look that different to building the whole list and returning it, but it's very different because the whole list doesn't have to be created in memory before the caller can look at the first item on it.

When the caller says:

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

Each time the loop requires a new i, it runs a bit more of the code in Integers(). The code in that function is "paused" when it hits a yield return statement.

鱼窥荷 2024-07-18 10:52:44

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

Yield allows you to build methods that produce data without having to gather everything up before returning. Think of it as returning multiple values along the way.

Here's a couple of methods that illustrate the point

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

Neither of these two methods will read the whole contents of the file into memory, yet you can use them like this:

foreach (String lineWithEmail in LinesWithEmails(LinesFromFile("test.txt")))
    Console.Out.WriteLine(lineWithEmail);
久夏青 2024-07-18 10:52:44

您可以使用 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;
        }
    }

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

You can use yield to build any iterator. That could be a lazily evaluated series (reading lines from a file or database, for example, without reading everything at once, which could be too much to hold in memory), or could be iterating over existing data such as a List<T>.

C# in Depth has a free chapter (6) all about iterator blocks.

I also blogged very recently about using yield for smart brute-force algorithms.

For an example of the lazy file reader:

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

This is entirely "lazy"; nothing is read until you start enumerating, and only a single line is ever held in memory.

Note that LINQ-to-Objects makes extensive use of iterator blocks (yield). For example, the Where extension is essentially:

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

And again, fully lazy - allowing you to chain together multiple operations without forcing everything to be loaded into memory.

锦欢 2024-07-18 10:52:44

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

yield allows you to process collections that are potentially infinite in size because the entire collection is never loaded into memory in one go, unlike a List based approach. For instance an IEnumerable<> of all the prime numbers could be backed off by the appropriate algo for finding the primes, whereas a List approach would always be finite in size and therefore incomplete. In this example, using yield also allows processing for the next element to be deferred until it is required.

魔法唧唧 2024-07-18 10:52:44

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

想象一下这样的事情(伪代码):

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

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

A real situation for me, is when i want to process a collection that takes a while to populate more smoothly.

Imagine something along the lines (psuedo code):

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

Instead of having to wait a minute for the collection to populate before i can start processing items in it. I will be able to start immediately, and then report back to the user-interface as it happens.

再见回来 2024-07-18 10:52:44

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

但使用收益率也有好处。

  • Yield 提供了一种构建惰性评估迭代器的简单方法。 (意味着当调用 MoveNext() 方法时,仅执行按顺序获取下一个元素的代码,然后迭代器返回,不再进行任何计算,直到再次调用该方法为止)

  • Yield 在幕后构建了一个状态机,并且这无需对通用生成器的状态进行编码,从而节省了您的工作量 => 更简洁/简单的代码。

  • Yield 自动构建优化且线程安全的迭代器,让您无需了解如何构建它们的详细信息。

  • Yield 比乍一看要强大得多,并且不仅仅可以用于构建简单的迭代器,请观看此视频以了解 Jeffrey Richter 和他的 AsyncEnumerator 以及如何使用 Yield 使使用异步模式的编码变得容易。

You may not always want to use yield instead of returning a list, and in your example you use yield to actually return a list of integers. Depending on whether you want a mutable list, or a immutable sequence, you could use a list, or an iterator (or some other collection muttable/immutable).

But there are benefits to use yield.

  • Yield provides an easy way to build lazy evaluated iterators. (Meaning only the code to get next element in sequence is executed when the MoveNext() method is called then the iterator returns doing no more computations, until the method is called again)

  • Yield builds a state machine under the covers, and this saves you allot of work by not having to code the states of your generic generator => more concise/simple code.

  • Yield automatically builds optimized and thread safe iterators, sparing you the details on how to build them.

  • Yield is much more powerful than it seems at first sight and can be used for much more than just building simple iterators, check out this video to see Jeffrey Richter and his AsyncEnumerator and how yield is used make coding using the async pattern easy.

你列表最软的妹 2024-07-18 10:52:44

您可能想要迭代各种集合:

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

}

You might want to iterate through various collections:

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

}
写下不归期 2024-07-18 10:52:44

我同意每个人在这里所说的关于惰性求值和内存使用的所有内容,并且想添加另一个场景,在该场景中我发现使用 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,我的处理管道是完全可组合的,易于阅读,并且延迟评估,所以我只需要做我真正需要做的处理。 这让我可以将几乎所有处理都放在 GUI 线程中,而不会影响响应能力,因此我不必担心任何线程问题。

I agree with everything everyone has said here about lazy evaluation and memory usage and wanted to add another scenario where I have found the iterators using the yield keyword useful. I have run into some cases where I have to do a sequence of potentially expensive processing on some data where it is extremely useful to use iterators. Rather than processing the entire file immediately, or rolling my own processing pipeline, I can simply use iterators something like this:

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

The advantage here is that by keeping the input and output to all of the functions IEnumerable<double>, my processing pipeline is completely composable, easy to read, and lazy evaluated so I only have to do the processing I really need to do. This lets me put almost all of my processing in the GUI thread without impacting responsiveness so I don't have to worry about any threading issues.

日暮斜阳 2024-07-18 10:52:44

我想出这个来克服 .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();
        }
    }
}

I came up with this to overcome .net shortcoming having to manually deep copy List.

I use this:

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

And at another place:

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

I tried to come up with oneliner that does this, but it's not possible, due to yield not working inside anonymous method blocks.

EDIT:

Better still, use generic 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();
        }
    }
}
流年已逝 2024-07-18 10:52:44

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

The method used by yield of saving memory by processing items on-the-fly is nice, but really it's just syntactic sugar. It's been around for a long time. In any language that has function or interface pointers (even C and assembly) you can get the same effect using a callback function / interface.

This fancy stuff:

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

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

is basically equivalent to old-fashioned:

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());
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文