函数应该返回null还是空对象?

7wp*_*7wp 209 .net c# function return-value

从函数返回数据时的最佳做法是什么.返回Null或空对象更好吗?为什么要一个人做另一个呢?

考虑一下:

public UserEntity GetUserById(Guid userId)
{
     //Imagine some code here to access database.....

     //Check if data was returned and return a null if none found
     if (!DataExists)
        return null; 
        //Should I be doing this here instead? 
        //return new UserEntity();  
     else
        return existingUserEntity;
}
Run Code Online (Sandbox Code Playgroud)

让我们假设在这个程序中有效的案例,数据库中没有该GUID的用户信息.我想在这种情况下抛出异常是不合适的?此外,我的印象是异常处理可能会损害性能.

ljs*_*ljs 207

如果您打算表明没有可用数据,则返回null通常是最好的想法.

空对象意味着返回了数据,而返回null则清楚地表明没有返回任何内容.

此外,如果您尝试访问对象中的成员,则返回null将导致null异常,这对于突出显示错误代码非常有用 - 尝试访问任何成员都没有意义.访问空对象的成员不会失败意味着错误可能未被发现.

  • @Chris:我不同意.如果代码清楚地记录了返回值为null,那么如果没有找到符合您条件的结果,则返回null是完全可以接受的.抛出异常应该是您的最后选择,而不是您的第一个选择. (130认同)
  • 你应该抛出异常,不要吞下问题并返回null.至少应该记录下来,然后继续. (21认同)
  • 我有点困惑,微软开发人员认为"返回null"等同于"吞噬问题".如果内存服务,框架中有吨方法,如果没有任何东西与调用者的请求匹配,则返回方法null.这是"吞下这个问题吗?" (17认同)
  • @Chris:你在什么基础上决定这个?添加记录到等式**肯定**似乎过度.让消费代码决定在没有用户的情况下应该做什么 - 如果有的话.与我之前的评论一样,返回一个普遍定义为"无数据"的值绝对没有问题. (12认同)
  • 最后但并非最不重要的是`bool GetUserById(Guid userId,out UserEntity result)` - 我更喜欢"null"返回值,而不是抛出异常那么极端.它允许美丽的`null`-free代码,如`if(GetUserById(x,u)){...}`. (5认同)
  • @joseph:您还可以实现任何其他任意方法来返回保留值,但*已经*为"无数据"保留了值:`null`. (3认同)
  • 我想在返回null或抛出异常之间做出选择取决于很多其他因素.它应与其他人保持一致.它不应该在一个错误上返回null并在另一个错误上抛出异常.例外应仅用于发信号错误,而不是改变程序流程.如果(在这个例子中)!DataAvailable是一个可以经常发生的标准情况,我会去返回null.如果应用程序假设DB通常存在,那么Data就在那里,我会去寻找异常,因为那时我可以在应用程序的任何部分处理错误,而不仅仅是调用者. (3认同)
  • 返回NULL是`bool GetUserById(GUID id,out UserEntity user)`的快捷方式,我认为这是完全可以接受的.为什么要采取两个步骤来避免异常......你会这样做吗?`if(userexists(uid)){<getuser>}`并对数据库进行2次调用? (3认同)
  • 返回null,函数本身不会出错,而是表示没有返回任何内容.当然调用者应该测试null,我同意,但是它确实添加了额外的防线,因为即使空对象潜入它也会引发异常.这只是锦上添花,相比之下,你可以表明没有任何东西以普遍理解的方式归还.如果你只是返回一个空对象,你会如何测试它是空的并抛出异常?以这种方式实施起来更加困难和混乱. (2认同)
  • 我认为null和exception都是有效的选项.但是,如果方法抛出异常,则应该为用户提供一种避免异常的方法 - 例如,通过公开公开IsValidId(Guid id)或"GetListOfIds()". (2认同)
  • @Tobias:"它不应该在一个错误上返回null并在另一个错误上抛出异常." 我认为返回null或抛出异常是有意义的.如果将null记录为"在数据库中找不到匹配的用户",则null是此特定函数的预期返回值,尽管它仍可能是应用程序级错误.另一方面,如果数据库服务器关闭,我不希望该函数返回null.我想要一个例外. (2认同)

Rex*_*x M 44

这取决于对你的案件最有意义的东西.

返回null是否有意义,例如"没有这样的用户存在"?

或者创建默认用户是否有意义?当你可以安全地假设如果用户不存在时,这就是最有意义的,调用代码打算在他们要求时存在一个.

或者,如果调用代码要求具有无效ID的用户,则抛出异常(la"FileNotFound")是否有意义?

然而 - 从关注点分离/ SRP的角度来看,前两个更正确.从技术上讲,第一个是最正确的(但只有一个头发) - GetUserById应该只负责一件事 - 获取用户.通过返回其他内容来处理自己的"用户不存在"案例可能违反了SRP.bool DoesUserExist(id)如果您选择抛出异常,则分成不同的检查是合适的.

基于以下广泛的评论:如果这是一个API级设计问题,这种方法可能类似于"OpenFile"或"ReadEntireFile".我们从一些存储库"打开"用户并从结果数据中保存对象.在这种情况下,例外可能是适当的.它可能不是,但它可能是.

