这是一个一般性的问题(但我使用c#),最好的方法(最佳实践)是什么,对于一个有集合作为返回类型的方法,你返回null集合还是空集合?
当前回答
我认为null与空集合不同,您应该选择哪一个最能代表您要返回的内容。在大多数情况下,null什么都不是(SQL除外)。空的收藏是一种东西,尽管是一种空的东西。
如果你不得不选择一个或另一个,我会说你应该倾向于一个空集合而不是null。但有时空集合与空值并不相同。
其他回答
这取决于你的合同和具体情况。 通常最好返回空集合,但有时(很少):
Null可能意味着更具体的东西; 你的API(合约)可能会强制你返回null。
一些具体的例子:
一个UI组件(来自一个不受你控制的库),如果传递一个空集合,可能会呈现一个空表,或者如果传递null,则根本不呈现表。 在Object-to-XML (JSON/其他)中,null表示元素缺失,而空集合将呈现冗余(可能不正确)<collection /> 你正在使用或实现一个明确声明null应该返回/传递的API
还有一点尚未被提及。考虑下面的代码:
public static IEnumerable<string> GetFavoriteEmoSongs()
{
yield break;
}
在调用此方法时,c#语言将返回一个空枚举数。因此,为了与语言设计(以及程序员的期望)保持一致,应该返回一个空集合。
返回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());
}
}
}
Go似乎是首选nil而不是空数组的一种语言。
https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices
当声明一个空切片时,优先使用var t []string而不是t:= []string{}。前者声明一个nil切片值,而后者非nil但长度为0。它们在功能上是等价的——它们的len和cap都是0——但是nil片是首选样式。
推荐文章
- 虚拟方法和抽象方法的区别
- i++和++i的区别是什么?
- 可空对象必须有一个值
- 按类型查找WPF窗口中的所有控件
- 为什么我不能继承静态类?
- 为什么java.util.Set没有get(int index)?
- 如何在c#中获取CPU的使用情况?
- BindingFlags。IgnoreCase不为Type.GetProperty()工作?
- 使用私有静态方法的优点
- 一个任务被取消了?
- 新DateTime()与默认值(DateTime)
- 从Description属性中获取Enum
- 从包含文件名的路径获取不包含文件名的完整路径
- 如何从字符串的开始或结束删除所有空白?
- 为什么使用try {} finally{}和一个空的try块?