【问题标题】:Ensure that an object is not null确保对象不为空
【发布时间】:2011-11-08 14:29:14
【问题描述】:

如何确保某个类的某个实例永远不会为空?有人告诉我使用 Debug.Assert() 但这样做,我只会确保代码在调试模式下工作,而我也想确保发布时的 is-never-null 条件。

例如,过去我写的代码如下:

public string MyString
{
get
{
    if(instance1.property1.Equals("bla"))
    {
        return bla; 
    }
}
}

但如果 instance1 为空,则会引发异常。我想避免在将来犯这样的错误并产生这样的异常。

谢谢,


请参阅下面的具体示例来说明问题:

我有一种方法可以根据服务器的响应对用户进行身份验证。方法是这样的:

        /// <summary>
    /// attempts authentication for current user
    /// </summary>
    /// <returns></returns>
    public AuthResult CheckUser()
    {
        WebRequest request = WebRequest.Create(GetServerURI);
        request.Method = "POST";
        request.ContentType = "application/x-www-form-urlencoded";

        string postdata = "data=" + HttpUtility.UrlEncode(SerializeAuth());
        byte[] arr = Utils.AppDefaultEncoding.GetBytes(postdata);
        request.ContentLength = arr.Length;
        request.Timeout = Convert.ToInt32(TimeUtils.GetMiliseconds(10, TimeUtils.TimeSelect.Seconds));

        Stream strToWrite = request.GetRequestStream();
        strToWrite.Write(arr, 0, arr.Length);

        WebResponse response = request.GetResponse();
        using (Stream dataFromResponse = response.GetResponseStream())
        {
            using (StreamReader reader = new StreamReader(dataFromResponse))
            {
                string readObj = reader.ReadToEnd();
                return DeserializeAuth(readObj);
            }
        }
    }

调用这个方法,我使用

_authenticationResult = authObj.CheckUser();

我也有这个属性,等等

        public ResultType AuthResult
    {
        get
        {
            if (_authenticationResult.auth == "1")
                return ResultType.Success;
            if (_authenticationResult.auth == "0")
                return ResultType.FailAccountExpired;
            if (_authenticationResult.auth == "-1")
                return ResultType.FailWrongUsernameOrPassword;
            if (_authenticationResult.auth == "-2")
                return ResultType.Banned;


            return ResultType.NoAuthDone;
        }
    }

public enum ResultType { Success, FailWrongUsernameOrPassword, FailAccountExpired, NoAuthDone, Banned }

发生的事情是 _authenticationResult 曾经为 null,并且属性 AuthResult 在尝试“null.auth”时抛出了一个 nullref。如何确保(可能在 CheckUser() 方法中)它永远不会返回 null。

当我调试应用程序时,它从未发生过。但是在生产中,当服务器超时时,该方法有时会返回 null。

谢谢,

【问题讨论】:

  • 该代码实际上不起作用-您需要始终从get AFAIK 返回一些内容...当instance1null 时,在发布模式下应该发生什么?
  • 谢谢 - 这只是一些伪代码。通常我也会在 if 块之后用第二次返回来编写这些属性。在发布模式下,如果 instance1 为 null,那么我会得到一个 nullref 异常。我相信我所做的是糟糕的编码实践,也许还有更优雅的事情要做。
  • @unholysampler 是的...我的问题不是例如当instance1null 时会发生什么,而是应该在这种情况下发生什么?一个特定的例外?
  • 调试和发布有什么区别?正常情况下,assert 在调试期间处于开启状态,在发布版本中处于关闭状态。
  • 看看你发布的代码示例,我认为这样做的优雅方法是重构你的类(也许通过将它分成两个类,或者我们可以讨论的其他方法),这样绝对没有调用者在 _authenticationResult = authObj.CheckUser(); 行之前调用 AuthResult 的方式被执行

标签: c# coding-style exception-safety


【解决方案1】:

我认为您需要了解instance1 和随后的property1 将如何被实例化,并且仅以它们不能为空的方式实例化它们。这通常通过在构造时检查参数来完成,例如:

public instance1(string property1)
{
    if (property1 == null) throw new ArgumentNullException("property1");

    this.property1 = property1;
}

如果您创建的类型不会以无效状态存在,则可以确保您的依赖代码不会因null 值而崩溃。

否则,我们需要查看您正在做什么的更完整示例,以便为您提供更具体的建议。

要考虑的另一件事是,您的类可以存在的状态是操作的必需状态,而不是操作的可选状态。也就是说,您的类操作需要哪些成员,您应该努力设计您的类,使它们始终具有所需的状态,例如:

public class Person
{
  public Person(string forename, string surname)
  {
    if (forename == null) throw new ArgumentNullException("forename");
    if (surname == null) throw new ArgumentNullException("surname");

    Forename = forename;
    Surname = surname;
  }

  public string Forename { get; private set; }
  public string Surname { get; private set; }
}

