180 votes

Nettoyeur de façon à faire un contrôle null en c# ?

Supposons, j'ai cette 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 (Cela fonctionne pour vérifier si la Ville est null ou pas.)

Toutefois, cette vérification échoue si l'Adresse ou Contact ou de la Personne elle-même est nulle.

Actuellement, une solution à laquelle je pouvais penser était ce:

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

{ 
    // Do some stuff here..
}

Est-il une manière plus propre de faire cela?

Je n'aime vraiment pas l' null case fait qu' (something == null). Au lieu de cela, est-il une autre façon agréable de faire quelque chose comme l' something.IsNull() méthode?

236voto

Toto Points 2577

D’une manière générique, vous pouvez utiliser une arborescence d’expression et vérifier avec une méthode d’extension :

Code complet :

62voto

Kevin Points 1686

Votre code peut avoir plus de problèmes que d'avoir besoin de vérifier les références nulles. Comme il est, vous êtes probablement à la violation de la Loi de Déméter.

La Loi de Déméter est l'un de ces heuristiques, comme Ne pas se Répéter, qui vous permet d'écrire facilement maintenable code. Il raconte les programmeurs de ne pas accéder à quoi que ce soit trop loin de la portée immédiate. Par exemple, supposons que j'ai ce code:

public interface BusinessData {
  public decimal Money { get; set; }
}

public class BusinessCalculator : ICalculator {
  public BusinessData CalculateMoney() {
    // snip
  }
}

public BusinessController : IController {
  public void DoAnAction() {
    var businessDA = new BusinessCalculator().CalculateMoney();
    Console.WriteLine(businessDA.Money * 100d);
  }
}

L' DoAnAction méthode est contraire à la Loi de Déméter. Dans une fonction, il accède à une BusinessCalcualtor, BusinessData, et un decimal. Cela signifie que si l'une des modifications suivantes sont apportées, la ligne devra être refait:

  • Le type de retour d' BusinessCalculator.CalculateMoney() changements.
  • Le type d' BusinessData.Money changements

Compte tenu de la situation à eu, ces changements sont plutôt susceptibles de se produire. Si le code comme c'est écrit tout au long de la base de code, de sorte que ces changements pourraient devenir très coûteux. En outre, il signifie que votre BusinessController est couplé à la fois à l' BusinessCalculator et de la BusinessData types.

Une façon d'éviter cette situation est reécriture le code comme ceci:

public class BusinessCalculator : ICalculator {
  private BusinessData CalculateMoney() {
    // snip
  }
  public decimal CalculateCents() {
    return CalculateMoney().Money * 100d;
  }
}

public BusinessController : IController {
  public void DoAnAction() {
    Console.WriteLine(new BusinessCalculator().CalculateCents());
  }
}

Maintenant, si vous effectuez l'une des modifications ci-dessus, vous n'avez qu'à refactoriser un plus de morceau de code, l' BusinessCalculator.CalculateCents() méthode. Vous avez également éliminé BusinessController's la dépendance de l' BusinessData.


Votre code souffre d'un problème similaire:

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

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

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

class Test {
  public void Main() {
    var contact = new Person().contact;
    var address = contact.address;
    var city = address.city;
    Console.WriteLine(city);
  }
}

Si l'une des modifications suivantes sont apportées, vous aurez besoin de revoir la principale méthode que j'ai écrit ou le nul de vérifier que vous avez écrit:

  • Le type d' IPerson.contact changements
  • Le type d' IContact.address changements
  • Le type d' IAddress.city changements

Je pense que vous devriez envisager un approfondissement de la refactorisation du code de la simple réécriture null vérifier.


Cela dit, je pense qu'il y a des moments où, à la suite de la Loi de Déméter est inapproprié. (Il est, après tout, une heuristique, pas difficile et rapidement la règle, même si elle est appelée "la loi".)

En particulier, je pense que si:

  1. Vous avez quelques classes qui représentent les enregistrements stockés dans la couche de persistance de votre programme, ET
  2. Vous êtes très sûr que vous n'aurez pas besoin de revoir ces classes dans l'avenir,

