如何识别所有可能导致 C# 中的 NullReferenceException 的代码块

How to identify all possible code blocks which can cause the NullReferenceException in c#

我在 .NET 4.7.2 中使用 C# 和 Entity Framework 的一个项目在多个场景中导致 NullReferenceException。代码库庞大,因此在使用前分析代码和处理可空对象非常困难。

大多数异常发生在我们尝试使用 Entity Framework 访问给定实体的相关空实体或空实体集合(通过外键关联)时。如果外键 table.

中不存在数据,则该相关实体将存在或为空

我们假设相关对象过去必须存在,但现在大多数 table 的结构已更改,这些对象现在可能不存在。

我的代码太长,很难分析和更改可空对象处理。

所以有人可以向我建议解决方案,给我一个代码块,将来会导致 NullReferenceException 吗?

有什么工具会在 NullReferenceException 发生的地方发出警告?

NullReferenceException 是您从第一天起就必须考虑的事情。在引用总是被设置的假设下编写代码会为 bug 敞开大门,这些 bug 在未来往往看起来是间歇性的。据我所知,这个问题没有“快速解决方案”,但是您可以采取重构步骤来减少空值的影响,并帮助确保在调用堆栈实际有用的地方发生意外空值周围的异常:

#1。从 Linq 表达式中删除所有 *OrDefault() 方法,它们没有明确处理数据可能不会返回的事实。这意味着将 FirstOrDefault() 替换为 First() 或更好的 Single(),您希望有 1 条记录。您仍然会遇到异常,但它将在实体被读取时清楚地表明什么条件无效与稍后在不检查 #null 的情况下使用实体的代码中。

#2。初始化实体中的集合属性。这有助于确保实体中的集合在创建新实体时“准备就绪”,以防接收实体的函数可能获得新行而不是现有行。即

public virtual ICollection<OrderLine> OrderLines { get; set; } = new List<OrderLine>();

#3。确保启用延迟加载,并将引用和集合标记为 virtual。这绝不是理想的,但恕我直言,延迟加载调用比 NullReferenceException 更好。如果调用恰好是在 DbContext 的范围之外进行的,那么您仍然会收到异常,但通常会更容易确定访问的内容。

#4。添加快速失败验证。在接受存储的构造函数参数时,添加空检查断言。对于接受任何可能为空的方法,添加断言。您会得到异常,但有意义的异常将帮助您确定错误的来源。例如:

// Constructor injected references:
oublic OrderService(IOrderRepository repository)
{
    // change this:
    // _repository = repository;

    // ... to this:
    _repository = repository ?? throw new ArgumentNullException("repository");
}

// Parameter assertion:

public void AddCustomer(Customer customer)
{   
    // add these to assert your parameters.
    if(customer == null) throw new ArgumentNullException("customer");

    // ...
}

这些通常是快速重构,可以在整个现有应用程序中非常不引人注目地应用。

#5。旨在消除除插入新实体之外的任何构造实体的代码。我遇到的常见示例如下:

.Select(x => new Order { /* populating some Order columns */ })

其中 Order 是一个实体 class 但被用作视图模型或其他一些临时数据容器。除了使用 context.Orders.Add(order); 之外的任何其他 new-ing 实体的代码都应该在有偏见的情况下进行重构。实体 class 应始终反映数据的完整或可完整表示。 (完整的意思是被跟踪的实体仍然在它的 DbContext 的范围内并且能够在需要时延迟加载)任何被调用接受 Order 实体的函数应该期望收到一个完整且有效的订单,其中所有引用要么是预加载的要么是延迟的可加载。传递的所有实体也都应该关联到同一个 DbContext,因此理想情况下,应该重构与 Web 客户端之间的实体序列化代码以使用投影,或者非常谨慎地确保实体重新关联到当前范围内的 DbContext。

最终你面临的是一种技术债务,就像所有债务一样,你在通过假设节省开发时间方面早期获得的“信用”会产生利息,这将增加时间来追踪错误并最终永远“修复”错误的假设。您需要从童子军的角度看待应用程序,并力求让它比开始时更干净。