不应为 null 的 C# 变量将引发 null 引用异常

  • 本文关键字:null 引用 异常 变量 c#
  • 更新时间 :
  • 英文 :


我发现了一个无法解释的柯尔情况。 基本上属性"UserCalls"在开始时工作正常,但是在应用程序开始很长时间(超过一个月(之后,不应该采用空值的属性在调用函数时会抛出空异常(例如linq.FirstOrDefault(( (。

代码示例:

public class UserCall
{
public long UserID { get; set; }
public long CallID { get; set; }
}
public static class Cache
{
private static List<UserCall> userCalls = new List<UserCall>();
public static List<UserCall> UserCalls
{
get
{
if (userCalls == null)
{
userCalls = new List<UserCall>();
}
return userCalls;
}
set
{
userCalls = value;
}
}
private static void AddCall(long userID, long callID)
{
UserCalls.Add(
new UserCall
{
CallID = callID,
UserID = userID
});
}

public static UserCall GetCall(long userID)
{
var userCall = UserCalls.FirstOrDefault(x => x.UserID == userID);
return userCall;
}
public static void RemoveCall(long userID)
{
UserCalls.RemoveAll(x => x.UserID == userID);
}
}

调用"GetCall"时会引发以下异常:

[ERROR] Object reference not set to an instance of an object. | at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)

方法"AddCall","GetCall"和"RemoveCall"可以从多个线程调用。 此例外在更多负载的服务上随机发生(但仅在服务启动后很长时间后(。如果发生异常,它将锁定在此永久空状态,并且不会自行更正,只有服务重新启动才会有所帮助。 我知道此实现并不完全是线程安全的,但它仍然不应该返回 null。 为什么会这样?有没有人遇到过类似的情况?

UserCalls属性在多线程上下文中可以为null。

鉴于:

public static List<UserCall> UserCalls
{
get
{
if (userCalls == null)
{
userCalls = new List<UserCall>();
}
return userCalls;
}
set
{
userCalls = value;
}
}

考虑以下具有两个线程 [A] 和 [B] 的序列,其中userCalls当前为非 null。

[A] Accesses `UserCalls` getter. 
[A] Checks if `userCalls` is null. It is not, so it will skip the assignment.    
[B] Accesses `UserCalls` setter, about to set it to `null`.
[B] Sets `userCalls` to null.
[A] Returns `userCalls`, which is now null => BANG!

您可以通过使用锁来防止这种情况:

public static List<UserCall> UserCalls
{
get
{
lock (_locker)
{
if (userCalls == null)
{
userCalls = new List<UserCall>();
}
return userCalls;
}
}
set
{
lock (_locker)
{ 
userCalls = value;
}
}
}
static object _locker = new object();

但是,请注意,没有什么可以阻止某些东西将UserCalls元素之一设置为null.如果某事这样做,您还将在像UserCalls.FirstOrDefault(x => x.UserID == userID);这样的调用中得到NullReferenceException,因为x将是空的,使用x.UserID会给你空引用异常。

我建议:

  1. 删除属性用户调用并仅将列表用作字段。这 防止添加空值或列表的可能性 由您自己的进程以外的其他进程修改。
  2. 列表项

添加锁以使列表线程的使用安全

代码:

public static class Cache
{
private static List<UserCall> userCalls = new List<UserCall>();    
private static void AddCall(long userID, long callID)
{
lock(userCalls)
{
userCalls.Add(
new UserCall
{
CallID = callID,
UserID = userID
});
}
}

public static UserCall GetCall(long userID)
{
UserCall userCall = null;
lock(userCalls)
userCall = UserCalls.FirstOrDefault(x => x.UserID == userID);
return userCall;
}
public static void RemoveCall(long userID)
{
lock(userCalls)
userCalls.RemoveAll(x => x.UserID == userID);
}
}

最新更新