【问题标题】:C# - Correct way to check for nullable DateTime in if statementC# - 在 if 语句中检查可为空的 DateTime 的正确方法
【发布时间】:2021-01-21 03:27:13
【问题描述】:

我有以下 code 方法,该方法旨在检查用户在两小时内登录的最大尝试次数(例如 10 次)无效后是否被暂时阻止。

public MyError ValidationMethod(MyObject myObject) {
    int maxMaximumAttempts = 10;

    if (myObject.Attempts >= maximumAttempts && myObject.LastAttempt.Value.AddHours(2)) < DateTime.Now)
        return new MyError();

    return null;
}

“Attempts”和“LastAttempt”这两个 DateTime 字段都可以为空,那么设置此 if 语句的格式以确保不会出现空引用异常的正确方法是什么?

任何一个字段都为 Null,应该与 if 语句返回 false 的结果相同,即他们之前没有尝试登录。然后我将从方法本身返回 null,这表示输入没有发现错误。

我认为字段为空会使整个语句为假,但我得到一个空引用异常,谁能解释一下为什么?

编辑:我添加了完整的方法以使上下文更清晰。

【问题讨论】:

  • 如果任一值为空,你希望发生什么?
  • 做相反的myObject.LastAttempt &lt; DateTime.Now.AddHours(-2)。如果LastAttempt 为空,这将失败。你必须决定如果要做什么。 LastAttempt 实际上是 null
  • 在这种情况下为 Null 意味着他们之前没有登录失败,因此与代码返回 false 的结果相同。此代码位于验证方法中,因此如果检查为假,我将返回 null。
  • 然后使用myObject.LastAttempt &lt; DateTime.Now.AddHours(-2)
  • 不使用可为空的类型,而是使用普通的 DateTime,但对未初始化的情况使用 DateTime.Max 或 DateTime.Min 值。那么你就不用担心 null 了。

标签: c#


【解决方案1】:

你可以使用这个代码:

if (myObject != null && myObject.LastAttempt != null)
  if (myObject.Attempts >= maximumAttempts && myObject.LastAttempt.Value.AddHours(2) < DateTime.Now)
     return new myError();

或:

try
{
  if (myObject.Attempts >= maximumAttempts && myObject.LastAttempt.Value.AddHours(2) < DateTime.Now)
     return new myError();
}
catch(Exception ex)
{
   return new myError(ex.Message);
}

【讨论】:

    【解决方案2】:

    不要为 nullables 烦恼,使用 DateTime.MaxTime 作为尚未设置的值。 对于尝试使用 0 作为尚未设置的值

     public class myType {
        // initialize myObject.LastAttempt = DateTime.MaxTime
        DateTime LastAttempt = DateTime.Max;
        int Attempts = 0;
    
    /*
    .... rest of the class
    */
    
    }
    
    public MyError ValidationMethod(MyObject myObject) {
        int maxMaximumAttempts = 5;
    
        if (myObject.Attempts >= maximumAttempts && myObject.LastAttempt != DateTime.Max && myObject.LastAttempt.AddHours(2)) < DateTime.Now)
            return new MyError();
    
        return null;
    }
    

    【讨论】:

    • 谢谢你,但我需要使用可空类型来与数据库兼容。否则,这是一个很好的答案。
    • 您可以在序列化和反序列化期间将 DB 空值映射到这些应用程序常量。如果不能,则需要使用 == nul 检查 null,或使用 HasValue() 方法检查 not null。或者为了使代码更紧凑,您可以将空检查和 Value 属性方法调用与 ?.Value 运算符结合使用。不过你需要小心。 ?.不适用于所有情况,这里可能不适用,因为结果分配不是可空类型。
    【解决方案3】:

    可空的DateTime 对象有一个名为HasValue 的属性,所以你可以这样写:

    if (myObject.Attempts.HasValue && myObject.Attempts.Value >= maximumAttempts && myObject.LastAttempt.HasValue && myObject.LastAttempt.Value.AddHours(2) < DateTime.Now)
        return new myError(); 
    

    【讨论】:

      【解决方案4】:

      首先,我会将这些检查移至包含 AttemptsLastAttempt 字段的类(或至少为此类创建扩展) - 检查 Tell Don't Ask Principle

      public class MyObject 
      {
           public bool IsBlockedOn(DateTime time)
           {
              if (!Attempts.HasValue || !LastAttempt.HasValue)
                  return false;
      
              var hasTooManyAttempts = MaximumAttempts <= Attempts.Value;
              var timeoutPassed = Timeout < (time - LastAttempt.Value);
              return hasTooManyAttempts && !timeoutPassed;
           }
      
           private TimeSpan Timeout = TimeSpan.FromHours(2);
           private int MaximumAttempts = 10;
           // other properties
      }
      

      你的 if 语句:

      if (myObject.IsBlockedOn(DateTime.Now))
         return new MyError();
      

      好的部分 - 这个逻辑现在是可单元测试的,因为您可以使用不同的时间参数来执行它。 另外,考虑使 Attempts 不可为空并将其初始化为 0。

      【讨论】:

        猜你喜欢
        • 2021-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多