Cleaner way to do a null check in C#? [duplicate]

2019-01-12 13:06发布

This question already has an answer here:

Suppose, I have this interface,

interface IContact
{
    IAddress address { get; set; }
}

interface IAddress
{
    string city { get; set; }
}

class Person : IPerson
{
    public IContact contact { get; set; }
}

class test
{
    private test()
    {
        var person = new Person();
        if (person.contact.address.city != null)
        {
            //this will never work if contact is itself null?
        }
    }
}

Person.Contact.Address.City != null (This works to check if City is null or not.)

However, this check fails if Address or Contact or Person itself is null.

Currently, one solution I could think of was this:

if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)

{ 
    // Do some stuff here..
}

Is there a cleaner way of doing this?

I really don't like the null check being done as (something == null). Instead, is there another nice way to do something like the something.IsNull() method?

19条回答
Rolldiameter
2楼-- · 2019-01-12 13:43

In my opinion, the equality operator is not a safer and better way for reference equality.

It's always better to use ReferenceEquals(obj, null). This will always work. On the other hand, the equality operator (==) could be overloaded and might be checking if the values are equal instead of the references, so I will say ReferenceEquals() is a safer and better way.

class MyClass {
   static void Main() {
      object o = null;
      object p = null;
      object q = new Object();

      Console.WriteLine(Object.ReferenceEquals(o, p));
      p = q;
      Console.WriteLine(Object.ReferenceEquals(p, q));
      Console.WriteLine(Object.ReferenceEquals(o, p));
   }
}

Reference: MSDN article Object.ReferenceEquals Method.

But also here are my thoughts for null values

  • Generally, returning null values is the best idea if anyone is trying to indicate that there is no data.

  • If the object is not null, but empty, it implies that data has been returned, whereas returning null clearly indicates that nothing has been returned.

  • Also IMO, if you will return null, it will result in a null exception if you attempt to access members in the object, which can be useful for highlighting buggy code.

In C#, there are two different kinds of equality:

  • reference equality and
  • value equality.

When a type is immutable, overloading operator == to compare value equality instead of reference equality can be useful.

Overriding operator == in non-immutable types is not recommended.

Refer to the MSDN article Guidelines for Overloading Equals() and Operator == (C# Programming Guide) for more details.

查看更多
仙女界的扛把子
3楼-- · 2019-01-12 13:43

You could use reflection, to avoid forcing implementation of interfaces and extra code in every class. Simply a Helper class with static method(s). This might not be the most efficient way, be gentle with me, I'm a virgin (read, noob)..

public class Helper
{
    public static bool IsNull(object o, params string[] prop)
    {
        if (o == null)
            return true;

        var v = o;
        foreach (string s in prop)
        {
            PropertyInfo pi = v.GetType().GetProperty(s); //Set flags if not only public props
            v = (pi != null)? pi.GetValue(v, null) : null;
            if (v == null)
                return true;                                
        }

        return false;
    }
}

    //In use
    isNull = Helper.IsNull(p, "ContactPerson", "TheCity");

Offcourse if you have a typo in the propnames, the result will be wrong (most likely)..

查看更多
爷的心禁止访问
4楼-- · 2019-01-12 13:44

In what circumstances can those things be null? If nulls would indicate a bug in the code then you could use code contracts. They will pick it up if you get nulls during testing, then will go away in the production version. Something like this:

using System.Diagnostics.Contracts;

[ContractClass(typeof(IContactContract))]
interface IContact
{
    IAddress address { get; set; }
}

[ContractClassFor(typeof(IContact))]
internal abstract class IContactContract: IContact
{
    IAddress address
    {
        get
        {
            Contract.Ensures(Contract.Result<IAddress>() != null);
            return default(IAddress); // dummy return
        }
    }
}

[ContractClass(typeof(IAddressContract))]
interface IAddress
{
    string city { get; set; }
}

[ContractClassFor(typeof(IAddress))]
internal abstract class IAddressContract: IAddress
{
    string city
    {
        get
        {
            Contract.Ensures(Contract.Result<string>() != null);
            return default(string); // dummy return
        }
    }
}

class Person
{
    [ContractInvariantMethod]
    protected void ObjectInvariant()
    {
        Contract.Invariant(contact != null);
    }
    public IContact contact { get; set; }
}

class test
{
    private test()
    {
        var person = new Person();
        Contract.Assert(person != null);
        if (person.contact.address.city != null)
        {
            // If you get here, person cannot be null, person.contact cannot be null
            // person.contact.address cannot be null and person.contact.address.city     cannot be null. 
        }
    }
}

Of course, if the possible nulls are coming from somewhere else then you'll need to have already conditioned the data. And if any of the nulls are valid then you shouldn't make non-null a part of the contract, you need to test for them and handle them appropriately.

查看更多
▲ chillily
5楼-- · 2019-01-12 13:48

You can write:

public static class Extensions
    {
        public static bool IsNull(this object obj)
        {
            return obj == null;
        }
    }

and then:

string s = null;
if(s.IsNull())
{

}

Sometimes this makes sense. But personally I would avoid such things... because this is is not clear why you can call a method of the object that is actually null.

查看更多
虎瘦雄心在
6楼-- · 2019-01-12 13:48

Do you need C#, or do you only want .NET? If you can mix another .NET language, have a look at Oxygene. It's an amazing, very modern OO language that targets .NET (and also Java and Cocoa as well. Yep. All natively, it really is quite an amazing toolchain.)

Oxygene has a colon operator which does exactly what you ask. To quote from their miscellaneous language features page:

The Colon (":") Operator

In Oxygene, like in many of the languages it was influenced by, the "." operator is used to call members on a class or object, such as

var x := y.SomeProperty;

This "dereferences" the object contained in "y", calls (in this case) the property getter and returns its value. If "y" happens to be unassigned (i.e. "nil"), an exception is thrown.

The ":" operator works in much the same way, but instead of throwing an exception on an unassigned object, the result will simply be nil. For developers coming from Objective-C, this will be familiar, as that is how Objective-C method calls using the [] syntax work, too.

... (snip)

Where ":" really shines is when accessing properties in a chain, where any element might be nil. For example, the following code:

var y := MyForm:OkButton:Caption:Length;

will run without error, and return nil if any of the objects in the chain are nil — the form, the button or its caption.

查看更多
何必那么认真
7楼-- · 2019-01-12 13:50
try
{
  // do some stuff here
}
catch (NullReferenceException e)
{
}

Don't actually do this. Do the null checks, and figure out what formatting you can best live with.

查看更多
登录 后发表回答