考虑以下示例。
假设我开发了一个将由第三方使用的库。该库有一个接受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
,还是在需要时我们是否应该在安全的上下文中允许和枚举它,因为它可能会引发异常。
答案 0 :(得分: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
顺便说一句,这是一本很棒的书,值得一读,目前真正享受它。 附:写这篇文章的人帮助编写了.NET框架
答案 1 :(得分:1)
就我个人而言,我没有发现从您的方法中引发异常的问题 此 是一个错误,您的API用户应该知道它。
答案 2 :(得分:1)
您客户的代码错误的事实不是您的问题。如果他们希望他们的程序能够工作,请告诉他们停止编写错误的代码。你不能保护他们免受这些查询被懒惰评估的事实,他们显然不知道这一点。
您的客户有责任处理枚举集合时发生的事情,如果他们已经通过了懒惰评估的集合,那么他们的责任确保这个惰性求值不能抛出(如果这些是您定义的参数)。打破这个不变量就是他们的问题。
答案 3 :(得分:0)
是的,最好在库中使用最抽象的接口作为参数。 您可以通过这种方式为这些项目定义合同。
如果你只需要能够枚举一个集合,那么IEnumerable是一个非常好的选择。
如果您希望能够添加,索引等项目,IList是正确的选择。
答案 4 :(得分:0)
作为旁注,这里的问题不在于您的API采用IEnumerable。实际问题是.Select调用。延迟评估是LINQ的一个特性,而不是IEnumerable接口的一个特性。
所以是的,您应该将IEnumerable作为参数。资源的加载和管理完全在客户的控制之下,而不是您的。仅仅因为客户端可能会传递IEnumerable的某些实现,这可能导致延迟报告异常,这不足以牺牲API的可维护性和灵活性。