有人可以揭穿产量关键字?(Can someone demystify the yield keywo

2019-07-03 16:27发布

我已经看到了正在使用相当多的堆栈溢出和博客的产量关键字。 我不使用LINQ 。 有人可以解释yield关键字?

我知道,类似的问题存在。 但没有真正解释什么是它的朴素简单的语言使用。

Answer 1:

到目前为止,这方面最好的解释(我见过)是乔恩斯基特的书 - 这章是免费的! 第6章, C#中的深度 。 没有什么我可以在这里补充一点,不是盖的。

然后买的书; 你将是一个更好的C#程序员。


问:我为什么不写一个较长的答案在这里(从意见转述); 简单。 作为埃里克利珀观察( 这里 ),在yield结构(以及去其背后的魔法) 是在C#编译器代码的最复杂的位 ,并尝试和描述它在一份简短的答复这里是天真的最好的。 有这么多的细微差别来yield ,海事组织最好是指预先存在的(和完全合格的)资源。

Eric的博客现在有7项(这还只是近期的)讨论yield 。 我对埃里克·尊重了大量的 ,但他的博客可能更适合作为“更多信息”的人谁舒适的主题( yield在这种情况下),因为它通常描述了很多的背景设计考虑。 在合理的基础的情况下最好的做法。

(是的,第6章下载,我验证了...)



Answer 2:

yield关键字用于与返回方法IEnumerable<T>IEnumerator<T>它使编译器生成实现必需的管道使用迭代器的类。 例如

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

鉴于上述编译器的会产生一个实现类IEnumerator<int>IEnumerable<int>IDisposable (实际上它也将实现的非通用版本IEnumerableIEnumerator )。

这使您可以调用该方法SequenceOfOneToThreeforeach循环这样

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

迭代器是一个状态机,所以每次yield被称为在该方法中的位置被记录。 如果迭代被移动到下一个元素,该方法这个位置之后重新开始。 因此,在第一次迭代返回1,标志着该位置。 接下来的迭代器恢复后正确的,因而返回2等等。

不用说,你可以生成你喜欢的任何方式的顺序,这样你就不必硬编码的数字,像我一样。 另外,如果你想打破循环,可以使用yield break



Answer 3:

在努力神秘性,我会避免谈论迭代器,因为他们可能是谜本身的一部分。

收益率回报和产量break语句是最经常被用来提供集合的“延迟评估”。

这意味着,当你得到一个使用产回报的事情,你试图让不存在共同的收集又一个方法的值(它本质上是空的)。 当你遍历他们(使用的foreach),将执行当时的方法,并获得枚举中的下一个元素。

某些属性和方法将导致整个枚举一次进行评估(如“计数”)。

下面是返回集合并返回产量之间的差异的一个简单的例子:

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


Answer 4:

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


Answer 5:

看一看在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);
        }
    }
}


Answer 6:

埃里克·怀特在函数式编程系列非常值得读在它的全部,但对产量的条目是明确的,因为我已经看到了一个解释。



Answer 7:

yield是没有直接关系的LINQ,而是迭代器块 。 链接的MSDN 文章给出了这种语言的功能非常详细。 特别参见使用迭代器部分。 对于迭代器块的深详情,请参阅埃里克利珀最近的博客帖子上的功能。 对于一般的概念,请参阅Wikipedia 文章上的迭代器。



Answer 8:

我想出了这个克服不必手动深拷贝名单一个.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做这个工作,但它是不可能的,因为产生匿名方法块内部不能正常工作。

编辑:

更妙的是,使用通用名单拷贝机:

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


Answer 9:

让我加入到这一切。 产率不是关键字。 如果使用“回报收益率”以外,它会像一个正常的变量等它才会工作。

它是用来从一个函数返回迭代器。 您可以进一步对搜索。 我建议您搜索“返回数组VS迭代器”



文章来源: Can someone demystify the yield keyword?