GRATUIT

Vos offres d'emploi informatique

Développeurs, chefs de projets, ingénieurs, informaticiens
Postez gratuitement vos offres d'emploi ici visibles par 4 000 000 de visiteurs uniques par mois

emploi.developpez.com

Les types nullables en C# 2.0

Article présentant et expliquant les types nullables apparus avec le framework 2.0

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Introduction

Les types nullables sont une réponse à la problématique d'une valeur nulle pour les types valeurs.

Cette problématique est habituelle notemment dans le cas d'applications utilisant les bases de données relationnelles. En effet, le null d'un champ d'un enregistrement ne trouve pas d'équivalent dans les propriétés de nos objets métiers. C# 2.0 apporte une réponse…

1. Présentation des classes concernées

Analysons de plus près le code du framework pour comprendre comment fonctionne les types nullables. Pour cela, utilisons l'excellent outil de Lutz Roeder's : Reflector qui va nous permettre de décompiler System.dll et d'analyser la structure Nullable<T> du framework.

Les génériques ont fait leur apparition dans la version 2 du framework .Net, ils ont permis d'écrire cette structure du framework qui encapsule un paramètre générique de type T. Le type valeur T est encapsulé dans un type référence. Des opérateurs de conversion implicites ont été surchargés. Un certain nombre d'autres opérateurs sont supportés (on verra comment…).

 
Sélectionnez
 public struct Nullable<T> where T : struct
{
    private bool hasValue;
    internal T value;
    public Nullable( T value );
    public bool HasValue { get; }
    public T Value { get; }
    public T GetValueOrDefault();
    public T GetValueOrDefault( T defaultValue );
    public override bool Equals( object other );
    public override int GetHashCode();
    public override string ToString();
    public static implicit operator Nullable<T>( T value );
    public static explicit operator T( Nullable<T> value );
}

Cette structure générique possède 2 propriétés principales

 
Sélectionnez
public bool HasValue { get; }
public T Value { get; }

HasValue est vrai pour les instances non nulles et faux pour les instances nulles.
Value nous retourne cette valeur si HasValue est vrai, dansle cas contraire une InvalidOperationException est levée.

Imaginons cette utilisation :

 
Sélectionnez
Nullable<int> nullable1;
Nullable<int> nullable2 = new Nullable<int>( 3 );
nullable1 = 2;
nullable1 = null;
bool testNullable1 = nullable1.HasValue;
Nullable<int> result = nullable1 * nullable2;

Cette surcharge d'opérateur de conversion

 
Sélectionnez
public static implicit operator Nullable<T>(T value); 

permet d'utiliser cette syntaxe :

 
Sélectionnez
b = false;

Cette surcharge d'opérateur de conversion

 
Sélectionnez
public static explicit operator T(Nullable<T> value);

permet d'utiliser cette syntaxe :

 
Sélectionnez
bool test = (bool)b;

Lors de cette affectation

 
Sélectionnez
b = null;

c'est l'instance du type référence Nullable<T> qui est nulle, elle ne référence aucun objet.

2. Le compilateur

Déjà plusieurs questions se posent à la vue de cette classe et de son utilisation…
- Comment peut on appeler « nullable1.HasValue» alors que nullable1 est null … ?
- Comment l'opération de multiplication est elle effectuée alors que l'opérateur de multiplication n'est pas surchargé dans la classe Nullable<T>… ?

