返回null或空集合是否更好?

Omu*_*Omu 397 c# collections

这是一个普遍的问题(但我使用的是C#),最好的方法是什么(最佳实践),对于将集合作为返回类型的方法,是否返回null或空集合?

小智 475

空集合.总是.

这很糟糕:

if(myInstance.CollectionProperty != null)
{
  foreach(var item in myInstance.CollectionProperty)
    /* arrgh */
}
Run Code Online (Sandbox Code Playgroud)

null返回集合或可枚举时,最好不要返回. 总是返回一个空的枚举/集合.它可以防止上述废话,并防止您的汽车被您的班级的同事和用户怂恿.

在谈论房产时,请务必将房产设置一次并忘记

public List<Foo> Foos {public get; private set;}

public Bar() { Foos = new List<Foo>(); }
Run Code Online (Sandbox Code Playgroud)

在.NET 4.6.1中,您可以压缩这么多:

public List<Foo> Foos { get; } = new List<Foo>();
Run Code Online (Sandbox Code Playgroud)

在讨论返回枚举的方法时,您可以轻松返回空的枚举而不是null...

public IEnumerable<Foo> GetMyFoos()
{
  return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}
Run Code Online (Sandbox Code Playgroud)

使用Enumerable.Empty<T>()可以被视为比返回更有效,例如,新的空集合或数组.

  • 您实际上应该更喜欢返回System.Linq.Enumerable.Empty <Foo>()而不是新的Foo [0].它更明确,并为您节省了一个内存分配(至少,在我安装的.NET实现中). (31认同)
  • 它并不总是一个问题,"你是否可以轻松地返回一个空数组",而不是一个空数组是否可能在当前上下文中产生误导.空数组实际上意味着什么,就像null一样.要说你应该总是返回一个空数组而不是null,几乎就像说一个布尔方法应该总是返回true一样误导.两种可能的价值都传达了意义. (29认同)
  • 我同意威尔,但我认为"总是"有点过分.虽然空集合可能意味着"0项",但返回Null可能意味着"根本没有集合" - 例如.如果要解析HTML,查找id ="foo"的<ul>,<ul id ="foo"> </ ul>可能会返回空集合; 如果没有带有id ="foo"的<ul>,则返回null会更好(除非你想用异常处理这种情况) (22认同)
  • **相关(CodeProject文章):**[它真的更好地'返回空列表而不是空'?](http://www.codeproject.com/Articles/794448/Is-it-Really-Better-到返回-AN-空一览Instea) (7认同)
  • @Will:OP:"你为一个**方法**返回null或空集合,它有一个集合作为返回类型".我认为在这种情况下,它是"IEnumerable"还是"ICollection"并不重要.无论如何,如果你选择`ICollection`类型的东西,它们也会返回`null` ...我希望它们返回一个空集合,但我遇到它们返回`null`,所以我想我只是在这里提到它.我会说枚举的集合的默认值为空而不是null.我不知道这是一个如此敏感的话题. (4认同)
  • 一般的答案通常都很糟糕。`null` 意味着一件事,清空不同的东西。不要通过进行这种概括来混合它们。 (2认同)

Ric*_*dOD 150

来自框架设计指南第2版(第256页):

不要从集合属性或返回集合的方法返回空值.返回一个空集合或一个空数组.

这是另一篇关于不返回空值的好处的有趣文章(我试图在Brad Abram的博客上找到一些东西,并且他链接到了这篇文章).

编辑 -正如Eric Lippert现在对原始问题发表评论,我也想链接到他的优秀文章.

  • +1.最好遵循框架设计指南,除非你有非常好的理由不这样做. (32认同)

Boz*_*zho 88

取决于您的合同具体案例.通常最好返回空集合,但有时(很少):

  • null 可能意味着更具体的东西;
  • 您的API(合同)可能会迫使您返回null.

一些具体的例子:

  • UI组件(来自您控件之外的库),如果传递空集合,则可能呈现空表,或者如果传递null则可能没有表.
  • 在一个对象到XML(JSON /无论)中,哪里null意味着元素丢失,而一个空集合将呈现冗余(可能不正确)<collection />
  • 您正在使用或实现一个API,该API明确声明应该返回/传递null

  • 不,这意味着"没有设定",而不是"集合没有元素" (13认同)
  • @ Bozho-这里有一些有趣的例子. (4认同)
  • 我有点明白你的观点,尽管我认为你不应该围绕其他错误构建你的代码,并且根据定义,c# 中的“null”_never_ 意味着特定的东西。值 null 被定义为“没有信息”,因此说它携带特定信息是一个矛盾。这就是为什么 .NET 指南指出,如果集合确实为空,您应该返回一个空集合。返回 null 是说:“我不知道预期的集合去了哪里” (2认同)

Jef*_*dge 36

还有一点尚未提及.请考虑以下代码:

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }
Run Code Online (Sandbox Code Playgroud)

调用此方法时,C#语言将返回一个空的枚举器.因此,为了与语言设计(以及程序员期望)保持一致,应返回空集合.

  • +1为一组空的喜爱的emo歌曲. (33认同)
  • @FryGuy - 不,不.它返回一个Enumerable对象,其GetEnumerator()方法返回一个Enumerator(由analagy with a collection)为空.也就是说,Enumerator的MoveNext()方法总是返回false.调用example方法不返回null,也不返回GetEnumerator()方法返回null的Enumerable对象. (3认同)

Geo*_*voy 30

空对消费者更友好.

有一个明确的方法来组成一个空的可枚举:

Enumerable.Empty<Element>()
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你的巧妙把戏.我正在实例化一个空的List <T>(),就像一个白痴,但这看起来更清洁,也可能更有效率. (2认同)

Jay*_*Jay 18

在我看来,你应该在上下文中返回语义上正确的值,无论它是什么.一句话说"总是返回一个空集合"对我来说似乎有点过分了.

假设在一个医院系统中,我们有一个功能,应该返回过去5年以前所有住院治疗的清单.如果客户没有去过医院,那么返回一个空列表是很有意义的.但是,如果客户将该部分的准入表格留空,该怎么办?我们需要一个不同的值来区分"空列表"和"无答案"或"不知道".我们可以抛出异常,但它不一定是错误条件,并不一定会使我们脱离正常的程序流程.

我经常对无法区分零和无答案的系统感到沮丧.我有很多次系统要求我输入一些数字,我输入零,我收到一条错误消息,告诉我必须在此字段中输入一个值.我刚做了:我进了零!但是它不会接受零,因为它无法区分它和没有答案.


答复桑德斯:

是的,我假设"人没有回答问题"和"答案为零"之间存在差异.这是我答案最后一段的重点.许多程序无法区分"不知道"与空白或零,这在我看来是一个潜在的严重缺陷.例如,我大约一年前在买房子.我去了一个房地产网站,有很多房子上市,要价为0美元.对我来说听起来很不错:他们免费赠送这些房子!但我确信悲惨的事实是他们只是没有进入价格.在这种情况下,你可能会说,"嗯,显然零意味着他们没有进入价格 - 没有人会免费赠送房屋." 但该网站还列出了各个城镇房屋的平均询价和售价.我不禁想知道平均值是否不包含零,因此某些地方的平均值不正确.即100,000美元的平均值是多少; $ 120,000; 和"不知道"?从技术上讲,答案是"不知道".我们可能真正想要的是110,000美元.但我们可能得到的是73,333美元,这是完全错误的.另外,如果我们在用户可以在线订购的网站上遇到此问题怎么办?(不太适用于房地产,但我相信你已经看到它已经为许多其他产品做了.)我们真的想要"未指定价格"被解释为"免费"吗?

RE有两个独立的功能,"有没有?" 和"如果是这样,它是什么?" 是的,你当然可以这样做,但你为什么要这样做?现在调用程序必须进行两次调用而不是一次.如果程序员未能调用"any"会发生什么?然后直奔"它是什么?" ?该程序是否会返回误导零?抛出异常?返回未定义的值?它会创建更多代码,更多工作和更多潜在错误.

我看到的唯一好处是它使您能够遵守任意规则.这条规则是否有任何优势使得值得顺从它的麻烦?如果没有,为什么要这么麻烦?


回复Jammycakes:

考虑一下实际代码的样子.我知道问题说C#但是如果我写Java的话请原谅.我的C#不是很尖锐,原理是一样的.

返回null:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}
Run Code Online (Sandbox Code Playgroud)