在我的示例类型中,我要求我的 ForenameSurname 值具有非空值。这是通过我的构造函数强制执行的......我的Person 类型永远不能用空值实例化(尽管,空值可能同样糟糕,所以检查IsNullOrWhiteSpace 并抛出适当的ArgumentException 是路线,但让保持简单)。

如果我要引入一个 可选 字段,我会允许它改变我的 Person 实例的状态,例如,给它一个 setter:

public class Person
{
  public Person(string forename, string surname)
  {
    if (forename == null) throw new ArgumentNullException("forename");
    if (surname == null) throw new ArgumentNullException("surname");

    Forename = forename;
    Surname = surname;
  }

  public string Forename { get; private set; }
  public string Surname { get; private set; }

  public string Initial { get; set; }
}

我的Person 类型仍然强制执行操作所需的字段,但引入了一个可选字段。然后,在执行使用这些成员的操作时,我需要考虑到这一点:

public override ToString()
{
  return Forename + (Initial == null ? String.Empty : " " + Initial) + " " + Surname;
}

(虽然这不是ToString 的最佳示例)。

【讨论】:

  • 这是一个非常好的建议,我会记住这一点以备不时之需。我还根据您的要求使用特定示例更新了初始帖子。谢谢
  • 我觉得检查一个字符串是否为空有点傻。如果将所有字符串初始化为空字符串,就不用担心空值了,总有IsNullOrEmpty。
  • @Ramhound 有时可能会出现这种情况,但可能存在nullEmpty与拥有之间存在差异的情况一个值。这将取决于他希望他的类型的消费者如何处理这些不同的值。
【解决方案2】:

你可以使用:

if ( instance1 != null && instance1.property1.Equals("bla")){
   // Your code 
 } 

【讨论】:

  • 感谢您的回复,我知道,我一直这样做。我一直在寻找一种好的编码实践,一些类似单元测试的答案或类似的东西。
  • 您的解决方案很有帮助 - 但仍然不是我想要的。但我在最初的帖子中模棱两可 - 验证对象是否为空,是处理结果,而不是原因 - 我需要处理原因本身,从一开始就永远不允许对象为空。
  • 那么你有责任正确初始化instance1。
  • @Andrei Cristof - 在您尝试使用对它的引用之前检查对象是否为空并不是您所说的“处理效果”。你甚至认识到你所做的不是“正确”的做法。
【解决方案3】:

就我个人而言,我会使用?? operator(假设property1 是一个字符串)

public string MyString
{
    get { instance1.property1 ?? "Default value"; }
}

【讨论】:

    【解决方案4】:

    人们通常以三种方式之一来处理这种情况。最糟糕的方式(在我看来)是对你看到的每一个引用都保持偏执,总是针对 null 测试它,然后在遇到 null 时执行“某事”。这种方法的问题在于,您经常深入某个调用树,因此您所做的“某事”(例如返回“”“合理”“”默认值)不仅可能违反分层,但也很可能掩盖问题而不是导致它被面对。在这些情况下,实际上最好让 NulLReferenceException 被抛出,而不是半途而废地尝试继续。

    更明智的做法是建立一个编码约定,使您的引用永远不会为空,除非在少数情况下,从上下文中可以明显看出它们可以为空。此外,只要有可能,可以使自己的类不可变或大部分不可变,以便所有不变量都可以在构造函数中完成,其余代码可以继续其生命。例如,我可能会写:

    public class Person {
      private readonly string firstName;
      private readonly string lastName;
      private readonly Nubbin optionalNubbin;
    }
    

    ...从名称中可以清楚地看出 optionalNubbin 可能为空。

    最后也是最激进的方法是编写不接受空值的代码。你可以发明 Nullable 的对偶,即:

    public struct NonNullable<T> {
      ...
    }
    

    实现可以以几种不同的方式工作(通过使用显式 Value 属性,或者可能使用运算符重载),但在任何情况下,NonNullable 的工作是永远不要让某人将其设置为 null。

    【讨论】:

      【解决方案5】:

      由于instance1.property1 永远不应为空,请查看是否有办法正确初始化它,如果有人试图将其设置为空,则抛出ArgumentNullException

      例子:

      public string Property1
      {
          set 
          {
            if(value == null)
            {
              throw new ArgumentNullException();
            } 
            instance1.property1 = value;
          }
      }
      

      【讨论】:

        【解决方案6】:

        您可以执行以下操作。

        public string MyString
        {
            get
            {
                if(instance!=null && instance1.property1.Equals("bla"))
                {
                    return "bla"; 
                }
                else 
                {
                    return String.Empty; 
                }
            }
        }
        

        这基本上会首先检查实例是否为空。

        【讨论】:

          猜你喜欢
          • 2021-06-26
          • 1970-01-01
          • 2012-11-09
          • 2021-05-14
          • 1970-01-01
          • 2014-11-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多