Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/283.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 参数的最佳实践:IEnumerable与IList与IReadOnlyCollection_C#_Collections_Parameters - Fatal编程技术网

C# 参数的最佳实践:IEnumerable与IList与IReadOnlyCollection

C# 参数的最佳实践:IEnumerable与IList与IReadOnlyCollection,c#,collections,parameters,C#,Collections,Parameters,当延迟执行中有值时,我会从方法中返回IEnumerable。返回列表或IList应该只在修改结果时才返回,否则我会返回IReadOnlyCollection,这样调用者就知道他得到的不是要修改的(这让该方法甚至可以重用来自其他调用者的对象) 然而,在参数输入端,我不太清楚。我可以取一个IEnumerable,但是如果我需要多次枚举呢 俗话说“发送的东西要保守,接受的东西要自由”,这意味着使用IEnumerable是好的,但我不是很确定 例如,如果以下IEnumerable参数中没有元素,则此方法

当延迟执行中有值时,我会从方法中返回
IEnumerable
。返回
列表
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));
}
现在,如果您这样做:

var myData = GetVeryExpensiveDataForDate(todayDate);
var unhandledData = RemoveHandledForDate(myData, todayDate);
foreach (var data in unhandledData) {
   messageBus.Dispatch(data); // fully enumerate
)
如果
RemovedHandledForDate
执行了
任何
操作,并且执行了
Where
,则您将承担两次5秒的成本,而不是一次。这就是为什么您应该尽最大努力避免多次枚举
IEnumerable
。不要依赖于你的知识,事实上它是无害的,因为将来某个倒霉的开发人员可能会用一个你从未想到的新实现的
IEnumerable
调用你的方法,它具有不同的特性

IEnumerable
的合同规定可以枚举它。它并没有承诺多次这样做的性能特征

事实上,一些
IEnumerables
是易变的,在后续枚举时不会返回任何数据!如果与多个枚举相结合,切换到一个将是一个完全破坏性的更改(如果后来添加了多个枚举,则很难诊断)

不要对IEnumerable执行多重枚举


如果您接受IEnumerable参数,实际上,您承诺将它精确地枚举0或1次。

有一些方法可以让您接受IEnumerable,只枚举一次,并确保不多次查询数据库。我能想到的解决办法是:

  • 您可以直接使用枚举器,而不是使用
    Any
    Where
    。调用
    MoveNext
    而不是
    Any
    ,查看集合中是否有任何项,并在进行数据库查询后在中进一步手动迭代
  • 使用
    Lazy
    初始化
    HashSet
第一个看起来很难看,第二个可能很有意义:

public IEnumerable<Data> RemoveHandledForDate(IEnumerable<Data> data, DateTime dateTime)
{
    var ids = new Lazy<HashSet<int>>(
        () => new HashSet<int>(
       GetHandledDataForDate(dateTime) // Expensive database operation
          .Select(d => d.DataId)
    ));

    return data.Where(d => !ids.Value.Contains(d.DataId));
}
public IEnumerable RemoveHandledForDate(IEnumerable数据,日期时间日期时间)
{
var id=新的惰性(
()=>新哈希集(
GetHandledDataForDate(dateTime)//昂贵的数据库操作
.Select(d=>d.DataId)
));
返回数据,其中(d=>!ids.Value.Contains(d.DataId));
}

您可以在方法中使用一个
IEnumerable
,并使用类似的CachedEnumerable来包装它

此类包装了一个
IEnumerable
,并确保它只被枚举一次。如果您再次尝试枚举它,它将从缓存中生成项

请注意,这样的包装器不会立即从包装的枚举中读取所有项。当您从包装器中枚举单个项时,它仅从包装的可枚举项中枚举单个项,并在过程中缓存单个项

这意味着,如果在包装器上调用
Any
,则只会从包装的可枚举项中枚举单个项,然后将缓存此类项

如果随后再次使用该枚举数,它将首先从缓存中生成第一个项,然后继续从其保留的位置枚举原始枚举数

您可以这样做来使用它:

public IEnumerable<Data> RemoveHandledForDate(IEnumerable<Data> data, DateTime dateTime)
{
    var dataWrapper = new CachedEnumerable(data);
    ...
}
public IEnumerable RemoveHandledForDate(IEnumerable数据,日期时间日期时间)
{
var dataWrapper=新的CachedEnumerable(数据);
...
}

请注意,这里的方法本身正在包装参数
数据
。这样,您就不会强迫您的方法的使用者做任何事情。

我不认为仅仅通过更改输入类型就可以解决这个问题。如果您想允许比
List
IList
更通用的结构,那么您必须决定是否/如何处理这些可能的边缘情况

要么计划最坏的情况,花一点时间/内存创建一个具体的数据结构,要么计划最好的情况,冒偶尔执行两次查询的风险


您可以考虑记录该方法多次枚举集合,以便调用方可以决定是否要传递一个“昂贵”的查询,或者在调用方法之前对查询进行水合物。p> 我认为

IEnumerable
是参数类型的一个好选择。它是一种简单、通用且易于提供的结构。
IEnumerable
契约本身并不意味着应该只迭代一次

一般来说,测试
.Any()
的性能成本可能不高,但当然不能保证如此。在您描述的情况下,很明显,迭代第一个元素会有相当大的开销,但这绝不是通用的

将参数类型更改为
IReadOnlyCollection
IReadOnlyList
是一个选项,但可能只有在提供了部分或全部属性/方法的情况下才是一个好选项
public IEnumerable<Data> RemoveHandledForDate(IEnumerable<Data> data, DateTime dateTime)
{
    var dataWrapper = new CachedEnumerable(data);
    ...
}
param = param as IList<SomeType> ?? param.ToList();
param = param as IReadOnlyCollection<SomeType> ?? param.ToList();