具有单独的功能:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}
Run Code Online (Sandbox Code Playgroud)

它实际上是一行或两行代码,返回null,所以它不会给调用者增加负担,而是更少.

我不知道它是如何造成干旱问题的.这不像我们必须执行两次调用.如果我们总是想在列表不存在时做同样的事情,也许我们可以将处理推送到get-list函数而不是让调用者这样做,因此将代码放入调用者将是DRY违规.但我们几乎肯定不想总是做同样的事情.在我们必须要处理列表的函数中,缺少列表是一个可能会停止处理的错误.但是在编辑屏幕上,如果他们还没有输入数据,我们肯定不想停止处理:我们想让他们输入数据.因此,必须以这种或那种方式在呼叫者级别处理"无列表".而且我们是否使用null返回或单独的函数来执行此操作对于更大的原则没有任何区别.

当然,如果调用者没有检查null,程序可能会因空指针异常而失败.但是如果有一个单独的"得到任何"函数并且调用者没有调用该函数但是盲目地调用"获取列表"函数,那么会发生什么?如果它抛出异常或以其他方式失败,那么,这与它返回null并且没有检查它的情况几乎相同.如果它返回一个空列表,那就错了.您无法区分"我有一个零元素列表"和"我没有列表".当用户没有输入任何价格时,就像返回零价:这是错误的.

