有人可以揭开yield关键字的神秘面纱吗?

发布于 2024-08-02 10:01:43 字数 203 浏览 8 评论 0原文

我在 Stack Overflow 和博客上看到过很多次使用 Yield 关键字。我不使用 LINQ。有人可以解释一下yield关键字吗?

我知道也存在类似的问题。 但没有人真正用简单的语言解释它的用途。

I have seen the yield keyword being used quite a lot on Stack Overflow and blogs. I don't use LINQ. Can someone explain the yield keyword?

I know that similar questions exist.
But none really explain what is its use in plain simple language.

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

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

发布评论

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

评论(9

世态炎凉 2024-08-09 10:01:44

为了揭开神秘面纱,我将避免谈论迭代器,因为它们本身可能就是谜团的一部分。

Yield return 和 Yield Break 语句最常用于提供集合的“延迟评估”。

这意味着当你获取使用yield return的方法的值时,你试图获取的东西的集合还不存在(它本质上是空的)。当您循环遍历它们时(使用 foreach),它将执行当时的方法并获取枚举中的下一个元素。

某些属性和方法将导致整个枚举立即被计算(例如“Count”)。

下面是返回集合和返回yield之间差异的一个简单示例:

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };

public IEnumerable<string> GetYieldEnumerable()
{
    foreach (var name in names)
        yield return name;
}

public IEnumerable<string> GetList()
{
    var list = new List<string>();
    foreach (var name in names)
        list.Add(name);

    return list;
}

// we're going to execute the GetYieldEnumerable() method
// but the foreach statement inside it isn't going to execute
var yieldNames = GetNamesEnumerable();

// now we're going to execute the GetList() method and
// the foreach method will execute
var listNames = GetList();

// now we want to look for a specific name in yieldNames.
// only the first two iterations of the foreach loop in the 
// GetYieldEnumeration() method will need to be called to find it.
if (yieldNames.Contains("Jim")
    Console.WriteLine("Found Jim and only had to loop twice!");

// now we'll look for a specific name in listNames.
// the entire names collection was already iterated over
// so we've already paid the initial cost of looping through that collection.
// now we're going to have to add two more loops to find it in the listNames
// collection.
if (listNames.Contains("Jim"))
    Console.WriteLine("Found Jim and had to loop 7 times! (5 for names and 2 for listNames)");

如果您需要在源数据具有值之前获取对枚举的引用,也可以使用此示例。例如,如果名称收集一开始就不完整:

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };

public IEnumerable<string> GetYieldEnumerable()
{
    foreach (var name in names)
        yield return name;
}

public IEnumerable<string> GetList()
{
    var list = new List<string>();
    foreach (var name in names)
        list.Add(name);

    return list;
}

var yieldNames = GetNamesEnumerable();

var listNames = GetList();

// now we'll change the source data by renaming "Jim" to "Jimbo"
names[1] = "Jimbo";

if (yieldNames.Contains("Jimbo")
    Console.WriteLine("Found Jimbo!");

// Because this enumeration was evaluated completely before we changed "Jim"
// to "Jimbo" it isn't going to be found
if (listNames.Contains("Jimbo"))
    // this can't be true
else
   Console.WriteLine("Couldn't find Jimbo, because he wasn't there when I was evaluated.");

In an effort to demystify I'll avoid talking about iterators, since they could be part of the mystery themselves.

the yield return and yield break statements are most often used to provide "deferred evaluation" of the collection.

What this means is that when you get the value of a method that uses yield return, the collection of things you are trying to get don't exist together yet (it's essentially empty). As you loop through them (using foreach) it will execute the method at that time and get the next element in the enumeration.

Certain properties and methods will cause the entire enumeration to be evaluated at once (such as "Count").

Here's a quick example of the difference between returning a collection and returning yield:

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };

public IEnumerable<string> GetYieldEnumerable()
{
    foreach (var name in names)
        yield return name;
}

public IEnumerable<string> GetList()
{
    var list = new List<string>();
    foreach (var name in names)
        list.Add(name);

    return list;
}

// we're going to execute the GetYieldEnumerable() method
// but the foreach statement inside it isn't going to execute
var yieldNames = GetNamesEnumerable();

// now we're going to execute the GetList() method and
// the foreach method will execute
var listNames = GetList();

