在这种情况下使用yield有什么好处吗?

4

我在工作中维护一些代码,原作者已离职,所以想在这里问一下,看看能否满足我的好奇心。

以下是一段代码(已匿名化),其中使用了 yield。据我观察,它并没有添加任何好处,仅返回一个列表就足够了,可能更易读(至少对我来说如此)。只是想知道是否有遗漏,因为这种模式在代码库中重复出现了几次。

public virtual IEnumerable<string> ValidProductTypes
{
  get
  {
    yield return ProductTypes.Type1;
    yield return ProductTypes.Type2;
    yield return ProductTypes.Type3;
  }
}

这个属性被用作某个类的参数,该类仅使用它来填充一个集合:

var productManager = new ProductManager(ValidProductTypes);

public ProductManager(IEnumerable<string> validProductTypes)
{
  var myFilteredList = GetFilteredTypes(validProductTypes);
}

public ObservableCollection<ValidType> GetFilteredTypes(IEnumerable<string> validProductTypes)
{
  var filteredList = validProductTypes
                    .Where(type => TypeIsValid); //TypeIsValid returns a ValidType
  return new ObservableCollection<ValidType>(filteredList);
}

一个好处可能是,虽然在这种情况下似乎并不是这样,但当枚举没有完成时,例如使用FirstOrDefault,对于后续的yield,不会执行所有潜在的昂贵执行,而这些执行对于列表来说是必须的。在这种情况下,如果Type2是一个重量级属性,但枚举不使用它,那么它将比列表更轻。 - Me.Name
3
在我看来,使用简单的数组实现那种情况会更好。我认为迭代器方法的语法并没有增加任何东西。但这主要是个人观点。你似乎在这里没有任何实际、实用的编程问题需要解决。 - Peter Duniho
1个回答

5
我认为返回一个 IEnumerable<T> 并使用 yield return 实现是最简单的选择。
如果你看到一个方法返回一个 IEnumerable<T>,你真正可以做的只有一件事情:迭代它。任何更复杂的操作(比如使用 LINQ)只是封装了特定的迭代方式。
如果一个方法返回一个数组或列表,你也获得了修改它的能力,你可能会开始思考这是否是 API 的可接受用法。例如,如果你执行 ValidProductTypes.Add("a new product"),会发生什么?
如果你只是谈论实现,那么区别就变得更小了。但是调用者仍然能够将从 IEnumerable<T> 返回的数组或列表转换为其具体类型并对其进行修改。任何人认为这是 API 的预期用法的机会很小,但是使用 yield return,机会为零,因为这是不可能的。
考虑到语法的复杂性和易于理解程度大致相同,我认为 yield return 是一个合理的选择。虽然使用 C# 6.0 表达式主体属性时,数组的语法可能会占据上风:
public virtual IEnumerable<string> ValidProductTypes =>
  new[] { ProductTypes.Type1, ProductTypes.Type2, ProductTypes.Type3 };

上面的答案假设这不是性能关键代码,因此性能上的微小差异并不重要。如果这是性能关键代码,则应测量哪个选项更好。您还可能希望考虑消除分配(可能通过在字段中缓存数组或类似方法),这可能是主要因素。

尽管看起来很简单,但我预计那些使用yield return的用法,实际上将其转换为生成器,会在底层产生比使用可枚举集合更复杂的IL代码。我认为这也会有性能影响(不是关键问题,但可能与普通数组不同)。 - poke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接