C'est le compilateur qui nous apporte ces réponses. Pour s'en persuader, il suffit de décompiler notre petit exemple et de regarder quel travail le compilateur a fait sur notre code (retraduction du code IL généré en c#), voici le résultat

On comprend maintenant que le compilateur transforme notre affectation de null par un appel au constructeur par défaut de Nullable<T>. Ainsi l'appel « nullable1.HasValue » est possible et renvoie false car HasValue est initialisé à sa valeur par défaut.

Notre opération de multiplication est également modifiée, le compilateur s'occupe de tester la non nullité des 2 opérandes, et de renvoyer le résultat de la multiplication intrinsèque si ce test est vérifié. Ainsi l'opérateur * n'a pas été surchargé, c'est le travail du compilateur qui permet de supporter cet opérateur.

3. La syntaxe et les opérateurs

3.1. Syntaxe

En C# 2.0, le langage vous permet d'utiliser cette syntaxe pour les types nullables :

 
Sélectionnez
int? nullable1 = 2;

On est evidemment pas obligé de tester si HasValue est vrai avant d'accéder à Value, on peut comparer notre entier au mot clé null comme nous le ferions pour nos types références :

 
Sélectionnez
int? nullable1 = 2;
int integer2;
if (nullable1 == null)
  integer2 = default( int );
else
  integer2 = (int)i;

Vous remarquerez que la surcharge de l'opérateur vu dans notre premier exemple (public static explicit operator T( Nullable<T> value );) nous oblige ici à effectuer un cast explicit vers int. Ce code est l'occasion pour nous de rencontrer le nouveau mot clé default qui permet de retourner une valeur par défaut en fonction du type passé en paramètre (elle renverra null pour les types références et 0 pour les types numériques).

3.2. Opérateurs de comparaison

Mais heureusement un nouvel opérateur a été introduit en C# 2.0 (null coalescing operator) qui nous permet réécrire notre code précédent ainsi

 
Sélectionnez
int? nullable1 = 2;
int integer2;
integer2 = nullable1 ?? default(int);

Cet opérateur ?? nous permet donc de renvoyer « nullable.Value » s'il est non nul ou default(int) dans le cas contraire.

Analysons maintenant comment se comportent les différents opérateurs avec les types nullables. Commencons par les opérateurs de comparaisons

 
Sélectionnez
int? nullable1 = 3;
int? nullable2 = null;
bool b = nullable1 > nullable2;                b is false
b = nullable2 > nullable1;                     b is false
b = nullable2 >= nullable1;                    b is false

Quelquesoit l'opérateur de comparaison dans ces exemples, il renvoie false dès que l'un de ces opérandes est null. Considerons ce code :

 
Sélectionnez
int? nullable1 = null;
int? nullable2 = null;
bool b = nullable2 != nullable1;               b is false
b = nullable2 == nullable1;                    b is true
b = nullable2 >= nullable1;                    b is false

Les opérateurs d'égalité et de différence renvoie un résultat cohérent. L'incohérence se situe dans le fait que l'opérateur « Supérieur ou Egal » ne renvoie pas le même résultat que l'opérateur « Egal ». Il faut pour obtenir un résultat cohérent, utiliser la méthode Statique Nullable<T>.Compare

 
Sélectionnez
int result = Nullable.Compare<int>( nullable1, nullable2 );

if (result < 0)
{
    MessageBox.Show("N1 est null  et N2 non Null"
                     + " ou" + Environment.NewLine
                     + " N1 est inférieur à N2");
}
else if (result == 0)
{
    MessageBox.Show( "N1 et N2 sont Null"
                     + " ou" + Environment.NewLine
                     + " N1 est égal à N2" );
}
else if (result > 0)
{
    MessageBox.Show( "N1 est non null et N2 est Null"
                     + " ou" + Environment.NewLine
                     + " N1 est supérieur à N2" );
}

3.3. Opérateurs arithmétiques

Pour ce qui est des opérateurs arithmétiques (+,-,*,...), ils renvoient pour la plupart null lorsque l'un des opérandes est null.

3.4. Opérateurs logiques

Les types nullables, notamment dans son utilisation avec des booleens nous font entrer dans le monde trinaire : oui, non, ou peut-être… En effet, les opérateurs logiques & et | renvoient true ou false lorsque c'est possible.

nullable1&nullable2nullfalsetrue
nullnullfalsenull
falsefalsefalsefalse
truenullfalsetrue
nullable1|nullable2nullfalsetrue
nullnullnulltrue
falsenullfalsetrue
truetruetruetrue

4. Conclusion

Vous avez pu aborder à travers cet article comment les nouvelles fonctionnalités de la plateforme 2.0 permettent de résoudre ce problème de la gestion des null dans les types valeurs, et comment certains comportements sont gérés par le compilateur. Il est important de comprendre en détails ce fonctionnement car, comme nous avons pu le constater, les résultats obtenus ne sont pas toujours intuitifs…

5. Ressources

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2008 Ténière Servan. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.