数据对象属性的最佳实践:IEnumerable vs Array

4

简短问题: 声明 data-object 的属性为 IEnumerable 是否可以,或者应该使用 Array

背景:

我刚刚在我们的项目中发现了一个导致性能问题的 bug。原因是 IEnumerable 被多次遍历。但这只是表面上看起来很简单。我认为那里存在设计缺陷,才会导致这种情况发生。

更深入的调查显示,一个名为GetAllUsers的方法返回了一个UsersResponse对象,其中一个属性是IEnumerable<T> UsersList。当实现缓存时,很明显整个UsersResponse对象被缓存了,并且在那个时候它能够正常工作,因为GetAllUsers将一个数组分配给了IEnumerable<T> UsersList。后来,GetAllUsers的实现发生了改变,由于某种原因开发人员决定ToArray()调用是多余的。所以我认为问题在于UsersResponse对象设计不良,允许其工厂方法过于自由。另一方面,在原则上,缓存包含IEnumerable属性的对象也是无用的。

因此,我们回到了关于设计数据对象的一般问题:当你声明它时,不知道它将来是否会被缓存或者除了当前需求之外还将如何使用,是否可以将其属性声明为IEnumerable,将谨慎使用的责任放在其他开发人员身上,还是必须从一开始就声明为Array

我搜索过的内容:

我找到的唯一建议是Jon Wagner的博客文章,他建议尽快“封装”LINQ链。但这更涉及构建IEnumerable而不是将其存储在实体属性中。虽然与尽可能返回特定类型的原则相结合,它可以意味着将属性声明为Array

5
答案是:这取决于情况。在一种情况下,您会想要使用只向前只读集合,在另一种情况下则需要使用数组。没有一种最好的方法适用于所有情况。 - oleksii
@oleksii,您能否详细说明一下:在哪种情况下会在数据实体中使用前向集合?我也认为答案应该是“取决于情况”,但是找不到任何合理的情况。 - Sasha
@oleksii,抱歉,但这些答案都没有回答我的问题。在开始新的线程之前,我已经看过其中一些帖子了。它们都太笼统了,答案显然是“取决于情况”。我的问题特别针对数据对象。比如从数据库存储库、Web服务或仅用于将数据从一个类传递到另一个类的内部业务实体返回的对象。核心在于数据对象原则上应该代表数据而不是检索数据的操作。同样的问题也适用于Lazy<T>和数据实体中的委托。 - Sasha
我看不出缓存与这个问题有什么关系。你是不是想说 IEnumerable<T> 而不是 IQueryable<T>?如果使用某种远程 API,IEnumerablearray 都将被传递到客户端的内存中。这只取决于你想要做什么。例如,如果你想在集合中使用 LINQ 进行查询,那么 IEnumerable 可能更容易查询。如果你想要对集合中的任何元素进行 O(1) 访问,则数组似乎是更好的选择。对我来说,区别只在于你打算如何使用它。 - oleksii
1
@oleksii,缓存只是使用IEnumerable时可能出现的问题之一。当您设计数据实体时,通常不知道它将在未来的每种情况下如何使用,这就是问题所在 - 您必须根据对用法的假设来决定IEnumerable或Array。另一个支持Array的观点是,它可以在不破坏向后兼容性的情况下将其更改为IEnumerable(仅更改行为)。在Array下,我指的是T [],因此LINQ在其上运行得很好。 - Sasha
3个回答

3

当我考虑API设计时,我总是试图对用户“友好”。这意味着尽可能接受参数(在可能的情况下),并提供尽可能多的返回值。如果你也认同这一点,那么你应该努力使用IEnumerable参数,同时提供Array(或类似)返回值。这样可以最大化API的价值(即使最终用户是你自己)。


我的问题中的数据实体既可以作为一组方法的返回参数,也可以作为其他方法的参数。这种情况怎么处理? - Sasha
我同意,在参数中使用IEnumerable,但始终将List<T>或T[]作为返回类型。唯一让人烦恼的是无法使用yield return; :( - demoncodemonkey
@demoncodemonkey 有时候我也想使用 yield return。那么我就简单地抛出一个异常 :-) - Daniel Lidström
2
@OleksandrPshenychnyy,在我看来,对于你的情况,Array更加合适,因为你需要缓存东西。此外,与你的团队讨论类设计。强制所有消费者使用IEnumerable是一个严重的限制,完全没有必要,除非数据实际上需要被流式传输。 - Daniel Lidström
@bstenzel 返回 IEnumerable 强制你的消费者只能流式传输数据(仅一次)。这是 IEnumerable 给出的唯一保证。这是不是很有限呢? - Daniel Lidström
显示剩余3条评论

0

通常我更喜欢使用Array方法,除非它被强制转换为集合。特别是在有大量数值计算的情况下。

优点:

  • 紧凑的存储方式。(可能会使用hacky技术)
  • [ ]访问器。(不适用于C#)
  • 多维数组的可读性高

最后但并非最不重要的是,坚持使用一种方法,永远不要同时使用两种方法。将集合从数组转换为集合或反之亦然是很糟糕的,这样做没有任何好处。


0
应使用最抽象的类型来满足要求。如果您需要使在DTO中存储“打开”查询变得不可能,则应为属性使用ICollection<T>或(如果需要索引访问)IList<T>
使用array将限制您到具体实现。这可能是无关紧要的,或者在以后的某个时候可能会变得很痛苦。后者排除了array在我看来。
顺便说一句:只有一次迭代IEnumerable<T>是调用者的责任。

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