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


209

从函数返回数据时的最佳实践是什么。返回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;
}

让我们假设在该程序中可能存在这样的情况:具有该GUID的数据库中将没有用户信息。我会想象在这种情况下抛出异常是不合适的?另外,我的印象是异常处理会损害性能。


4
我想你的意思是if (!DataExists)
Sarah Vessels,2009年

107
这是一个体系结构问题,非常合适。无论OP要解决的业务问题如何,OP的问题都是有效的。
约瑟夫·弗里斯

2
这个问题已经被充分回答。我认为这是一个非常有趣的问题。
约翰·西弗奥妮

“ getUser()”应返回null。“getCurrentUserInfo()”或“getCurrentPermissions()”,OTOH,将更能说明问题的问题-他们应该返回一个非空的答案对象,无论谁/或是否有人已登录。
托马斯W¯¯

2
没有@Bergi,另一个是重复的。首先,在十月份问了我的地雷,在十二月后的三个月问了另一个。再加上另一个人谈论的是一个略有不同的集合。
7wp

Answers:


207

如果您打算指示没有可用数据,则返回null通常是最好的主意。

空对象表示已返回数据,而返回null则表示未返回任何内容。

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


21
您应该抛出一个异常,不要吞下该问题并返回null。至少,您应该对此进行记录,然后继续。
克里斯·巴伦斯

130
@克里斯:我不同意。如果代码清楚地表明返回值是null,那么如果找不到符合您条件的结果,则返回null是完全可以接受的。抛出异常应该是您的最后选择,而不是第一个选择。
Mike Hofer,

12
@克里斯:您根据什么决定?在等式中增加测井显然是多余的。让使用方代码决定在没有用户的情况下应采取的措施(如果有的话)。与我之前的评论一样,返回被普遍定义为“无数据”的值绝对没有问题。
亚当·罗宾逊

17
微软开发人员认为“返回null”等同于“减轻问题”,这让我有些困惑。如果有内存,框架中有很多方法,如果没有任何匹配调用者请求的方法,则返回null。那是“吞噬问题”吗?
Mike Hofer,

5
最后但并非最不重要的一点是bool GetUserById(Guid userId, out UserEntity result)-我宁愿使用“空”返回值,也不如抛出异常那样极端。它允许null类似的漂亮代码if(GetUserById(x,u)) { ... }
Marcel Jackwerth,2009年

44

这取决于哪种情况最适合您的情况。

返回null是否有意义,例如“不存在这样的用户”?

还是创建默认用户有意义?当您可以安全地假设一个用户不存在时,这才最有意义。

还是如果调用代码要求使用无效ID的用户抛出异常(例如“ FileNotFound”),是否有意义?

但是,从关注点/ SRP的角度来看,前两个更正确。从技术上讲,第一个是最正确的(但只能靠头发)-GetUserById只应负责一件事-获取用户。通过返回其他内容来处理其自己的“用户不存在”的情况可能违反了SRP。分开检查- bool DoesUserExist(id)如果您确实选择引发异常,则比较合适。

基于以下广泛的评论:如果这是API级设计问题,则此方法可能类似于“ OpenFile”或“ ReadEntireFile”。我们正在从某个存储库中“打开”用户,并从结果数据中添加对象。在这种情况下,例外可能是适当的。可能不是,但可能是。

所有方法都是可以接受的-这取决于API /应用程序的较大上下文。


有人对您投了反对票,而我对您投了反对票,因为这对我来说似乎不是一个不好的答案。除了:在像海报提供的方法中找不到用户时,我永远不会抛出异常。如果发现没有用户意味着一个无效的ID或一些这样的例外,值得的问题,即应该发生上涨-投掷方法需要更多地了解其中该ID从等来了
雅各布马蒂森

(我猜这种低票是对在这种情况下引发异常的想法的反对。)
Jacob Mattison

1
同意,直到最后一点。通过返回一个普遍定义为“无数据”的值,不会违反SRP。这就像说如果where子句不产生任何结果,则SQL数据库应返回错误。尽管Exception是一种有效的设计选择(尽管这会激怒我作为使用者),但它没有比返回null更“正确”的选择了。不,我不是DV。
亚当·罗宾逊

