确保对象不为null

时间:2011-11-08 14:29:14

标签: c# coding-style exception-safety

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

例如,在过去我写过代码:

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

但是如果instance1为null,则抛出异常。我想避免犯这样的错误并在将来产生这样的例外。

谢谢,


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

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

        /// <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。

谢谢,

6 个答案:

答案 0 :(得分:6)

我认为您需要了解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值具有非null值。这是通过我的构造函数强制执行的......我的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)的最好例子。

答案 1 :(得分:2)

您可以使用:

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

人们通常以三种方式之一处理这种情况。最糟糕的方式(在我看来)是对你看到的每一个引用都是偏执狂,总是对着null进行测试,然后如果遇到null就做“”某事“”。这种方法的问题在于你经常深入某些调用树,所以你所做的“某事”(如返回“”“合理”“”默认值)不仅可能是分层违规,但也可能会引发一个问题,而不是让它面临问题。在这些情况下,实际上可能更好的方法是抛出NulLReferenceException而不是尝试继续进行一半的尝试。

要做的更好的事情是建立一个编码约定,其中您的引用永远不会为null,除了在少数情况下,从上下文中可以明显看出它们可以为null。另外,在可能的情况下,可以使一个类不可变或大多数不可变,这样所有不变量都可以在构造函数中完成,其余的代码可以继续使用它的生命。例如,我可能会写:

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

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

最终和最激进的方法是编写不允许null的代码。你可以发明Nullable&lt; T&gt;的对偶,即:

public struct NonNullable<T> {
  ...
}

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

答案 4 :(得分:0)

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

示例:

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

答案 5 :(得分:0)

你可以做类似下面的事情。

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

这将首先检查实例是否为null。