关于c#:我似乎陷入了NullReferenceException的巨大麻烦

I seem to have fallen into some massive, massive trouble with NullReferenceExceptions

最近,我开发了一个软件,可以解析和显示网站上的XML信息。很简单,对吧?

我正在获取大量nullreferenceexceptions。例如,此方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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循环周围包装一个难看得像罪恶的东西。

我觉得我只是把创可贴贴贴在一个漏气的轮胎上,而不是实际解决问题。我有办法解决这个问题吗?也许我应该读一本关于编程模式的书,或者什么?

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


听起来,如果在方法中收到错误的参数,您可能不确定该怎么做。您现在所做的并没有本质上的错误,但是更常见的模式是检查方法头中的参数,如果这些参数不是您所期望的,则抛出异常:

1
2
3
4
if (list == null)
{
    throw new ArgumentNullException(list);
}

这是一种常见的防御性编程模式——检查以确保提供的数据通过了基本的健全性检查。

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


我可能会被"没有多个出口"的人群否决,但我通常会在方法的开头简单检查一下:

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

这指定了退出条件,然后您就不需要担心方法中存在多个级别的缩进。只有当你能接受你的列表是空的或者是空的这一事实时,这才有效——这在某些情况下可能发生。

但我同意codeka,因为你需要看看调用代码,并找出是否可以从中改进它。


似乎防御性编程和参数验证是您要寻找的。

正如其他人所说,简单的参数验证可以为您工作:

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

或者,如果您厌倦了为每个参数不断地编写这样的检查,那么您可以签出许多开放源码.NET前提条件强制库中的一个。我喜欢刀刃。条件。

这样,您就可以使用如下内容:

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

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


除了抛出argumentNullException异常之外,还有一个叫做"空obejct模式"的东西,如果您想传递一个空值,可以使用它来表示,例如,某个东西不存在,但不想显式地检查空值。本质上,它是一个实现相同接口的存根类,但它的方法通常是空的,或者返回的值足以使它们完成。http://en.wikipedia.org/wiki/null_对象_模式

对于不能轻易地表示不存在的值类型(不能为空)也很有用。


你的确问错了问题。正确的问题是"空值是否表示无效的输入或表示x的标志"。

在向语言中添加非空引用类型并使其成为各种API之前,您可以选择在代码中显式地使用该类型,或者让空引用异常帮助您找到违反期望的位置,然后以某种方式修复数据/代码。


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

例如:

1
2
3
4
5
6
7
private void SetUserFriends(List<Friend> list)
{
    if (list == null)
        return;

    /* Do stuff */
}

或者,您可以使用常规的空合并模式:

1
2
3
4
5
6
private void SetUserFriends(List<Friend> list)
{
    list = list ?? new List<Friend>();

    /* Do Stuff */
}