@JacobM当我们要求文件系统路径不存在,不返回null或从数据库返回时,会引发异常。因此,显然两者都是合适的,这就是我要得到的-这取决于。
Rex M

2
@Charles:您正在回答问题“应该在某个时候抛出异常”,但是问题是“此函数是否应该抛出异常”。正确答案是“也许”,而不是“是”。
亚当·罗宾逊

30

我个人使用NULL。很明显,没有数据要返回。但是在某些情况下,空对象可能有用。


自己将其添加为答案。NullObjectPattern或特殊情况的模式。然后,你可以实现一个针对每种情况,NoUserEntitiesFound,NullUserEntities等
大卫·斯温德尔斯

27

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


列表中的0项与此时未分配的列表相同吗?
AnthonyWJones

3
清单中的0个项目与相同null。它使您可以在foreach语句和linq查询中使用它,而无需担心NullReferenceException
达林·迪米特洛夫

5
我很惊讶这还没有得到更多的支持。对我来说,这似乎是一个非常合理的准则。
shashi 2012年

好吧,空容器只是空对象模式的特定实例。我们无法确定哪个合适。
Deduplicator 2015年

当数据不可用时返回一个空数组是完全错误的。可用的数据和不包含任何项目的数据与不可用的数据之间是有区别的。在这两种情况下都返回一个空数组将使您无法得知哪种情况。这样做只是让你可以使用foreach,而不检查存在,即使该数据是否超出傻-来电者应该要检查是否存在数据和一个NullReferenceException如果呼叫者不检查是好的,因为它暴露了一个错误..
恢复莫妮卡2015年

12

如果特定合同违约,则应该(仅)引发异常。
在您的特定示例中,基于已知ID询问UserEntity,这取决于事实(如果缺少(删除)用户)是预期情况。如果是这样,则返回,null但如果不是预期的情况,则抛出异常。
请注意,如果调用该函数,UserEntity GetUserByName(string name)则可能不会抛出该异常,但返回null。在这两种情况下,返回一个空的UserEntity都是无济于事的。

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


很高兴您提到字符串是不同的,因为Google在我决定是否返回空字符串时向我展示了这一点。
Noumenon

字符串,集合和数组没有不同。如果MS这么说,MS是错误的。空字符串和null之间以及空collection和null之间存在区别。在这两种情况下,前者代表现有数据(大小为0),而后者代表缺乏数据。在某些情况下,区别非常重要。例如,如果您在高速缓存中查找条目,则想知道正在高速缓存但为空的数据与未高速缓存的数据之间的区别,因此必须从底层数据源中获取数据,而实际情况可能不是空的。
恢复莫妮卡

1
您似乎错过了重点和背景。.Wher(p => p.Lastname == "qwerty")应该返回一个空集合,而不是null
汉克·霍尔特曼

@HenkHolterman如果您可以访问完整集合并应用一个不接受该集合中任何项目的过滤器,则空集合是正确的结果。但是,如果不存在完整的集合,那么空集合将极具误导性-根据情况是正常还是异常,将null或抛出正确。由于您的帖子不符合您所谈论的情况(现在您澄清了您所谈论的是前一种情况),并且由于OP谈论的是后一种情况,所以我不同意您的看法。
恢复莫妮卡

11

这是一个业务问题,取决于是否存在具有特定Guid ID的用户是此功能的正常预期使用情况,还是异常情况会阻止应用程序成功完成此方法提供给用户的任何功能反对...

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

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

编辑:(以解决来自亚当的评论)

如果应用程序包含多个业务流程,其中一个或多个需要用户才能成功完成,而一个或多个流程可以在没有用户的情况下成功完成,则应在调用堆栈的更远处(更靠近位置)抛出异常。需要用户的业务流程正在调用此执行线程。此方法与该点之间的方法(引发异常的位置)仅应传达不存在任何用户的信息(null,boolean,无论如何,这是实现细节)。

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


反对者为-1,查尔斯为+1-完全是一个商业问题,没有最佳实践。
奥斯汀·萨洛宁