ignorant la Loi de Déméter est acceptable lorsque traitent spécifiquement de ces classes. C'est parce qu'ils représentent les données que votre application fonctionne avec, afin d'atteindre à partir d'un objet de données dans un autre est un moyen d'explorer l'information dans votre programme. Dans mon exemple ci-dessus, le couplage causés par la violation de la Loi de Déméter était beaucoup plus grave: j'ai atteint tout le chemin à partir d'un contrôleur de près le haut de ma pile à travers une logique d'entreprise de la calculatrice.

Je soulève cette exception à la Loi de Déméter car avec des noms comme Person, Contact, et Address, vos classes regardent comme ils pourraient être données de la couche POCOs. Si c'est le cas et que vous êtes très sûr que vous n'aurez plus jamais besoin de revoir dans le futur, vous pourriez être en mesure de s'en sortir ignorant la Loi de Déméter dans votre situation spécifique.

48voto

Koryu Points 768

dans votre cas, vous pouvez créer une propriété pour une personne

 public bool HasCity
{
   get 
   { 
     return (this.Contact!=null && this.Contact.Address!= null && this.Contact.Address.City != null); 
   }     
}
 

mais vous devez toujours vérifier si la personne est nulle

 if (person != null && person.HasCity)
{

}
 

à votre autre question, pour les chaînes, vous pouvez également vérifier si null ou vide de cette façon:

 string s = string.Empty;
if (!string.IsNullOrEmpty(s))
{
   // string is not null and not empty
}
if (!string.IsNullOrWhiteSpace(s))
{
   // string is not null, not empty and not contains only white spaces
}
 

37voto

bigge Points 950

Une autre option (qui je pense est sous-utilisé) est l' objet null modèle. Il est difficile de dire si cela a du sens dans votre situation particulière, mais il pourrait être la peine d'essayer. En bref, vous aurez un NullContact , NullAddress de la mise en œuvre et ainsi de suite que vous utilisez à la place de null. De cette façon, vous pouvez vous débarrasser de la plupart de la valeur null vérifie, bien sûr, au frais, à certains pensaient que vous avez mis dans la conception de ces implémentations.

Comme Adam l'a souligné dans son commentaire, cela permet d'écrire

if (person.Contact.Address.City is NullCity)

dans les cas où cela est vraiment nécessaire. Bien sûr, cela n'a de sens que si la ville est vraiment un non-trivial de l'objet...

Sinon, l'objet null peut être implémentée comme un singleton (par exemple, regardez ici pour la pratique de certaines des instructions concernant l'utilisation de l'objet nul motif et ici pour des instructions relatives à des singletons en C#) qui permet d'utiliser les classiques de comparaison.

if (person.Contact.Address.City == NullCity.Instance)

Personnellement, je préfère cette approche, car je pense que c'est plus facile à lire pour les personnes qui ne connaissent pas le modèle.

26voto

Adam Houldsworth Points 38632

Mise à jour 28/04/2014: Null propagation est prévue pour le C# vNext


Il y a des problèmes plus graves que la propagation null contrôles. But pour lisible code qui peut être compris par un autre développeur, et bien que c'est verbeux - votre exemple est très bien.

Si c'est un chèque qui est fait fréquemment, envisager l'encapsulation à l'intérieur de l' Person classe comme une propriété ou un appel de méthode.


Cela dit, gratuites Func et les génériques!

Je ne ferais jamais cela, mais voici une autre solution:

class NullHelper
{
    public static bool ChainNotNull<TFirst, TSecond, TThird, TFourth>(TFirst item1, Func<TFirst, TSecond> getItem2, Func<TSecond, TThird> getItem3, Func<TThird, TFourth> getItem4)
    {
        if (item1 == null)
            return false;

        var item2 = getItem2(item1);

        if (item2 == null)
            return false;

        var item3 = getItem3(item2);

        if (item3 == null)
            return false;

        var item4 = getItem4(item3);

        if (item4 == null)
            return false;

        return true;
    }
}

Appelé:

    static void Main(string[] args)
    {
        Person person = new Person { Address = new Address { PostCode = new Postcode { Value = "" } } };

        if (NullHelper.ChainNotNull(person, p => p.Address, a => a.PostCode, p => p.Value))
        {
            Console.WriteLine("Not null");
        }
        else
        {
            Console.WriteLine("null");
        }

        Console.ReadLine();
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X