// now we want to look for a specific name in yieldNames.
// only the first two iterations of the foreach loop in the 
// GetYieldEnumeration() method will need to be called to find it.
if (yieldNames.Contains("Jim")
    Console.WriteLine("Found Jim and only had to loop twice!");

// now we'll look for a specific name in listNames.
// the entire names collection was already iterated over
// so we've already paid the initial cost of looping through that collection.
// now we're going to have to add two more loops to find it in the listNames
// collection.
if (listNames.Contains("Jim"))
    Console.WriteLine("Found Jim and had to loop 7 times! (5 for names and 2 for listNames)");

This can also be used if you need to get a reference to the Enumeration before the source data has values. For example if the names collection wasn't complete to start with:

string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" };

public IEnumerable<string> GetYieldEnumerable()
{
    foreach (var name in names)
        yield return name;
}

public IEnumerable<string> GetList()
{
    var list = new List<string>();
    foreach (var name in names)
        list.Add(name);

    return list;
}

var yieldNames = GetNamesEnumerable();

var listNames = GetList();

// now we'll change the source data by renaming "Jim" to "Jimbo"
names[1] = "Jimbo";

if (yieldNames.Contains("Jimbo")
    Console.WriteLine("Found Jimbo!");

// Because this enumeration was evaluated completely before we changed "Jim"
// to "Jimbo" it isn't going to be found
if (listNames.Contains("Jimbo"))
    // this can't be true
else
   Console.WriteLine("Couldn't find Jimbo, because he wasn't there when I was evaluated.");
猥︴琐丶欲为 2024-08-09 10:01:44

yield 关键字是编写 IEnumerator 的便捷方法。例如:

public static IEnumerator<int> Range(int from, int to)
{
    for (int i = from; i < to; i++)
    {
        yield return i;
    }
}

由 C# 编译器转换为类似于:

public static IEnumerator<int> Range(int from, int to)
{
    return new RangeEnumerator(from, to);
}

class RangeEnumerator : IEnumerator<int>
{
    private int from, to, current;

    public RangeEnumerator(int from, int to)
    {
        this.from = from;
        this.to = to;
        this.current = from;
    }

    public bool MoveNext()
    {
        this.current++;
        return this.current < this.to;
    }

    public int Current
    {
        get
        {
            return this.current;
        }
    }
}

The yield keyword is a convenient way to write an IEnumerator. For example:

public static IEnumerator<int> Range(int from, int to)
{
    for (int i = from; i < to; i++)
    {
        yield return i;
    }
}

is transformed by the C# compiler to something similiar to:

public static IEnumerator<int> Range(int from, int to)
{
    return new RangeEnumerator(from, to);
}

class RangeEnumerator : IEnumerator<int>
{
    private int from, to, current;

    public RangeEnumerator(int from, int to)
    {
        this.from = from;
        this.to = to;
        this.current = from;
    }

    public bool MoveNext()
    {
        this.current++;
        return this.current < this.to;
    }

    public int Current
    {
        get
        {
            return this.current;
        }
    }
}
貪欢 2024-08-09 10:01:44

请查看 MSDN 文档和示例。它本质上是在 C# 中创建迭代器的一种简单方法。

public class List
{
    //using System.Collections;
    public static IEnumerable Power(int number, int exponent)
    {
        int counter = 0;
        int result = 1;
        while (counter++ < exponent)
        {
            result = result * number;
            yield return result;
        }
    }

    static void Main()
    {
        // Display powers of 2 up to the exponent 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }
}

Take a look at the MSDN documentation and the example. It is essentially an easy way to create an iterator in C#.

public class List
{
    //using System.Collections;
    public static IEnumerable Power(int number, int exponent)
    {
        int counter = 0;
        int result = 1;
        while (counter++ < exponent)
        {
            result = result * number;
            yield return result;
        }
    }

    static void Main()
    {
        // Display powers of 2 up to the exponent 8:
        foreach (int i in Power(2, 8))
        {
            Console.Write("{0} ", i);
        }
    }
}
も让我眼熟你 2024-08-09 10:01:44

Eric White 的函数式编程系列非常值得完整阅读,但是 Yield 条目 非常清楚正如我所看到的解释。

Eric White's series on functional programming it well worth the read in it's entirety, but the entry on Yield is as clear an explanation as I've seen.

等风也等你 2024-08-09 10:01:44

yield 与 LINQ 没有直接关系,而是迭代器块。链接的 MSDN 文章 提供了有关此语言功能的详细信息。特别请参阅使用迭代器部分。有关迭代器块的详细信息,请参阅 Eric Lippert 最近的博客帖子 关于功能。有关一般概念,请参阅维基百科有关迭代器的文章

yield is not directly related to LINQ, but rather to iterator blocks. The linked MSDN article gives great detail on this language feature. See especially the Using Iterators section. For deep details of iterator blocks, see Eric Lippert's recent blog posts on the feature. For the general concept, see the Wikipedia article on iterators.

溇涏 2024-08-09 10:01: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 a .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 a 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-08-09 10:01:44

让我补充一下这一切。产量不是关键词。
仅当您使用“yield return”时它才会起作用,而不是像普通变量一样工作。

它用于从函数返回迭代器。您可以对此进行进一步搜索。
我建议搜索“返回数组与迭代器”

Let me add to all of this. Yield is not a keyword.
It will only work if you use "yield return" other than that it will work like a normal variable.

It's uses to return iterator from a function. You can search further on that.
I recommend searching for "Returning Array vs Iterator"

又怨 2024-08-09 10:01:43

到目前为止(我见过的)对此最好的解释是乔恩·斯基特(Jon Skeet)的书 - 并且该章是免费的!第 6 章,深入了解 C#。我可以在这里添加任何未涵盖的内容。

然后买书;你将因此成为一名更好的 C# 程序员。


问:为什么我不在这里写一个更长的答案(从评论中解释);简单的。正如埃里克·利珀特(Eric Lippert)观察到的(此处),yield 构造(及其背后的魔力)是C# 编译器中最复杂的代码位,试图在这里简短的回复中描述它充其量是天真的。 yield 有很多细微差别,在我看来,最好参考预先存在的(且完全合格的)资源。

Eric 的博客现在有 7 个讨论 yield 的条目(这只是最近的条目)。我对埃里克怀有极大的敬意,但他的博客可能更适合作为那些熟悉该主题的人的“更多信息”(yield 在本例中),因为它通常描述了许多背景设计考虑因素。最好在合理的基础上进行。

(是的,第 6 章确实下载;我已验证......)

By far the best explanation of this (that I've seen) is Jon Skeet's book - and that chapter is free! Chapter 6, C# in Depth. There is nothing I can add here that isn't covered.

Then buy the book; you will be a better C# programmer for it.


Q: Why didn't I write a longer answer here (paraphrased from comments); simple. As Eric Lippert observes (here), the yield construct (and the magic that goes behind it) is the single most complex bit of code in the C# compiler, and to try and describe it in a brief reply here is naïve at best. There are so many nuances to yield that IMO it is better to refer to a pre-existing (and fully qualified) resource.

Eric's blog now has 7 entries (and that is just the recent ones) discussing yield. I have a vast amount of respect for Eric, but his blog is probably more appropriate as a "further information" for people who are comfortable with the subject (yield in this case), as it typically describes a lot of the background design considerations. Best done in the context of a reasonable foundation.

(and yes, chapter 6 does download; I verified...)

箹锭⒈辈孓 2024-08-09 10:01:43

yield 关键字与返回 IEnumerableIEnumerator 的方法一起使用,它使编译器生成一个实现使用迭代器的必要管道。例如,

public IEnumerator<int> SequenceOfOneToThree() {
    yield return 1;
    yield return 2;
    yield return 3;
}

鉴于上述情况,编译器将生成一个实现 IEnumeratorIEnumerableIDisposable 的类(实际上它也会实现IEnumerableIEnumerator 的非通用版本)。

这允许您在 foreach 循环中调用方法 SequenceOfOneToThree,如下所示

foreach(var number in SequenceOfOneToThree) {
    Console.WriteLine(number);
}

迭代器是一个状态机,因此每次调用 yield 时位置方法中记录了。如果迭代器移动到下一个元素,该方法将在此位置之后立即恢复。因此第一次迭代返回 1 并标记该位置。下一个迭代器在 1 之后立即恢复,从而返回 2 等等。

不用说,您可以按照您喜欢的任何方式生成序列,因此您不必像我一样对数字进行硬编码。另外,如果您想中断循环,可以使用yield break

The yield keyword is used with methods that return IEnumerable<T> or IEnumerator<T> and it makes the compiler generate a class that implements the necessary plumbing for using the iterator. E.g.

public IEnumerator<int> SequenceOfOneToThree() {
    yield return 1;
    yield return 2;
    yield return 3;
}

Given the above the compiler will generate a class that implements IEnumerator<int>, IEnumerable<int> and IDisposable (actually it will also implement the non-generic versions of IEnumerable and IEnumerator).

This allows you to call the method SequenceOfOneToThree in a foreach loop like this

foreach(var number in SequenceOfOneToThree) {
    Console.WriteLine(number);
}

An iterator is a state machine, so each time yield is called the position in the method is recorded. If the iterator is moved to the next element, the method resumes right after this position. So the first iteration returns 1 and marks that position. The next iterator resumes right after one and thus returns 2 and so forth.

Needless to say you can generate the sequence in any way you like, so you don't have to hard code the numbers like I did. Also, if you want to break the loop you can use yield break.

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