【问题标题】:I seem to have fallen into some massive, massive trouble with NullReferenceExceptionsNullReferenceExceptions 我似乎陷入了一些巨大的麻烦
【发布时间】:2010-07-12 03:50:15
【问题描述】:

最近我正在开发一种软​​件,可以解析和显示来自网站的 XML 信息。够简单吧?

我收到大量 NullReferenceExceptions。比如这个方法:

private void SetUserFriends(List<Friend> list)
{
    int x = 40;
    int y = 3;

    if (list != null)
    {
        foreach (Friend friend in list)
        {
            FriendControl control = new FriendControl();
            control.ID = friend.ID;
            control.URL = friend.URL;
            control.SetID(friend.ID);
            control.SetName(friend.Name);
            control.SetImage(friend.Photo);

            control.Location = new Point(x, y);
            panel2.Controls.Add(control);

            y = y + control.Height + 4;
        } 
    }
}

为了防止异常,我不得不在实际的 foreach 循环周围包裹一个丑陋的 as sin If。

我觉得我只是在爆胎上贴了创可贴,而不是真正解决问题。有什么办法可以解决这个问题吗?也许我应该读一本关于编程模式的书还是什么?

真的,我迷路了。我可能问错了问题。

【问题讨论】:

  • 您应该查看调用 SetUserFriends 的代码。如果您假设朋友列表不应该是null(我会说这是一个足够公平的假设),那么错误就在于传递 in null 的任何内容。遇到异常时使用调试器查找调用堆栈。
  • 最好检查一下为什么你有一个空的 List 引用而不是一个空的 List 对象。
  • 这是一个旁注,但我主张接受 IEnumerable,因此该方法不需要调用者使用特定的集合类。
  • @Steven 如果你需要列表功能,至少是一个 IList
  • @Darko:是的,当然。无论最低限度足够的接口是什么。

标签: c# .net nullreferenceexception


【解决方案1】:

如果您在方法中收到错误参数,您似乎不确定该怎么办。您现在所做的事情本身并没有错,但更常见的模式是检查方法头部的参数,如果它们不是您所期望的,则抛出异常:

if (list == null)
{
    throw new ArgumentNullException(list);
}

这是一种常见的防御性编程模式 - 检查以确保您提供的数据通过基本的完整性检查。

现在,如果您自己显式调用此方法,并且您发现此方法接收到一个空的list 参数,而您并不期待它,那么是时候查看调用方法的逻辑了。我自己,我更喜欢在没有元素时传递一个空列表,而不是null,以避免这种特殊情况。

【讨论】:

  • +1 如果您始终如一地这样做,您将痛苦地学习如何编写避免无意空值的代码。
【解决方案2】:

我可能会被“没有多重退出”的人群投票否决,但我通常在方法开始时通过简单的检查来处理这个问题:

if (list == null || list.Count == 0) return;

这指定了退出条件,然后您无需担心方法中的多级缩进。这只有在您有能力接受您的列表为空或为空的事实时才有效 - 这在某些情况下可能会发生。

但我同意 codeka,因为您需要查看调用代码并确定是否可以从那里改进它。

【讨论】:

  • 这种方法可能适用于这种特定情况,但通常会导致非常微妙的错误。大多数情况下,我们无法通过传入null 合理地推断出调用者的意图,因此通过异常中止是最安全的。
  • 应该记录一个方法是否接受 null,以及(理想情况下)如果不满足这个前提条件它会做什么。我同意@Rex 的观点,即无声返回通常会损害调试。
  • 我完全同意你们俩的观点,但在某些情况下它可能有用
  • 如果代码不应该接受 null,那么静默失败将是不好的,正确的答案是抛出异常。但是,空值很可能是可接受的输入。视情况而定,所以我们不要在这里操之过急。
  • @Steven 在这种情况下,OP 正在抱怨他通过让空值通过而陷入的所有问题。所以... :)
【解决方案3】:

您正在寻找防御性编程和参数验证。

正如其他人所说,简单的参数验证对您有用:

if (list == null)
    throw new ArgumentNullException("list");

或者,如果您厌倦了不断为每个参数编写这样的检查,您可以查看许多开源 .NET 前置条件强制库之一。我喜欢CuttingEdge.Conditions

这样,你可以使用这样的东西:

Condition.Requires(list, "list").IsNotNull();

但是,像上述任何一个那样设置前提条件只会指定您的方法不接受空值。 您的问题仍然存在,因为您将空值传递给方法!要解决这个问题,您必须检查调用方法的原因,并找出传入空对象的原因。

【讨论】:

  • 和 +1 用于引用 CuttingEdge.Conditions ;-)
【解决方案4】:

除了抛出 ArgumentNullException 异常之外,还有一种叫做“Null Obejct Pattern”的东西,如果你想要传递一个空值,你可以使用它来表明,例如,某事没有' t 存在,但不想显式检查空值。本质上它是一个实现相同接口的存根类,但它的方法通常要么是空的,要么返回刚好足以使它们变得完整。 http://en.wikipedia.org/wiki/Null_Object_pattern

由于不能为空,对于不能轻易表达其不存在的值类型也很有用。

【讨论】:

  • 很有趣,但我不确定它是否适用于这里,因为这里最接近空对象模式的是保留一个空列表。我在别处指出的另一个问题是,此方法可能应该接受 IEnumerable 而不是 List。
  • 是的,但他提到这只是他的“加载 NullReferenceExceptions”的一个例子。对于他的其他一些人来说,这可能是正确的解决方案。对于这个,我也会抛出异常。
  • 我的印象是,这与其说是忘记检查 null 的问题,不如说是不知道为什么它首先为 null 的问题。空对象模式的目的是避免空检查,但它不会提供对这里更深层次问题的任何洞察。然而,异常会,因为它们会抑制空值的传播,从而更容易跟踪堆栈跟踪到空值潜入的位置。
【解决方案5】:

如果输入无效,我会提前返回(或提前抛出 InvalidArgumentException)。

例如:

private void SetUserFriends(List<Friend> list) 
{ 
    if (list == null) 
        return;

    /* Do stuff */
}

您也可以使用一般的空值合并模式:

private void SetUserFriends(List<Friend> list) 
{ 
    list = list ?? new List<Friend>();

    /* Do Stuff */
}

【讨论】:

  • 不过,对于这种情况,null 合并建议有点荒谬。
  • 早退和投掷是非常不同的方法。你推荐哪个?
  • 嗯,如果你要抛出异常,正确的应该是 ArgumentNullException,正如 Michael 的示例所示。
  • @Dan:同意。分配一个空列表只是为了不做任何事情是没有意义的。
  • @Dan 同意,但是 OP 确实询问了您通常如何处理它,我只是想给他们一个想法。 @Rex,在某些方面它们并没有那么不同(它们阻止方法做它的事情),但它具有高度的上下文关系。如果您来到柜台并忘记带任何东西购买,我不必将您赶出我的商店(我可以“不处理”)。如果您给我的支票会被退回,那么可能是时候引起别人的注意了。
【解决方案6】:

你确实问错了问题。正确的问题是“null 是否表示无效输入或表示 X 的标志”。

在将非空引用类型添加到语言中并使用各种 API 之前,您可以选择在代码中明确说明,或者让空引用异常帮助您找到违反预期的地方,然后修复数据/以一种或另一种方式编码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-21
    • 1970-01-01
    • 2022-01-16
    • 2013-01-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多