它越过溪流。它是否是“错误条件”是由业务逻辑决定的。如何处理是应用程序体系结构的决定。业务逻辑不会要求返回空值,而只是满足要求。如果企业要确定方法返回类型,那么它们也将涉及实现的技术方面。
约瑟夫·弗里斯

@joseph,“结构化异常处理”背后的核心原则是,当方法无法完成其编码实现的任何功能时,应引发异常。您是对的,因为如果此方法已编码为实现的业务功能可以“成功完成”,(无论在域模型中是什么意思),那么就不需要抛出异常,则可以返回null ,或布尔“ FoundUser”变量,或其他...如何与找不到用户的调用方法进行通信,这将成为技术实现的详细信息。
2009年

10

我个人将返回null,因为这是我期望DAL /存储库层起作用的方式。

如果它不存在,请不要返回任何可以被解释为成功获取对象的东西,null在这里效果很好。

最重要的是要在DAL / Repos层上保持一致,这样您就不会对如何使用它感到困惑。


7

我倾向于

  • return null如果当它不知道是不存在的对象ID事前是否应该存在。
  • throw如果对象ID不存在,在何时存在。

我用这三种类型的方法来区分这两种情况。第一:

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

        return true;
    }
    else
    {
        return false;
    }
}

第二:

SomeObject FindSomeObjectById(Int32 id)
{
    SomeObject o;

    return TryGetObjectById(id, out o) ? o : null;
}

第三:

SomeObject GetSomeObjectById(Int32 id)
{
    SomeObject o;

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

    return o;
}

您的意思out不是ref
Matt Ellen

@Matt:是的,先生,我当然可以!固定。
Johann Gerell

2
的确,这是唯一的“一刀切”的答案,因此完全是唯一的真理!:)是的,这取决于调用该方法所基于的假设 ...因此,请首先清除这些假设,然后选择以上内容的正确组合。不得不向下滚动太多才能到达这里:) +100
yair

这似乎是要使用的模式,只是它不支持异步方法。我引用了这个答案,并在Tuple
Literals中

6

还有另一种方法涉及传递将对值进行操作的回调对象或委托。如果找不到值,则不调用回调。

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

当您要避免对代码进行空检查,并且未找到值不是错误时,此方法非常有用。如果需要任何特殊处理,还可以为找不到对象时提供回调。

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

使用单个对象的相同方法可能类似于:

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

从设计的角度来看,我真的很喜欢这种方法,但是它的缺点是使呼叫站点使用了不容易支持一流功能的语言。


有趣。当您开始谈论委托时,我立即开始怀疑Lambda表达式是否可以在这里使用。
9wp

对!据我了解,C#3.0及更高版本的lambda语法基本上是匿名委托的语法糖。类似地,在Java中,如果没有很好的lambda或匿名委托语法,则只需创建一个匿名类即可。这有点丑陋,但可以非常方便。我想这些天来,我的C#示例本可以使用Func <UserEntity>或类似的东西代替命名委托,但是我上一个进行的上一个C#项目仍在使用版本2。–
Marc

+1我喜欢这种方法。但是,问题在于它不是常规的,并且稍微增加了代码库的进入壁垒。
timoxley

4

我们使用CSLA.NET,它认为失败的数据提取应返回“空”对象。这实际上很烦人,因为它要求检查是否obj.IsNew不是的惯例obj == null

如前所述,空返回值将导致代码立即失败,从而减少了由空对象引起的隐身问题的可能性。

我个人认为null比较优雅。

这是一个非常常见的情况,令我感到惊讶的是,这里的人们似乎对此感到惊讶:在任何Web应用程序上,数据通常都是使用querystring参数来获取的,显然可以对它进行处理,因此要求开发人员处理“未找到”的情况。 ”。

您可以通过以下方式处理此问题:

如果(User.Exists(id)){
  this.User = User.Fetch(id);
}其他{
  Response.Redirect(“〜/ notfound.aspx”);
}

...但是这每次都是对数据库的额外调用,这在高流量页面上可能是个问题。鉴于:

this.User = User.Fetch(id);