所有方法都是可以接受的 - 它只取决于API /应用程序的更大上下文.

  • @Charles:你回答的问题是"应该在某个时候抛出异常",但问题是"这个函数应该抛出异常".正确答案是"可能",而不是"是". (2认同)

Fer*_*ndo 30

就个人而言,我使用NULL.它清楚地表明没有数据可以返回.但是有些情况下Null对象可能有用.


Dar*_*rov 27

如果返回类型是数组,则返回一个空数组,否则返回null.

  • 令我感到惊讶的是,这还没有得到更多的支持.这对我来说似乎是一个非常合理的指导方针. (5认同)
  • 列表中的0项与"null"不同.它允许您在`foreach`语句和linq查询中使用它,而不必担心`NullReferenceException`. (3认同)

Hen*_*man 12

如果特定合同被破坏,您应该抛出异常(仅限).
在您的具体示例中,根据已知的Id请求UserEntity,如果缺少(已删除)用户是预期的案例,则将取决于事实.如果是,则返回null但如果不是预期的情况则抛出异常.
请注意,如果调用该函数,UserEntity GetUserByName(string name)它可能不会抛出但返回null.在这两种情况下,返回一个空的UserEntity将是无益的.

对于字符串,数组和集合,情况通常是不同的.我记得一些指南形式的MS,方法应该接受null为"空"列表,但返回零长度的集合而不是null.字符串相同.请注意,您可以声明空数组:int[] arr = new int[0];


Cha*_*ana 11

这是一个业务问题,取决于具有特定Guid Id的用户是否是此功能的预期正常用例,或者是否会阻止应用程序成功完成此方法为用户提供的任何功能的异常反对...

如果它是一个"例外",因为缺少具有该Id的用户将阻止应用程序成功完成它正在执行的任何功能,(假设我们正在为客户创建发票,我们已将产品发送到... ),那么这种情况应该抛出ArgumentException(或其他一些自定义异常).

如果缺少用户没问题,(调用此函数的潜在正常结果之一)则返回null ....

编辑:(在另一个答案中解决亚当的评论)

如果应用程序包含多个业务流程,其中一个或多个需要用户才能成功完成,并且其中一个或多个业务流程可以在没有用户的情况下成功完成,那么异常应该在调用堆栈中进一步抛出,更靠近哪里需要用户的业务流程正在调用此执行线程.此方法与该点(抛出异常)之间的方法应该只传达没有用户存在(null,boolean,无论什么 - 这是一个实现细节).

但是如果应用程序中的所有进程都需要用户,我仍然会在此方法中抛出异常......


Ale*_*ore 10

我个人会返回null,因为这就是我期望DAL/Repository层行动的方式.

如果它不存在,不要返回任何可以被解释为成功获取对象的东西,null在这里工作得很漂亮.

最重要的是要在你的DAL/Repos Layer中保持一致,这样你就不会对如何使用它感到困惑.


Joh*_*ell 7

我倾向于

  • return null如果当它不知道它是否事前对象ID不存在存在.
  • throw如果对象id在它应该存在时存在.

我用这三种方法区分这两种情况.第一:

Boolean TryGetSomeObjectById(Int32 id, out SomeObject o)
{
    if (InternalIdExists(id))
    {
        o = InternalGetSomeObject(id);

        return true;
    }
    else
    {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

第二:

SomeObject FindSomeObjectById(Int32 id)
{
    SomeObject o;

    return TryGetObjectById(id, out o) ? o : null;
}
Run Code Online (Sandbox Code Playgroud)

第三:

SomeObject GetSomeObjectById(Int32 id)
{
    SomeObject o;

    if (!TryGetObjectById(id, out o))
    {
        throw new SomeAppropriateException();
    }

    return o;
}
Run Code Online (Sandbox Code Playgroud)

  • 真的,这是唯一一个通用的答案,因此是完全和唯一的真理!:)是的,它取决于*假设*基于该方法被调用...所以首先清除这些假设,然后选择上述的正确组合.不得不向下滚动到达这里:) +100 (2认同)

Mar*_*arc 6

另一种方法涉及传入将对值进行操作的回调对象或委托.如果未找到值,则不会调用回调.

public void GetUserById(Guid id, UserCallback callback)
{
    // Lookup user
    if (userFound)
        callback(userEntity);  // or callback.Call(userEntity);
}
Run Code Online (Sandbox Code Playgroud)

当您希望避免在代码中进行空检查时,以及在未找到值时不是错误,这种方法很有效.如果您需要任何特殊处理,也可以在没有找到对象时提供回调.

public void GetUserById(Guid id, UserCallback callback, NotFoundCallback notFound)
{
    // Lookup user
    if (userFound)
        callback(userEntity);  // or callback.Call(userEntity);
    else
        notFound(); // or notFound.Call();
}
Run Code Online (Sandbox Code Playgroud)

使用单个对象的相同方法可能如下所示:

public void GetUserById(Guid id, UserCallback callback)
{
    // Lookup user
    if (userFound)
        callback.Found(userEntity);
    else
        callback.NotFound();
}
Run Code Online (Sandbox Code Playgroud)

从设计的角度来看,我真的很喜欢这种方法,但是它的缺点是使得呼叫站点在不易支持一流功能的语言中变得更加庞大.