本文介绍了参数的最佳实践:IEnumerable vs. IList vs. IReadOnlyCollection的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当延迟执行中有值时,我会在方法中返回 IEnumerable 时获取 。并返回一个 List 或 IList 应该几乎只有当结果将被修改,否则我会返回一个 IReadOnlyCollection ,因此调用者知道他得到的是不是为了修改(这让该方法甚至重用来自其他调用者的对象)。

I get when one would return an IEnumerable from a method—when there's value in deferred execution. And returning a List or IList should pretty much be only when the result is going to be modified, otherwise I'd return an IReadOnlyCollection, so the caller knows what he's getting isn't intended for modification (and this lets the method even reuse objects from other callers).

但是,在参数输入端,我有点不太清楚。 可以取一个 IEnumerable ,但如果我需要多次枚举呢?

However, on the parameter input side, I'm a little less clear. I could take an IEnumerable, but what if I need to enumerate more than once?

不太确定。

例如,如果在下面的 IEnumerable 参数中没有元素,可以通过首先检查 .Any(),这需要 ToList() strong>避免枚举两次

For example, if there are no elements in the following IEnumerable parameter, a significant amount of work can be saved in this method by checking .Any() first, which requires ToList() before that to avoid enumerating twice.

public IEnumerable<Data> RemoveHandledForDate(IEnumerable<Data> data, DateTime dateTime) {
   var dataList = data.ToList();

   if (!dataList.Any()) {
      return dataList;
   }

   var handledDataIds = new HashSet<int>(
      GetHandledDataForDate(dateTime) // Expensive database operation
         .Select(d => d.DataId)
   );

   return dataList.Where(d => !handledDataIds.Contains(d.DataId));
}

所以我想知道什么是最好的签名,一种可能性是 IList< Data> data ,但接受列表建议您计划修改它,这是不正确的 - 此方法不接触原始列表,因此 IReadOnlyCollection< Data> 似乎更好。

So I'm wondering what is the best signature, here? One possibility is IList<Data> data, but accepting a list suggests that you plan to modify it, which is not correct—this method doesn't touch the original list, so IReadOnlyCollection<Data> seems better.

但 IReadOnlyCollection 强制调用者执行 ToList .AsReadOnly()每次都得到一个丑陋,即使有一个自定义扩展方法 .AsReadOnlyCollection 。

But IReadOnlyCollection forces callers to do ToList().AsReadOnly() every time which gets a bit ugly, even with a custom extension method .AsReadOnlyCollection. And that's not being liberal in what is accepted.

这种情况下最好的做法是什么?

What is best practice in this situation?

这种方法没有返回 IReadOnlyCollection ,因为在最后的中可能有值使用延迟执行,因为整个列表不是 required 枚举。但是, Select 需要枚举,因为 .Contains 的成本会很糟糕,而没有 HashSet 。

This method is not returning an IReadOnlyCollection because there may be value in the final Where using deferred execution as the whole list is not required to be enumerated. However, the Select is required to be enumerated because the cost of doing .Contains would be horrible without the HashSet.

我没有调用 ToList的问题 ,它只是发生在我,如果我需要一个列表以避免多个枚举,为什么我不只是要求一个参数?所以这里的问题是,如果我不想在我的方法中的 IEnumerable ,我真的应该接受一个为了自由(和 ToList 它自己),或者我应该把调用者的负担 ToList()。AsReadOnly()?

I don't have a problem with calling ToList, it just occurred to me that if I need a List to avoid multiple enumeration, why do I not just ask for one in the parameter? So the question here is, if I don't want an IEnumerable in my method, should I really accept one in order to be liberal (and ToList it myself), or should I put the burden on the caller to ToList().AsReadOnly()?

对于不熟悉IEnumerable的人的更多信息

这里真正的问题不是 Any()与 ToList()。我理解,枚举整个列表的成本比$ Any()。然而,假设调用者将消耗来自上述方法的返回 IEnumerable 中的所有项目,并假定源 IEnumerable< Data> data 参数来自此方法的结果:

The real problem here is not the cost of Any() vs. ToList(). I understand that enumerating the entire list costs more than doing Any(). However, assume the case that the caller will consume all items in the return IEnumerable from the above method, and assume that the source IEnumerable<Data> data parameter comes from the result of this method:

public IEnumerable<Data> GetVeryExpensiveDataForDate(DateTime dateTime) {
    // This query is very expensive no matter how many rows are returned.
    // It costs 5 seconds on each `.GetEnumerator` call to get 1 value or 1000
    return MyDataProvider.Where(d => d.DataDate == dateTime);
}

现在,如果您这样做:

var myData = GetVeryExpensiveDataForDate(todayDate);
var unhandledData = RemoveHandledForDate(myData, todayDate);
foreach (var data in unhandledData) {
   messageBus.Dispatch(data); // fully enumerate
)

如果 RemovedHandledForDate 会任何 会其中费用两次 ,而不是一次。这就是为什么你应该总是极度痛苦,以避免多次枚举 IEnumerable 。不要依赖你的知识,事实上它是无害的,因为一些未来不幸的开发人员可能会调用你的方法有一天新的实现 IEnumerable 你从来没有想到,它有不同的特点

And if RemovedHandledForDate does Any and does Where, you'll incur the 5 second cost twice, instead of once. This is why you should always take extreme pains to avoid enumerating an IEnumerable more than once. Do not rely on your knowledge that in fact it's harmless, because some future hapless developer may call your method some day with a newly implemented IEnumerable you never thought of, which has different characteristics.

IEnumerable 的合约表示您可以枚举它。

The contract for an IEnumerable says that you can enumerate it. It does NOT promise anything about the performance characteristics of doing so more than once.

事实上,一些 IEnumerables 是 volatile ,并且在后续枚举时不会返回任何数据!如果与多个枚举组合(如果稍后添加多个枚举,则很难诊断一个枚举),切换到一个将是一个完全突变的更改。

In fact, some IEnumerables are volatile and won't return any data upon a subsequent enumeration! Switching to one would be a totally breaking change if combined with multiple enumeration (and a very hard to diagnose one if the multiple enumeration was added later).

不要

如果您接受一个I​​Enumerable参数,则您实际上有希望枚举它完全为0或1

If you accept an IEnumerable parameter, you are in effect promising to enumerate it exactly 0 or 1 times.

推荐答案

您可以使用 IEnumerable< T> ,并使用类似于包装它。

You can take an IEnumerable<T> in the method, and use a CachedEnumerable similar to the one here to wrap it.

这个类包装一个 IEnumerable< T> 它只是枚举一次。如果您尝试再次枚举它,它会从缓存中产生项目。

This class wraps an IEnumerable<T> and makes sure that it is only enumerated once. If you try to enumerate it again, it yield items from the cache.

请注意,这样的包装器不会立即从包装的枚举中读取所有项目。

Please note that such wrapper does not read all items from the wrapped enumerable immediately. It only enumerates individual items from the wrapped enumerable as you enumerate individual items from the wrapper, and it caches the individual items along the way.

这意味着如果你调用

如果你再次使用enumerable,它将首先从缓存中产生第一个项目,然后继续枚举离开的原始枚举器。

If you then use the enumerable again, it will first yield the first item from the cache, and then continue enumerating the original enumerator from where it left.

做这样使用它:

public IEnumerable<Data> RemoveHandledForDate(IEnumerable<Data> data, DateTime dateTime)
{
    var dataWrapper = new CachedEnumerable(data);
    ...
}

注意这里的方法本身是参数 data 。这样,你不会强迫你的方法的消费者做任何事情。

Notice here that the method itself is wrapping the parameter data. This way, you don't force consumers of your method to do anything.

这篇关于参数的最佳实践:IEnumerable vs. IList vs. IReadOnlyCollection的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-11 15:38