如果(this.User == null){
  Response.Redirect(“〜/ notfound.aspx”);
}

...只需要一个电话。


4

我更喜欢null,因为它与null运算符(??)兼容。


4

我会说返回null而不是一个空对象。

但是,您在此处提到的特定实例是按用户ID搜索用户,这是该用户的键,在这种情况下,如果找不到用户实例,我可能想抛出异常。

这是我通常遵循的规则:

  • 如果在主键操作的find上没有找到结果,则抛出ObjectNotFoundException。
  • 如果通过任何其他条件未在搜索结果中找到结果,则返回null。
  • 如果在非关键条件下未找到可能返回多个对象的结果,则返回一个空集合。

在这些情况下,为什么会引发异常?有时用户不存在于数据库中,我们希望这种情况不会发生。这不是异常行为。
siride

3

它会根据上下文而变化,但是如果我正在寻找一个特定的对象(如您的示例),则通常将返回null;如果我正在寻找一组对象但没有一个对象,则通常返回一个空集合。

如果您在代码中犯了一个错误,并且返回null会导致null指针异常,那么越早发现它越好。如果返回一个空对象,则可以初次使用它,但是稍后可能会出错。


+1我在质疑您在这里说的相同逻辑,这就是为什么我发布问题以查看其他人对此有何看法
2009年

3

在没有这种用户的情况下,最好在这种情况下返回“ null”。还要使您的方法静态。

编辑:

通常,这样的方法是某些“ User”类的成员,并且无法访问其实例成员。在这种情况下,该方法应该是静态的,否则,您必须创建“ User”的实例,然后调用GetUserById方法,该方法将返回另一个“ User”实例。同意这令人困惑。但是,如果GetUserById方法是某些“ DatabaseFactory”类的成员-将其保留为实例成员没有问题。


我可以问一下为什么要使我的方法静态化吗?如果我想使用依赖注入怎么办?
09年

好的,现在我明白了。但是我坚持使用Repository模式,并且我喜欢对我的存储库使用依赖项注入,因此我不能使用静态方法。但是+1表示建议返回null :)
7wp

3

我个人返回了该对象的默认实例。原因是我希望该方法返回零对多或零对一(取决于方法的目的)。使用这种方法,它将是任何类型的错误状态的唯一原因是,如果该方法未返回任何对象并且始终被期望(以一对多或单数返回)。

关于这是一个业务领域问题的假设-我只是从等式的那一侧看不到它。返回类型的规范化是一个有效的应用程序体系结构问题。至少,它是编码实践中标准化的主题。我怀疑是否有业务用户会说“在方案X中,请给他们一个空值”。


+1我喜欢问题的另一种观点。因此,基本上,您是说我选择哪种方法都可以,只要该方法在整个应用程序中都是一致的?
09年

1
那是我的信念。我认为一致性非常重要。如果您在多个地方以多种方式执行操作,则会增加新错误的风险。我们个人采用默认的对象方法,因为它与我们在域模型中使用的Essence模式配合得很好。我们有一个通用的扩展方法,可以对所有域对象进行测试以告诉我们是否填充了该域,以便我们知道可以通过调用objectname.IsDefault()来测试任何DO-避免直接进行任何相等性检查。
约瑟夫·费里斯

3

在我们的业务对象中,我们有两个主要的Get方法:

为了使事情在上下文中简单,或者您怀疑它们是:

// Returns null if user does not exist
public UserEntity GetUserById(Guid userId)
{
}

// Returns a New User if user does not exist
public UserEntity GetNewOrExistingUserById(Guid userId)
{
}

获取特定实体时使用第一种方法,而在网页上添加或编辑实体时专门使用第二种方法。

这使我们能够在使用它们的环境中拥有两全其美的优势。


3

我是法国IT专业的学生,​​请原谅我可怜的英语。在我们的类中,我们被告知这样的方法永远不要返回null或空对象。该方法的用户应该在尝试获取对象之前先检查其寻找的对象是否存在。

使用Java,要求我们assert exists(object) : "You shouldn't try to access an object that doesn't exist";在任何可能返回null的方法的开头添加一个,以表示“前提”(我不知道英语中的单词是什么)。

