Eri*_*ikE 12 c# collections parameters
当得到延迟执行的值时,我会从一个方法返回一个IEnumerable.并且返回a List或者IList应该只是在结果将被修改时,否则我将返回一个IReadOnlyCollection,所以调用者知道他得到的不是用于修改(这使得该方法甚至可以重用来自其他调用者的对象) ).
但是,在参数输入方面,我有点不太清楚.我可以拿一个IEnumerable,但如果我需要不止一次枚举怎么办?
俗话说" 你所发送的是保守的,你接受的是自由主义",这表明服用IEnumerable是好的,但我并不确定.
例如,如果以下IEnumerable参数中没有元素,则可以通过.Any()先检查在此方法中保存大量工作,这在此ToList()之前需要避免枚举两次.
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));
}
Run Code Online (Sandbox Code Playgroud)
所以我想知道什么是最好的签名,在这里?一种可能性是IList<Data> data,但接受列表表明您打算修改它,这是不正确的 - 这种方法不会触及原始列表,所以IReadOnlyCollection<Data>看起来更好.
但是,即使使用自定义扩展方法,也会IReadOnlyCollection强制调用者ToList().AsReadOnly()每次都会变得有点难看.AsReadOnlyCollection.在接受的东西中,这并不是自由主义者.
在这种情况下,最佳做法是什么?
此方法不返回a,IReadOnlyCollection因为最终Where使用延迟执行可能有值,因为不需要枚举整个列表.但是,Select需要列举,因为.Contains如果没有这样做,做的成本会很糟糕HashSet.
我没有调用问题,ToList我刚想到如果我需要List避免多次枚举,为什么我不只是在参数中要求一个?所以这里的问题是,如果我不想IEnumerable在我的方法中,我是否应该真正接受一个为了自由(和ToList我自己),或者我应该把负担放在呼叫者身上ToList().AsReadOnly()?
有关IEnumerables不熟悉的人的更多信息
这里真正的问题不是Any()对战的成本ToList().据我所知,枚举整个列表的成本高于实现成本Any().但是,假设调用者将使用IEnumerable上述方法返回的所有项目,并假设source IEnumerable<Data> data参数来自此方法的结果:
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);
}
Run Code Online (Sandbox Code Playgroud)
现在,如果你这样做:
var myData = GetVeryExpensiveDataForDate(todayDate);
var unhandledData = RemoveHandledForDate(myData, todayDate);
foreach (var data in unhandledData) {
messageBus.Dispatch(data); // fully enumerate
)
Run Code Online (Sandbox Code Playgroud)
如果RemovedHandledForDate不Any 和呢Where,你会招致5秒的成本的两倍,而不是一次.这就是为什么你应该总是采取极端的痛苦,以避免IEnumerable不止一次枚举.不要依赖于你的知识,事实上它是无害的,因为一些未来倒霉的开发人员有一天会用IEnumerable你从未想过的新实现来调用你的方法,它具有不同的特征.
合同中IEnumerable说你可以枚举它.它不会对不止一次这样做的性能特征做出任何承诺.
事实上,有些IEnumerables是易变的,并且在随后的枚举中不会返回任何数据!如果与多个枚举相结合,则切换到一个将是完全破坏性的变化(如果稍后添加多个枚举则很难诊断一个).
不要对IEnumerable进行多次枚举.
如果您接受IEnumerable参数,那么您实际上有希望将它精确地枚举0或1次.
IReadOnlyCollection<T>添加到IEnumerable<T>属性Count和相应的承诺,即不存在延迟执行。如果该参数是您想要解决此问题的地方,那么这将是需要询问的适当参数。
不过,我建议询问IEnumerable<T>, 并调用ToList()实现本身。
观察:这两种方法都有缺点,即多重枚举可能在某些时候被重构,导致参数更改或ToList()调用冗余,而我们可能会忽略这一点。我认为这是无法避免的。
该案例确实说明了ToList()在方法主体中进行调用:由于多重枚举是一个实现细节,因此避免它也应该是一个实现细节。这样,我们就可以避免影响 API。如果多重枚举被重构,我们也会避免改回API。IReadOnlyCollection<T>我们还避免通过一系列方法传播需求,由于我们的多重枚举,所有这些方法都必须要求一个just 。
如果您担心创建额外列表的开销(当输出已经是列表左右时),Resharper 建议采用以下方法:
param = param as IList<SomeType> ?? param.ToList();
Run Code Online (Sandbox Code Playgroud)
当然,我们可以做得更好,因为我们只需要防止延迟执行 - 不需要全面的IList<T>:
param = param as IReadOnlyCollection<SomeType> ?? param.ToList();
Run Code Online (Sandbox Code Playgroud)
您可以在该方法中使用一个,并使用与此处IEnumerable<T>类似的 CachedEnumerable来包装它。
此类包装 anIEnumerable<T>并确保它仅被枚举一次。如果您尝试再次枚举它,它将从缓存中生成项目。
请注意,此类包装器不会立即读取包装的可枚举中的所有项目。当您从包装器枚举单个项目时,它仅枚举来自包装的可枚举的单个项目,并且它会一路缓存单个项目。
这意味着,如果您调用Any包装器,则只会从包装的枚举中枚举单个项目,然后此类项目将被缓存。
如果您随后再次使用该枚举器,它将首先从缓存中生成第一项,然后继续从其离开的位置枚举原始枚举器。
您可以执行以下操作来使用它:
public IEnumerable<Data> RemoveHandledForDate(IEnumerable<Data> data, DateTime dateTime)
{
var dataWrapper = new CachedEnumerable(data);
...
}
Run Code Online (Sandbox Code Playgroud)
请注意,这里方法本身正在包装参数data。这样,您就不会强迫您的方法的使用者做任何事情。