在Nerd Dinner教程中有趣地使用C#yield关键字

dev*_*xer 13 c# ienumerable yield

通过一个教程(Professional ASP.NET MVC - Nerd Dinner),我遇到了这段代码:

public IEnumerable<RuleViolation> GetRuleViolations() {
    if (String.IsNullOrEmpty(Title))
        yield return new RuleViolation("Title required", "Title");
    if (String.IsNullOrEmpty(Description))
        yield return new RuleViolation("Description required","Description");
    if (String.IsNullOrEmpty(HostedBy))
        yield return new RuleViolation("HostedBy required", "HostedBy");
    if (String.IsNullOrEmpty(Address))
        yield return new RuleViolation("Address required", "Address");
    if (String.IsNullOrEmpty(Country))
        yield return new RuleViolation("Country required", "Country");
    if (String.IsNullOrEmpty(ContactPhone))
        yield return new RuleViolation("Phone# required", "ContactPhone");
    if (!PhoneValidator.IsValidNumber(ContactPhone, Country))
        yield return new RuleViolation("Phone# does not match country", "ContactPhone");
    yield break;
}
Run Code Online (Sandbox Code Playgroud)

我已经读过了yield,但我想我的理解仍然有点朦胧.它似乎做的是创建一个对象,允许循环遍历集合中的项目而不实际进行循环,除非并且直到它是绝对必要的.

不过,这个例子对我来说有点奇怪.我认为它正在做的是延迟任何RuleViolation实例的创建,直到程序员使用或者for eachLINQ扩展方法实际请求集合中的特定项.ElementAt(2).

不过,除此之外,我还有一些问题:

  1. 什么时候对if语句的条件部分进行评估?何时GetRuleViolations()调用或实际迭代枚举?换句话说,如果该值Title的变化来自nullReally Geeky Dinner我打电话的时间之间GetRuleViolations(),我试图其实遍历它的时候,会RuleViolation("Title required", "Title")创建或不?

  2. 为什么有yield break;必要?它到底在做什么?

  3. 假设Title为null或为空.如果我调用GetRuleViolations()然后连续两次迭代结果可枚举,将new RuleViolation("Title required", "Title")调用多少次?

Avi*_* P. 17

包含yield命令的函数与普通函数的处理方式不同.调用该函数时幕后发生的事情是,匿名类型是由函数的特定IEnumerable类型构成的,该函数创建该类型的对象并返回它.匿名类包含执行函数体的逻辑,直到yield每次IEnumerable.MoveNext调用时都执行下一个命令.这有点误导,函数的主体不像普通函数那样在一个批处理中执行,而是分段执行,每个部分在枚举器向前移动一步时执行.

关于你的问题:

  1. 正如我所说,if当你迭代到下一个元素时,每个都会被执行.
  2. yield break在上面的例子中确实没有必要.它的作用是终止枚举.
  3. 每次迭代枚举时,都会再次强制执行代码.在相关线路上设置断点并自行测试.

  • 小澄清 - 在一些地方提到“IEnumerable[&lt;T&gt;]”时,您的意思是“IEnumerator[&lt;T&gt;]”。 (2认同)
  • 好的,但显然,“private IEnumerable&lt;string&gt; Enumerate() { }”会给你一个“并非所有代码路径都返回值错误”的信息。因此,我认为这让我得出以下结论:您需要 `yield break` 作为占位符(1)如果您的方法中没有其他返回语句(yield 或其他),或者(2)“如果您需要在执行到达其自然结束之前,在函数体内的某个位置停止枚举。” (2认同)

Cha*_*ion 6

1)举一个更简单的例子:

public void Enumerate()
{
    foreach (var item in EnumerateItems())
    {
        Console.WriteLine(item);
    }
}

public IEnumerable<string> EnumerateItems()
{
    yield return "item1";
    yield return "item2";
    yield break;
}
Run Code Online (Sandbox Code Playgroud)

每次MoveNext()IEnumerator代码调用时,从该yield点返回并移动到下一个可执行代码行.

2)yield break;将告诉IEnumerator我们没有更多要枚举的内容.

3)每次枚举一次.

运用 yield break;

public IEnumerable<string> EnumerateUntilEmpty()
{
    foreach (var name in nameList)
    {
        if (String.IsNullOrEmpty(name)) yield break;
        yield return name;
    }     
}
Run Code Online (Sandbox Code Playgroud)