IMO这确实不容易使用,但这就是我在使用,等待更好的东西。


1
感谢您的回答。但是我不喜欢先检查是否存在的想法。原因是生成对数据库的附加查询。在一天中有数百万人访问的应用程序中,这可能会导致严重的性能损失。
09年

1
好处之一是对存在的检查是适当抽象的:if(userExists)更具可读性,更接近问题域,并且比'if computery'少:if(user == null)
timoxley

我会说“ if(x == null)”是一个已有数十年历史的模式,如果您以前从未看过它,那么您已经很长时间没有编写代码了(您应该按原样习惯)数百万行代码)。“计算机”?我们正在谈论数据库访问...
Lloyd Sargent 2014年

3

如果找不到用户的情况经常出现,并且您希望根据情况(有时抛出异常,有时替换空用户)以各种方式处理该问题,那么您也可以使用接近F#Option或Haskell Maybe类型的值,将“无价值”情况与“发现某物!”明确区分开来。数据库访问代码如下所示:

public Option<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 Option<UserEntity>.Nothing; 
 else
    return Option.Just(existingUserEntity);
}

并像这样使用:

Option<UserEntity> result = GetUserById(...);
if (result.IsNothing()) {
    // deal with it
} else {
    UserEntity value = result.GetValue();
}

不幸的是,每个人似乎都推出了自己的这种类型。


2

我通常返回null。它提供了一种快速简便的机制来检测是否发生了一些错误,而不会抛出异常,并且在整个地方都进行了大量尝试/捕获。


2

对于集合类型,我将返回一个空集合,对于所有其他类型,我更喜欢使用NullObject模式返回一个对象,该对象实现了与返回类型相同的接口。有关模式的详细信息,请查看链接文本

使用NullObject模式,它将是:-

public UserEntity GetUserById(Guid userId)

{//想像一下这里的代码来访问数据库.....

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

}

class NullUserEntity: IUserEntity { public string getFirstName(){ return ""; } ...} 

2

说得更通俗一点

例外情况适用于特殊情况

如果此方法是纯数据访问层,那么我会说,给定一个包含在select语句中的参数,它将期望我找不到从中构建对象的任何行,因此返回null将是可以接受的是数据访问逻辑。

在另一方面,如果我希望我的参数来反映一个主键,我应该只得到一个行回来,如果我得到了不止一回我会抛出异常。0可以返回null,2不能。

现在,如果我有一些登录代码根据LDAP提供程序进行了检查,然后针对数据库进行了检查以获取更多详细信息,并且我希望这些信息始终保持同步,那么我可能会抛出异常。正如其他人所说,这是业务规则。

现在,我要说这是一般规则。有时候您可能想打破它。但是,我的经验和对C#(很多)和Java(有点)的实验告诉我,处理异常要比通过条件逻辑处理可预测的问题贵得多。在某些情况下,我所说的价格要贵2到3个数量级。因此,如果您的代码有可能最终陷入循环,那么我建议您返回null并对其进行测试。


2

原谅我的伪PHP /代码。

我认为这实际上取决于结果的预期用途。

如果打算编辑/修改返回值并保存它,则返回一个空对象。这样,您可以使用相同的函数在新对象或现有对象上填充数据。

假设我有一个函数,该函数需要一个主键和一个数据数组,用数据填充行,然后将结果记录保存到db。由于我打算以任何一种方式用我的数据填充对象,因此从吸气剂中回收空对象可能是一个巨大的优势。这样,无论哪种情况,我都可以执行相同的操作。无论如何,都可以使用getter函数的结果。

例:

function saveTheRow($prim_key, $data) {
    $row = getRowByPrimKey($prim_key);

    // Populate the data here

    $row->save();
}

在这里,我们可以看到相同的一系列操作操作了该类型的所有记录。

但是,如果返回值的最终目的是读取数据并对其进行处理,那么我将返回null。这样,我可以非常迅速地确定是否没有数据返回,并向用户显示适当的消息。