我没有看到如何将附加属性附加到集合有帮助.呼叫者仍然需要检查它.这比检查null更好吗?同样,可能发生的最糟糕的事情是程序员忘记检查它,并给出不正确的结果.

如果程序员熟悉null的概念意味着"没有价值",那么返回null的函数并不令人惊讶,我认为任何有能力的程序员都应该听说过,无论他认为这是一个好主意.我认为拥有一个单独的功能更像是一个"惊喜"的问题.如果程序员不熟悉API,当他运行没有数据的测试时,他会很快发现有时候他会返回null.但他怎么会发现另一个函数的存在,除非他发现可能有这样的函数并且他检查文档,并且文档是完整和可理解的?我宁愿有一个函数总是给我一个有意义的响应,而不是我必须知道的两个函数,并记得同时调用它们.

  • 为什么有必要在相同的返回值中组合"无应答"和"零"响应?相反,让该方法返回"过去五年中任何以前的住院治疗",并有一个单独的方法,询问,"以前的住院治疗清单是否已填写?".这假设填写没有先前住院治疗的清单与未填写的清单之间存在差异. (4认同)
  • RE每次通话都要区分:嗯,是的,我假设,或者至少是来电者的作者应该做出他不在乎的有意识的决定.如果函数返回"不知道"的空列表,并且调用者盲目地将此处理为"无",则可能会给出严重不准确的结果.想象一下,如果函数是"getAllergicReactionToMedicationList".一个盲目地将"列表没有输入"作为"患者没有已知的过敏反应"的程序可能会导致杀死一个人.在许多其他系统中,你会得到类似的,如果不那么引人注目的结果.... (4认同)
  • RE名称:没有函数名称可以完全描述函数的功能,除非它与函数一样长,因此非常不切实际.但无论如何,如果函数在没有给出答案时返回一个空列表,那么通过相同的推理,它不应该被称为"getHospitalizationListOrEmptyListIfNoAnswer"吗?但实际上,您是否会坚持将Java Reader.read函数重命名为readCharacterOrReturnMinusOneOnEndOfStream?那ResultSet.getInt应该是"getIntOrZeroIfValueWasNull"?等等. (3认同)
  • 但是如果你返回null,你就已经给调用者带来了额外的负担!调用者必须检查null的每个返回值 - 一个可怕的DRY违规.如果你想单独指出"来电者没有回答问题",那么创建一个额外的方法调用来表明这个事实就更简单了.要么是这样,要么使用具有DeclinedToAnswer额外属性的派生集合类型.永不返回null的规则根本不是任意的,它是最不可思议的原则.此外,您的方法的返回类型应该表示它的名称所表示的内容.Null几乎肯定没有. (2认同)
  • 您假设您的getHospitalizationList仅从一个地方调用和/或其所有调用者都希望区分"无应答"和"零"情况.****将是**(几乎肯定是多数)的情况,你的调用者不需要进行区分,因此你强迫他们在不需要的地方添加空值检查.这会给您的代码库带来很大的风险,因为人们很容易忘记这样做 - 并且因为很少**合法的**理由返回null而不是集合,这将更有可能. (2认同)
  • 还有一点.一个名为getHospitalizationList的方法区分"无应答"和"零",它正在做一些超出其名称范围的事情,并且违反单一责任原则进入讨价还价.应该将这种区分的方法称为getHospitalizationListOrNullIfNoAnswer. (2认同)
  • ......如果它真的无关紧要,或者我们只关心在这个系统中输入的数据,那么任何未知的东西都是无关紧要的,那么我当然不会做出区分. (2认同)

Dav*_*und 10

如果一个空集合在语义上有意义,那就是我更喜欢返回的.返回一个空集合进行GetMessagesInMyInbox()通信"你的收件箱中确实没有任何消息",而返回null可能有助于沟通没有足够的数据来说明可能返回的列表应该是什么样子.

  • 在您给出的示例中,如果它不能满足请求而不是仅返回null,则该方法应该抛出异常.对于诊断问题而言,例外比空值更有用. (6认同)

Kar*_*der 6

返回null可能更有效,因为没有创建新对象.但是,它通常还需要null检查(或异常处理).

在语义上,null空列表并不意味着相同的事情.差异是微妙的,在特定情况下,一种选择可能比另一种更好.

无论您的选择如何,请记录下来以避免混淆.

  • 在考虑API设计的正确性时,效率几乎不应成为一个因素.在一些非常特殊的情况下,例如图形基元,它可能是这样,但在处理列表和大多数其他高级事物时,我非常怀疑它. (8认同)

Dan*_*Dan 6

有人可能会争辩说,Null Object Pattern背后的推理类似于支持返回空集合的推理.