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
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 :
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 :
Cette surcharge d'opérateur de conversion
public static explicit operator T(Nullable<T> value);
|
permet d'utiliser cette syntaxe :
Lors de cette affectation
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 :
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


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.