通常,我会在获取数据的函数中捕获异常(这样我就可以记录错误消息等),然后直接从捕获中返回null。对于最终用户来说,问题通常无关紧要,所以我发现最好将错误日志记录/处理直接封装在获取数据的函数中。如果您要在任何大型公司中维护共享的代码库,这将特别有益,因为您甚至可以在最懒惰的程序员上强制进行正确的错误记录/处理。

例:

function displayData($row_id) {
    // Logging of the error would happen in this function
    $row = getRow($row_id);
    if($row === null) {
        // Handle the error here
    }

    // Do stuff here with data
}

function getRow($row_id) {
 $row = null;
 try{
     if(!$db->connected()) {
   throw excpetion("Couldn't Connect");
  }

  $result = $db->query($some_query_using_row_id);

  if(count($result) == 0 ) {
   throw new exception("Couldn't find a record!");
  }

  $row = $db->nextRow();

 } catch (db_exception) {
  //Log db conn error, alert admin, etc...
  return null; // This way I know that null means an error occurred
 }
 return $row;
}

这是我的一般规则。到目前为止效果很好。


2

有趣的问题,我认为没有“正确”的答案,因为它始终取决于代码的责任。您的方法是否知道没有找到的数据是否有问题?在大多数情况下,答案是“否”,这就是为什么返回null并让呼叫者处理情况是完美的原因。

区分投掷方法和返回null的方法的一种好方法也许是在团队中找到一个约定:如果说什么都“得到”,那么如果什么也没得到,就应该抛出异常。可能返回null的方法可以用不同的名称命名,也许用“ Find ...”代替。


+1我喜欢这样的想法:使用统一的命名约定向程序员发送信号,告知如何使用该函数。
09年

1
突然我意识到这就是LINQ的作用:考虑First(...)vs.FirstOrDefault(...)
Marc Wittke

2

如果返回的对象是可以迭代的对象,那么我将返回一个空对象,这样就不必先测试null。

例:

bool IsAdministrator(User user)
{
    var groupsOfUser = GetGroupsOfUser(user);

    // This foreach would cause a run time exception if groupsOfUser is null.
    foreach (var groupOfUser in groupsOfUser) 
    {
        if (groupOfUser.Name == "Administrators")
        {
            return true;
        }
    }

    return false;
}

2

我不希望从任何方法中返回null,而是使用Option函数类型。无法返回结果的方法将返回一个空的Option,而不是null。

同样,此类无法返回结果的方法应通过其名称来表明这一点。我通常将Try或TryGet或TryFind放在方法名称的开头,以指示它可能返回空结果(例如TryFindCustomer,TryLoadFile等)。

这使调用者可以对结果应用不同的技术,例如集合流水线(请​​参阅Martin Fowler的Collection Pipeline)。

这是另一个示例,其中返回Option而不是null可以减少代码复杂度:如何降低循环复杂度:Option功能类型


1
我写了一个答案,向上滚动时可以看到它类似于您的答案,并且我同意,您可以使用具有0或1个元素的通用集合来实现选项类型。感谢您的其他链接。
加布里埃尔·

1

需要绞尽脑汁:假设某些建议,我的DAL为GetPersonByID返回NULL。如果我的BLL(比较稀薄)收到NULL,应该怎么办?传递该NULL并让最终用户担心它(在这种情况下,是ASP.Net页)?让BLL抛出异常怎么样?

BLL可能正在由ASP.Net和Win App或其他类库使用-我认为期望最终消费者从本质上“知道”方法GetPersonByID返回null是不公平的(除非使用null类型,我想)。

我的看法(价值)是,如果什么也没找到,我的DAL将返回NULL。对于某些对象,这没关系-可能是0:很多事物列表,所以没有任何事物是可以的(例如,最喜欢的书的列表)。在这种情况下,我的BLL返回一个空列表。对于大多数单个实体的事物(例如用户,帐户,发票),如果我没有,那肯定是一个问题,并且会引发代价高昂的异常。但是,将异常视为由应用程序先前提供的唯一标识符来检索用户应该总是返回一个用户,该异常是“适当的”异常,这是例外。BLL的最终用户(ASP.Net,f'rinstance)只希望事情是笨拙的,因此将使用未处理的异常处理程序,而不是将对GetPersonByID的每个调用都包装在try-catch块中。

如果我的方法存在明显问题,请告诉我,因为我一直热衷于学习。正如其他发布者所说的那样,例外是代价高昂的事情,“先检查”方法是好的,但是例外应仅是例外。

我喜欢这篇文章,对于“取决于情况”的场景有很多好的建议:-)


