这是一个普遍的问题(但是我正在使用C#),最好的方法是什么(最佳实践),对于以集合为返回类型的方法,您是否返回null或空集合?
这是一个普遍的问题(但是我正在使用C#),最好的方法是什么(最佳实践),对于以集合为返回类型的方法,您是否返回null或空集合?
Answers:
空集合。总是。
这很烂:
if(myInstance.CollectionProperty != null)
{
foreach(var item in myInstance.CollectionProperty)
/* arrgh */
}
null
在返回集合或可枚举的对象时,永远不要返回是一种最佳实践。 总是返回一个空的可枚举/集合。它可以防止上述的废话,并防止您的汽车被班上的同事和用户所困扰。
在谈论属性时,请始终设置一次属性,然后忘记它
public List<Foo> Foos {public get; private set;}
public Bar() { Foos = new List<Foo>(); }
在.NET 4.6.1中,您可以将其压缩得很多:
public List<Foo> Foos { get; } = new List<Foo>();
当谈论返回枚举的方法时,您可以轻松地返回一个空的枚举而不是null
...
public IEnumerable<Foo> GetMyFoos()
{
return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}
Enumerable.Empty<T>()
与返回例如新的空集合或数组相比,使用效率更高。
IEnumerable
或ICollection
不管那么多了。无论如何,如果您选择某种类型的东西,ICollection
它们也会返回null
...我希望他们返回一个空集合,但是我遇到了他们的returning null
,所以我想在这里只提一下。我会说一个可枚举集合的默认值为空而不是null。我不知道这是一个敏感的话题。
根据《框架设计指南第二版》(第256页):
不要从集合属性或返回集合的方法中返回空值。而是返回一个空集合或一个空数组。
这是另一篇有趣的文章,介绍了不返回空值的好处(我试图在Brad Abram的博客中找到某些内容,并且他链接到该文章)。
编辑-正如埃里克·利珀特(Eric Lippert)现在评论原始问题时,我也想链接到他的出色文章。
取决于您的合同和具体情况。通常,最好返回空集合,但有时(很少):
null
可能意味着更具体的内容;null
。一些具体的例子:
null
这意味着元素丢失,而空集合将导致冗余(可能不正确)<collection />
还有一点尚未提及。考虑以下代码:
public static IEnumerable<string> GetFavoriteEmoSongs()
{
yield break;
}
调用此方法时,C#语言将返回一个空的枚举数。因此,为了与语言设计保持一致(并因此符合程序员的期望),应返回一个空集合。
空的对消费者更友好。
有一种明确的方法可以构成一个空的可枚举的对象:
Enumerable.Empty<Element>()
在我看来,无论上下文如何,您都应该返回在语义上正确的值。一条说“总是返回空集合”的规则对我来说似乎有点简单。
假设在一个医院系统中,我们有一个函数应该返回过去5年中所有以前住院的清单。如果客户没有去过医院,则返回空列表是很有意义的。但是,如果客户将准入表格的这一部分留空,该怎么办?我们需要一个不同的值来区分“空列表”与“无答案”或“不知道”。我们可以抛出异常,但这不一定是错误条件,也不一定会使我们脱离正常的程序流程。
我经常对无法区分零答案和无答案的系统感到沮丧。我曾多次要求系统输入一些数字,然后输入零,然后收到一条错误消息,告诉我必须在该字段中输入一个值。我只是做:我输入了零!但是它不会接受零,因为它无法将它与没有答案区分开。
回复桑德斯:
是的,我假设“人员未回答问题”与“答案为零”之间存在差异。这就是我回答的最后一段的重点。许多程序无法区分“不知道”与空白还是零,在我看来,这是一个潜在的严重缺陷。例如,大约一年前,我正在买房。我去了一个房地产网站,列出了许多要价为0美元的房屋。对我来说听起来不错:他们免费提供这些房屋!但是我确信可悲的现实是他们还没有输入价格。在这种情况下,您可能会说:“好吧,零显然意味着他们没有输入价格-没有人会免费提供房屋。” 但是该网站还列出了各个城镇房屋的平均要价和售价。我不禁要问,平均值是否不包含零,因此某些地方的平均值不正确。即,平均$ 100,000是多少?$ 120,000;和“不知道”?从技术上讲,答案是“不知道”。我们可能真正希望看到的是110,000美元。但是我们可能会得到的是73,333美元,这是完全错误的。另外,如果我们在用户可以在线订购的网站上遇到此问题,该怎么办?(对于房地产而言,这不太可能,但是我敢肯定,您已经在许多其他产品上看到了这一点。)我们是否真的希望将“未指定价格”解释为“免费”?因此,某些地方的平均值偏低。即,平均$ 100,000是多少?$ 120,000;和“不知道”?从技术上讲,答案是“不知道”。我们可能真正希望看到的是110,000美元。但是我们可能会得到的是73,333美元,这是完全错误的。另外,如果我们在用户可以在线订购的网站上遇到此问题,该怎么办?(对于房地产而言,这不太可能,但是我敢肯定,您已经在许多其他产品上看到了这一点。)我们是否真的希望将“未指定价格”解释为“免费”?因此,某些地方的平均值偏低。即,平均$ 100,000是多少?$ 120,000;和“不知道”?从技术上讲,答案是“不知道”。我们可能真正希望看到的是110,000美元。但是我们可能会得到的是73,333美元,这是完全错误的。另外,如果我们在用户可以在线订购的网站上遇到此问题,该怎么办?(对于房地产而言,这不太可能,但是我敢肯定,您已经在许多其他产品上看到了这一点。)我们是否真的希望将“未指定价格”解释为“免费”?这是完全错误的。另外,如果我们在用户可以在线订购的网站上遇到此问题,该怎么办?(对于房地产而言,这不太可能,但是我敢肯定,您已经在许多其他产品上看到了这一点。)我们是否真的希望将“未指定价格”解释为“免费”?这是完全错误的。另外,如果我们在用户可以在线订购的网站上遇到此问题,该怎么办?(对于房地产而言,这不太可能,但是我敢肯定,您已经在许多其他产品上看到了这一点。)我们是否真的希望将“未指定价格”解释为“免费”?
RE具有两个独立的功能,“有吗?” 还有“如果是的话,那是什么?” 是的,您当然可以这样做,但是为什么要这么做?现在,调用程序必须进行两次调用,而不是一次。如果程序员未能调用“ any”会发生什么?然后直接进入“这是什么?” ?程序会返回误导性的零吗?抛出异常?返回未定义的值?它创建更多的代码,更多的工作和更多的潜在错误。
我看到的唯一好处是,它使您能够遵守任意规则。该规则是否有任何优势值得遵循呢?如果没有,何必呢?
回复果酱蛋糕:
考虑一下实际的代码是什么样子。我知道问题说的是C#,但是如果我写Java,请原谅。我的C#不是很敏锐,原理是一样的。
返回空值:
HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
// ... handle missing list ...
}
else
{
for (HospEntry entry : list)
// ... do whatever ...
}
具有单独的功能:
if (patient.hasHospitalizationList(patientId))
{
// ... handle missing list ...
}
else
{
HospList=patient.getHospitalizationList(patientId))
for (HospEntry entry : list)
// ... do whatever ...
}
实际上,它是一两行或更少的具有null返回值的代码,因此对调用方的负担不大,也就更少。
我看不到它是如何造成DRY问题的。好像我们不必执行两次调用。如果在列表不存在时我们总是想做同样的事情,也许我们可以将处理向下推到get-list函数,而不是让调用者来做,因此将代码放在调用者中将违反DRY。但是,我们几乎肯定不想总是做同样的事情。在必须要处理列表的函数中,缺少列表是一个错误,很可能会中断处理。但是在编辑屏幕上,如果他们还没有输入数据,我们当然不希望停止处理:我们希望让他们输入数据。因此,必须以一种或另一种方式在调用者级别上处理“无列表”。而且,无论我们使用null返回值还是使用单独的函数执行此操作,都不会影响更大的原则。
当然,如果调用者不检查null,则程序可能会因null指针异常而失败。但是,如果有一个单独的“获取任何信息”功能,而调用者没有调用该功能,而是盲目地调用了“获取列表”功能,那么会发生什么呢?如果它抛出异常或其他原因失败,那几乎与如果它返回null并且不检查它会发生什么情况相同。如果它返回一个空列表,那是错误的。您无法区分“我有一个零元素的列表”和“我没有一个列表”。这就像在用户未输入任何价格时返回零价格:这是错误的。
我看不到在集合中附加附加属性有何帮助。呼叫者仍然必须检查它。这比检查null更好吗?同样,可能发生的绝对最糟糕的事情是程序员忘记检查它并给出错误的结果。
如果程序员熟悉null的意思是“没有值”,那么返回null的函数就不足为奇了,我认为任何称职的程序员都应该听说过,无论他认为这是一个好主意。我认为拥有单独的职能更多是“惊喜”问题。如果程序员不熟悉API,则当他在没有数据的情况下运行测试时,他会迅速发现有时他会返回null。但是他怎么会发现另一个功能的存在,除非他想到可能存在这样一个功能并且他检查了文档,并且文档是完整且可理解的?我宁愿有一个总是能给我有意义的响应的函数,而不是两个我必须知道并记得要同时调用的函数。
如果空集合在语义上有意义,那就是我更愿意返回的内容。返回一个空集合以进行GetMessagesInMyInbox()
通信“收件箱中实际上没有任何消息”,而返回null
可能对传达足够的数据来说明可能要返回的列表的样子很有用。
null
值肯定看起来不合理,我是在更笼统地考虑那个值。异常也很好地传达了出问题的事实,但是,如果完全希望所指的“数据不足”,则抛出异常将导致设计不佳。我想的是一种完全有可能且该方法有时无法计算响应的完全没有错误的方案。
返回null可能更有效,因为不会创建新对象。但是,它通常也需要null
检查(或异常处理)。
从语义上说,null
空列表并不意味着同一件事。差异是细微的,在特定情况下,一种选择可能会比另一种更好。
无论您选择哪种方式,都要记录在案,以免造成混淆。
我认为这null
与空集合不是一回事,您应该选择最能代表您所返回的东西。在多数情况下null
,什么都不是(SQL中除外)。一个空的集合是一个东西,尽管是一个空的东西。
如果您必须选择其中一个,那么我想您应该倾向于一个空集合而不是null。但是有时候空集合与空值不是同一回事。
我想在这里举例说明。
在这里考虑一个案例。
int totalValue = MySession.ListCustomerAccounts()
.FindAll(ac => ac.AccountHead.AccountHeadID
== accountHead.AccountHeadID)
.Sum(account => account.AccountValue);
在这里考虑我正在使用的功能..
1. ListCustomerAccounts() // User Defined
2. FindAll() // Pre-defined Library Function
我可以轻松使用ListCustomerAccount
,FindAll
而不是。
int totalValue = 0;
List<CustomerAccounts> custAccounts = ListCustomerAccounts();
if(custAccounts !=null ){
List<CustomerAccounts> custAccountsFiltered =
custAccounts.FindAll(ac => ac.AccountHead.AccountHeadID
== accountHead.AccountHeadID );
if(custAccountsFiltered != null)
totalValue = custAccountsFiltered.Sum(account =>
account.AccountValue).ToString();
}
注意:由于AccountValue不是null
,Sum()函数不会返回null
。,因此我可以直接使用它。
在大多数情况下,返回空集合更好。
这样做的原因是实现调用方的便利性,一致的合同以及更容易实现。
如果方法返回null表示结果为空,则调用程序除枚举外还必须实现null检查适配器。然后,此代码在各个调用方中重复,因此为什么不将该适配器放在方法中以便可以重用。
IEnumerable的null的有效用法可能表示结果缺失或操作失败,但是在这种情况下,应考虑其他技术,例如引发异常。
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace StackOverflow.EmptyCollectionUsageTests.Tests
{
/// <summary>
/// Demonstrates different approaches for empty collection results.
/// </summary>
class Container
{
/// <summary>
/// Elements list.
/// Not initialized to an empty collection here for the purpose of demonstration of usage along with <see cref="Populate"/> method.
/// </summary>
private List<Element> elements;
/// <summary>
/// Gets elements if any
/// </summary>
/// <returns>Returns elements or empty collection.</returns>
public IEnumerable<Element> GetElements()
{
return elements ?? Enumerable.Empty<Element>();
}
/// <summary>
/// Initializes the container with some results, if any.
/// </summary>
public void Populate()
{
elements = new List<Element>();
}
/// <summary>
/// Gets elements. Throws <see cref="InvalidOperationException"/> if not populated.
/// </summary>
/// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>.</returns>
public IEnumerable<Element> GetElementsStrict()
{
if (elements == null)
{
throw new InvalidOperationException("You must call Populate before calling this method.");
}
return elements;
}
/// <summary>
/// Gets elements, empty collection or nothing.
/// </summary>
/// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with zero or more elements, or null in some cases.</returns>
public IEnumerable<Element> GetElementsInconvenientCareless()
{
return elements;
}
/// <summary>
/// Gets elements or nothing.
/// </summary>
/// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with elements, or null in case of empty collection.</returns>
/// <remarks>We are lucky that elements is a List, otherwise enumeration would be needed.</remarks>
public IEnumerable<Element> GetElementsInconvenientCarefull()
{
if (elements == null || elements.Count == 0)
{
return null;
}
return elements;
}
}
class Element
{
}
/// <summary>
/// http://stackoverflow.com/questions/1969993/is-it-better-to-return-null-or-empty-collection/
/// </summary>
class EmptyCollectionTests
{
private Container container;
[SetUp]
public void SetUp()
{
container = new Container();
}
/// <summary>
/// Forgiving contract - caller does not have to implement null check in addition to enumeration.
/// </summary>
[Test]
public void UseGetElements()
{
Assert.AreEqual(0, container.GetElements().Count());
}
/// <summary>
/// Forget to <see cref="Container.Populate"/> and use strict method.
/// </summary>
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void WrongUseOfStrictContract()
{
container.GetElementsStrict().Count();
}
/// <summary>
/// Call <see cref="Container.Populate"/> and use strict method.
/// </summary>
[Test]
public void CorrectUsaOfStrictContract()
{
container.Populate();
Assert.AreEqual(0, container.GetElementsStrict().Count());
}
/// <summary>
/// Inconvenient contract - needs a local variable.
/// </summary>
[Test]
public void CarefulUseOfCarelessMethod()
{
var elements = container.GetElementsInconvenientCareless();
Assert.AreEqual(0, elements == null ? 0 : elements.Count());
}
/// <summary>
/// Inconvenient contract - duplicate call in order to use in context of an single expression.
/// </summary>
[Test]
public void LameCarefulUseOfCarelessMethod()
{
Assert.AreEqual(0, container.GetElementsInconvenientCareless() == null ? 0 : container.GetElementsInconvenientCareless().Count());
}
[Test]
public void LuckyCarelessUseOfCarelessMethod()
{
// INIT
var praySomeoneCalledPopulateBefore = (Action)(()=>container.Populate());
praySomeoneCalledPopulateBefore();
// ACT //ASSERT
Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
}
/// <summary>
/// Excercise <see cref="ArgumentNullException"/> because of null passed to <see cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})"/>
/// </summary>
[Test]
[ExpectedException(typeof(ArgumentNullException))]
public void UnfortunateCarelessUseOfCarelessMethod()
{
Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
}
/// <summary>
/// Demonstrates the client code flow relying on returning null for empty collection.
/// Exception is due to <see cref="Enumerable.First{TSource}(System.Collections.Generic.IEnumerable{TSource})"/> on an empty collection.
/// </summary>
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void UnfortunateEducatedUseOfCarelessMethod()
{
container.Populate();
var elements = container.GetElementsInconvenientCareless();
if (elements == null)
{
Assert.Inconclusive();
}
Assert.IsNotNull(elements.First());
}
/// <summary>
/// Demonstrates the client code is bloated a bit, to compensate for implementation 'cleverness'.
/// We can throw away the nullness result, because we don't know if the operation succeeded or not anyway.
/// We are unfortunate to create a new instance of an empty collection.
/// We might have already had one inside the implementation,
/// but it have been discarded then in an effort to return null for empty collection.
/// </summary>
[Test]
public void EducatedUseOfCarefullMethod()
{
Assert.AreEqual(0, (container.GetElementsInconvenientCarefull() ?? Enumerable.Empty<Element>()).Count());
}
}
}
我称之为十亿美元的错误……当时,我正在设计第一个全面的类型系统,以面向对象的语言进行引用。我的目标是确保对引用的所有使用都绝对安全,并由编译器自动执行检查。但是我无法抗拒引入空引用的诱惑,只是因为它是如此易于实现。这导致了无数错误,漏洞和系统崩溃,在最近四十年中可能造成十亿美元的痛苦和破坏。– ALGOL W的发明人Tony Hoare。
有关一般的详细说明,请参见此处null
。我不同意undefined
另一种说法null
,但仍然值得一读。它解释了为什么您应该完全避免null
,而不仅仅是在您提出要求的情况下。本质是,null
在任何语言中这都是特例。您必须考虑null
一个例外。undefined
与此不同,在大多数情况下,处理未定义行为的代码只是一个错误。C语言和大多数其他语言也具有未定义的行为,但是大多数语言没有该语言的标识符。