Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi Eclipse MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS .NET FAQs .NET TUTORIELS .NET SOURCES .NET LIVRES .NET OUTILS .NET BLOG .NET DOTNET TV

Les types nullables en C# 2.0

Date de publication : 22 août 2008

Par Ténière Servan
 

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

               Version PDF   Version hors-ligne

Introduction
1. Présentation des classes concernées
2. Le compilateur
3. La syntaxe et les opérateurs
3.1. Syntaxe
3.2. Opérateurs de comparaison
3.3. Opérateurs arithmétiques
3.4. Opérateurs logiques
4. Conclusion
5. Ressources


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 : en 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…).
 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
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 :
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
public static implicit operator Nullable<T>(T value); 
permet d'utiliser cette syntaxe :
b = false;
Cette surcharge d'opérateur de conversion
public static explicit operator T(Nullable<T> value);
permet d'utiliser cette syntaxe :
bool test = (bool)b;
Lors de cette affectation
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 :
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 :
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
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
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 :
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
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&nullable2 null false true
null null false null
false false false false
true null false true
nullable1|nullable2 null false true
null null null true
false null false true
true true true true

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




               Version PDF   Version hors-ligne

Valid XHTML 1.1!Valid CSS!

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'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.

Responsables bénévoles de la rubrique DotNET : Jérôme Lambert et Louis-Guillaume Morand - Contacter par EMail :
Vos questions techniques : forum d'entraide DotNET - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.