3

考虑以下示例。

假设我开发了一个第三方将使用的库。该库有一个接受的方法IEnumerable,这很好,因为它从传递的集合的实现中抽象出来。但是,当我检索输入参数时会发生惰性求值。

public interface IStorage<in T>
{
  void Store(IEnumerable<T> values);
}

public class FileStorage<T> : IStorage<T>
{
  public void Store(IEnumerable<T> values)
  {
    foreach (var value in values) { /*Evaluation of IEnumerable*/}
  }
}

一些客户通过以下方式使用我的库:

  IEnumerable<int> values;
  try
  {
    values = new[] { "1", "2", "three" }.Select(int.Parse);
  }
  catch (Exception)
  {
    values = Enumerable.Empty<int>();
  }

  var storage = new FileStorage<int>();
  storage.Store(values);

Store这将导致发生评估的方法内部出现异常。如果我设计了Store要采取的方法,List<T>我会确定它是 Ts 的安全可枚举集合。问题是我们在设计 API 时是否应该避免IEnumerable,或者我们是否应该在需要时允许并在安全上下文中枚举它,因为它可能会引发异常。

4

5 回答 5

3

根据 Krzysztoc Cwalina 和 Brad Adams 的框架设计指南一书。

http://blogs.msdn.com/b/kcwalina/archive/2008/01/03/frameworkdesignguidelines2ndedition.aspx

建议您这样做...

摘自第 252 页 (8.3.1)

集合参数

务必使用最不专业的类型作为参数类型。大多数将集合作为参数的成员使用 IEnumerable 接口

 public void PrintNames(IEnumerable<string> names) {
      foreach(string name in names) {
          Console.WriteLine(name);
      } }

避免使用 ICollection 或 ICollection 作为参数来访问 Count 属性。

而是考虑 IEnumerable 或 IEnumerable 并动态检查对象是否实现 ICollection 或 ICollection

顺便说一句,这是一本很棒的书,值得一读,目前真的很喜欢。PS 写这篇文章的人帮助编写了 .NET 框架

于 2012-07-19T19:15:42.010 回答
1

就我个人而言,我认为从您的方法内部引发的异常没有问题。
一个错误,您的 API 的用户应该知道它。

于 2012-07-19T18:51:20.503 回答
1

您的客户的代码有问题的事实不是您的问题。如果他们希望他们的程序正常工作,请告诉他们停止编写错误代码。您无法保护他们免受这些查询被延迟评估并且他们显然不知道这一点的事实。

您的客户有责任处理枚举集合时发生的事情,如果他们向您传递了一个惰性求值的集合,那么他们有责任确保这种惰性求值不会抛出(如果这些是您定义的参数) . 打破这个不变量是他们的问题。

于 2012-07-19T18:54:26.100 回答
0

是的,将最抽象的接口作为库中的参数是一种很好的做法。您以这种方式定义这些项目的合同。

如果只需要能够枚举一个集合,IEnumerable 是一个非常不错的选择。

如果您希望能够添加、索引等项目,IList 是正确的选择。

于 2012-07-19T18:53:19.930 回答
0

附带说明一下,这里的问题不在于您的 API 采用 IEnumerable。实际问题出在 .Select 调用上。延迟评估是 LINQ 的一项功能,而不是 IEnumerable 接口的一项功能。

所以是的,您应该将 IEnumerable 作为参数。资源的加载和管理完全在客户端的控制之下,而不是你的。仅仅因为客户端可能会传入一些可能导致延迟报告异常的 IEnumerable 实现,这并不足以成为牺牲 API 的可维护性和灵活性的充分理由。

于 2012-07-19T18:59:31.420 回答