14

通过教程(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;
}

我已经阅读了yield,但我想我的理解仍然有点模糊。它似乎要做的是创建一个对象,该对象允许循环遍历集合中的项目,而无需实际执行循环,除非并且直到绝对必要。

不过,这个例子对我来说有点奇怪。我认为它正在做的是延迟任何RuleViolation实例的创建,直到程序员使用for each或者使用 LINQ 扩展方法(如.ElementAt(2).

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

  1. if何时评估语句的条件部分?何时GetRuleViolations()调用或何时实际迭代可枚举?换句话说,如果在我调用的时间和我尝试实际迭代它的时间之间的值Titlenullto变化,是否会被创建?Really Geeky DinnerGetRuleViolations()RuleViolation("Title required", "Title")

  2. 为什么是yield break;必要的?它到底在这里做什么?

  3. 假设Title是 null 或空。如果我调用GetRuleViolations()then 连续两次迭代生成的可枚举,将new RuleViolation("Title required", "Title")调用多少次?

4

3 回答 3

18

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

关于您的问题:

  1. 正如我所说,每个if元素都会在您迭代到下一个元素时执行。
  2. yield break在上面的例子中确实没有必要。它的作用是终止枚举。
  3. 每次迭代可枚举时,都会再次强制执行代码。在相关行设置断点,自己测试。
于 2009-12-28T19:30:19.953 回答
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;
}

每次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;
    }     
}
于 2009-12-28T19:29:09.857 回答
2

简洁版本:

1:yield 是神奇的“停止并稍后返回”关键字,因此“活动”之前的 if 语句已被评估。

2:yield break 显式结束枚举(在 switch 案例中考虑“break”)

3:每次。当然,您可以缓存结果,例如将其转换为 List 并在之后对其进行迭代。

于 2009-12-28T19:31:51.380 回答