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

时间:2009-12-28 19:18:16

标签: 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;
}

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

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

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

  1. 何时评估if语句的条件部分?当调用GetRuleViolations()或实际迭代枚举时?换句话说,如果Title的值在我调用null的时间和我尝试实际迭代它的时间之间从Really Geeky Dinner更改为GetRuleViolations(),那么{ {1}}是否已创建?

  2. 为什么需要RuleViolation("Title required", "Title")?它到底在做什么?

  3. 假设yield break;为空或空。如果我调用Title然后连续两次迭代结果可枚举,GetRuleViolations()将被调用多少次?

3 个答案:

答案 0 :(得分:17)

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

关于你的问题:

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

答案 1 :(得分: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;
    }     
}

答案 2 :(得分:2)

简短版本:

1:收益率是神奇的“停止并稍后回来”关键字,因此评估了“活动”前面的if语句。

2:yield break显式结束枚举(在switch的情况下认为是“break”)

3:每一次。当然,您可以通过将结果转换为List来缓存结果,然后迭代结果。