当然,今天我遇到了一种情况,我将从BLL返回NULL ;-)也就是说,我仍然可以抛出异常,并在使用的类中使用try / catch,但仍然有问题:我的消费类如何知道使用try / catch,类似地他们如何知道检查NULL?
迈克·金斯科特

您可以通过@throws doctag记录方法抛出异常,并在@return doctag中记录该方法可以返回null的事实。
timoxley

1

为了代码库的健康,我认为函数不应返回null。我可以想到一些原因:

将有大量的保护条款处理空引用if (f() != null)

什么是null,是公认的答案还是问题?null是否为特定对象的有效状态?(假设您是该代码的客户端)。我的意思是所有引用类型都可以为null,但是应该吗?

null游逛几乎总是给从时间一些意想不到的NullRef例外时间你的代码库的增长。

有一些解决方案,tester-doer patternoption type从功能编程中实现。


0

我对(在整个网络上)答案的数量感到困惑,他们说您需要两种方法:“ IsItThere()”方法和“ GetItForMe()”方法,因此这导致了竞争状况。返回null,将其分配给变量并在一次测试中检查变量是否为Null的函数有什么问题?我以前的C代码充满了

if(NULL!=(variable = function(arguments ...))){

因此,您可以在一个变量中获得值(或null),并立即获得所有结果。这个习语被遗忘了吗?为什么?


0

我同意这里的大多数帖子,倾向于null

我的理由是,生成具有不可为空的属性的空对象可能会导致错误。例如,具有int ID属性的实体的初始值为ID = 0,这是一个完全有效的值。如果在某种情况下该对象被保存到数据库中,那将是一件坏事。

对于任何带有迭代器的东西,我将始终使用空集合。就像是

foreach (var eachValue in collection ?? new List<Type>(0))

我认为代码有异味。集合属性永远不能为null。

边缘情况是String。许多人说,String.IsNullOrEmpty这并不是必须的,但是您不能总是区分空字符串和null。此外,某些数据库系统(Oracle)根本不会在它们之间进行区分(''存储为DBNULL),因此您不得不同等地处理它们。原因是,大多数字符串值要么来自用户输入,要么来自外部系统,而文本框和大多数交换格式都没有针对''和的不同表示形式null。因此,即使用户想要删除一个值,他也只能清除输入控件。同样nvarchar,如果您的DBMS不是oracle,则可空数据库字段和不可空数据库字段之间的区别也值得怀疑-强制字段允许''很奇怪,您的用户界面绝对不允许这样做,因此您的约束无法映射。因此,我认为这里的答案是,始终平等地对待它们。

关于有关异常和性能的问题:如果抛出无法在程序逻辑中完全处理的异常,则必须在某个时候中止程序正在执行的任何操作,并要求用户重做他所做的任何事情。在那种情况下,a的性能损失catch实际上是您最省心的事情-必须询问用户房间里的大象(这意味着重新渲染整个UI或通过Internet发送一些HTML)。因此,如果您不遵循“ 带异常的程序流 ”的反模式,请不要打扰,只要有意义就扔一个。即使在极端情况下,例如“ Validation Exception”,性能也不是问题,因为在任何情况下都必须再次询问用户。


0

一个异步TryGet模式:

对于同步方法,我相信@Johann Gerell的 答案是在所有情况下都可以使用模式。

但是,带有out参数的TryGet模式不适用于Async方法。

使用C#7的元组文字,您现在可以执行以下操作:

async Task<(bool success, SomeObject o)> TryGetSomeObjectByIdAsync(Int32 id)
{
    if (InternalIdExists(id))
    {
        o = await InternalGetSomeObjectAsync(id);

        return (true, o);
    }
    else
    {
        return (false, default(SomeObject));
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.