草庐IT

c# - 参数 : IEnumerable vs. IList 与 IReadOnlyCollection 的最佳实践

coder 2024-05-30 原文

我知道什么时候会返回 IEnumerable来自方法——当延迟执行有值(value)时。并返回 ListIList几乎应该只在结果要被修改时才会出现,否则我会返回一个 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));
}

所以我想知道最好的签名是什么?一种可能性是 IList<Data> data , 但接受一个列表表明你打算修改它,这是不正确的——这个方法没有触及原始列表,所以 IReadOnlyCollection<Data>看起来好多了。

但是IReadOnlyCollection强制调用者做 ToList().AsReadOnly()每次都变得有点难看,即使使用自定义扩展方法也是如此 .AsReadOnlyCollection .这在所接受的方面并不自由。

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

此方法不返回 IReadOnlyCollection因为最后的 Where 可能有值(value)使用延迟执行,因为不需要 枚举整个列表。然而,Select需要枚举,因为做 .Contains 的成本没有 HashSet 会很糟糕.

我调用 ToList 没问题,我突然想到,如果我需要 List为了避免多重枚举,为什么我不在参数中只要求一个?所以这里的问题是,如果我不想要 IEnumerable在我的方法中,我是否真的应该接受一个以变得自由(和 ToList 我自己),或者我应该把负担放在调用者身上 ToList().AsReadOnly()

为不熟悉 IEnumerables 的人提供的更多信息

这里真正的问题不是 Any() 的成本与 ToList() .我知道枚举整个列表比做 Any() 花费更多.但是,假设调用者将消耗返回中的所有项目 IEnumerable从上面的方法,并假设来源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);
}

现在如果你这样做:

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

如果RemovedHandledForDateAny Where ,您将承担 5 秒的费用两次,而不是一次。这就是为什么您应该始终竭尽全力避免枚举 IEnumerable不止一次。不要相信它实际上是无害的知识,因为 future 某个倒霉的开发人员可能有一天会用新实现的 IEnumerable 调用您的方法。你想不到的,它有不同的特点。

IEnumerable 的契约(Contract)说你可以枚举它。它不 promise 多次这样做的性能特征。

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

不要对 IEnumerable 进行多次枚举。

如果您接受一个 IEnumerable 参数,您实际上是在 promise 将它精确枚举 0 次或 1 次。

最佳答案

IReadOnlyCollection<T>添加到 IEnumerable<T>一个Count属性(property)和相应的 promise ,即没有延迟执行。如果该参数是您想要解决此问题的地方,这将是要求的适当参数。

但是,我建议询问 IEnumerable<T> , 并调用 ToList()而不是在实现本身中。

观察:这两种方法都有一个缺点,即多重枚举可能在某些时候被重构掉,呈现参数变化或 ToList()称为冗余,我们可能会忽略这一点。我认为这是无法避免的。

这个案例确实可以调用ToList()在方法体中:由于多重枚举是一个实现细节,因此避免它也应该是一个实现细节。这样,我们就避免了影响 API。如果多重枚举被重构掉,我们也避免更改 API。我们还避免通过一系列方法传播需求,所有这些方法都必须请求 IReadOnlyCollection<T>。只是因为我们的多重枚举。

如果您担心创建额外列表的开销(当输出已经是一个列表左右时),Resharper 建议采用以下方法:

param = param as IList<SomeType> ?? param.ToList();

当然,我们可以做得更好,因为我们只需要防止延迟执行 - 不需要成熟的 IList<T> :

param = param as IReadOnlyCollection<SomeType> ?? param.ToList();

关于c# - 参数 : IEnumerable vs. IList 与 IReadOnlyCollection 的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33637294/

有关c# - 参数 : IEnumerable vs. IList 与 IReadOnlyCollection 的最佳实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  4. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  5. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  6. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  7. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

    两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

  8. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  9. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  10. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

随机推荐