7 décembre 2015

Article

Technologies de l'information

«To New or Not to New?» Initialisation des objets dans un environnement .NET

Nous utilisons la syntaxe de Visual Basic dans cet article, mais le comportement est exactement le même en C# et dans la plupart des langages pouvant être utilisés avec .NET.

« Parfois on fait un New, parfois on n'en fait pas. Parfois on le fait dans la déclaration d'une variable, parfois dans le code. Je suis tout mêlé. C'est quoi, l'affaire? »

C’est une question souvent posée par les programmeurs qui commencent à travailler dans l’environnement .NET, plus particulièrement s’ils n’ont qu’une faible expérience en programmation orientée objet.

Deux grands types d’objets en .NET

Pour bien répondre à cette question, il faut tout d’abord comprendre qu’il y a deux grands types d’objets en .NET : des objets valeur et des objets référence.

objets-valeur-icone1

objets-valeur-icone2

Les objets valeur comprennent les types primitifs (String, Date, Integer, Double, etc.). On y retrouve aussi les structures, généralement des objets ne possédant qu’une à quatre propriétés numériques (Size, Point, IntPtr)1. Vous en trouverez très peu dans le framework. Mais comme vous pouvez le constater, ils sont très utilisés. Vous pouvez les repérer dans les listes déroulantes offertes par IntelliSense grâce à leurs icônes respectives.

 objets-reference-icone1

Les objets référence sont pour leur part basés sur une classe (Form, GridView, Bitmap, SqlConnection), et peuvent aussi être identifiés par leur icône. Il y en a plus de 10 000 dans les différentes librairies du framework.

Il y a plusieurs différences entre les deux types d’objets :

  • Les objets valeur sont petits (Microsoft recommande 16 octets ou moins), alors que les objets références requièrent généralement plus de mémoire.
  • Une variable définie sur un objet valeur contient directement la valeur, tandis qu’un objet référence est un pointeur vers un bloc de mémoire. C’est ce bloc de mémoire qui contient alors les valeurs des propriétés de l’objet.
  • Si vous avez déjà programmé en C, vous vous souvenez peut-être des nuances entre la pile (stack) et le tas (heap), ces deux mécanismes de gestion de la mémoire. La réalité est plus nuancée, mais fondamentalement, les objets valeur sont enregistrés dans la pile, tandis que les valeurs des objets référence sont conservées dans le tas.

Ces différences ont un impact direct sur les mécanismes utilisés pour réserver de la mémoire pour un objet, ainsi que pour libérer cette mémoire une fois que l’objet n’est plus utilisé. Elles déterminent si on doit utiliser New ou pas.

En programmation objet, les objets doivent être initialisées par un constructeur. C’est une méthode (routine, procédure) qui a deux rôles : réserver de la mémoire pour la ou les valeurs associées à l’objet, et préparer ce dernier selon les besoins. En .Net, on appelle le constructeur avec New (sans la majuscule en C#).

Les objets valeur étant de petits objets, l’initialisation se fait automatiquement, pas besoin de New. Vous déclarez simplement la variable, et le framework s’occupe de réserver automatiquement un bloc de mémoire pour enregistrer sa ou ses valeurs :

Dim numéro As Integer
Dim nom As String
Dim grosseur As Size

Un objet valeur est automatiquement initialisé avec des valeurs par défaut. Dans nos lignes de code, numéro vaut 0 et nom est une chaîne de caractères de longueur zéro (""). La variable grosseur est une structure qui possède deux propriétés, et ces dernières sont des Integer. Le seul fait de la déclarer crée un objet dont les propriétés grosseur.Height et grosseur.Width sont toutes deux initialisées à zéro. Ces variables sont utilisables dès leur déclaration.

Vous pouvez ajouter un New, mais il est inutile. Une exception à cette règle est que certains objets valeur possèdent un constructeur permettant de les initialiser à d’autres valeurs que celles par défaut :

Dim grosseur As New Size (10, 50)
Dim séparateur As New String ("-", 15)

Il n’en est pas du tout de même pour les objets référence, basés sur des classes. Déclarer une variable n’est pas suffisant. Si vous tentez d’utiliser un objet référence sans avoir appelé son constructeur, par exemple

Dim formulaire As Form
formulaire.Show

vous allez habituellement avoir un simple avertissement dans l’éditeur de code, mais recevoir une erreur (NullReferenceException) quand vous allez exécuter cette portion du code. C’est que votre variable n’a pas été initialisée. Elle vaut alors Nothing (en VB) ou null (en C#)2. Avant d’utiliser une variable référence, vous devez appeler son constructeur, soit à la déclaration

Dim formulaire As New Form
Dim formulaire As Form = New Form 'Syntaxe alternative
formulaire.Show()

soit plus tard dans le code, mais avant de vous en servir :

Dim formulaire As Form
'Code
formulaire = New Form
formulaire.Show()

Utilisation des constructeurs

La deuxième approche vous permet de contrôler à quel moment un bloc de mémoire sera réservé pour votre objet. Dans le premier cas, c’est le compilateur qui détermine quand le New devra être appelé, ce qui génère parfois un peu plus de code.

La différence entre les deux types d’appels du constructeur est souvent minime sinon nulle. L’optimiseur du compilateur fait la plupart du temps un bon travail. La plupart des programmeurs appellent donc le constructeur à la déclaration, pour s’assurer qu’elle soit disponible au moment où ils s’en servent. Mais c’est quelque chose à considérer si vous avez des problèmes de performance.

Quand vous appelez New, vous appelez une méthode. Celle-ci ne fait souvent pas grand-chose. Mais dans certaines classes, le constructeur contient beaucoup de code. Dans des structures If ou Select Case complexes, il est relativement fréquent que l’objet ne soit pas utilisé dans un ou plusieurs des chemins possibles. Vous pouvez donc parfois optimiser une méthode en évitant que ce code ne soit appelé inutilement.

Certaines classes possèdent plusieurs constructeurs, requérant ou non des paramètres, vous permettant ainsi d’initialiser vos objets de différentes façons selon les besoins. D’autres vont même vous obliger à passer des paramètres. C’est le cas entre autres de la classe Bitmap. Un objet Bitmap sans image, c’est absolument inutile, et l’idée d’une image par défaut est absurde. Alors cette classe vous force donc à spécifier une image, et ce dans tous ses 12 constructeurs :

Dim monHamster As New Bitmap() 'Génère une erreur
Dim monMinou As New Bitmap("C:\Photos\Mon minou.jpg")
Dim monPitou As New Bitmap(My.Resources.Pitou)

Pour terminer, une autre question souvent posée par les participants à nos cours d’introduction aux langages .Net : « J’ai vu un cas où on déclarait un objet référence, mais sans jamais appeler New, et ça ne causait pas de problème ». Par exemple :

Dim formulaire As Form
formulaire = frmToto
formulaire.Show()

C’est que vous assignez à formulaire un objet qui a lui-même déjà été initialisé. Il y a quelque part, plus tôt dans le code, un Dim frmToto As New Form. Souvenez-vous que les variables représentant des objets référence sont des pointeurs. En faisant formulaire = frmToto, vous assignez simplement à la variable formulaire la même adresse mémoire que frmToto, où il y a déjà un objet Form initialisé. Vous avez donc deux variables qui pointent au même bloc de mémoire. Vous pouvez manipuler l’objet avec l’un ou avec l’autre. Ce n’est pas très utile si les deux variables sont dans la même méthode. Mais ça permet de passer un objet à une méthode beaucoup plus efficacement. Au lieu de transmettre toutes les valeurs de l’objet, seulement le pointeur est passé.

Voilà des concepts dans le fond bien simples à comprendre quand on sait comment ça fonctionne par en dessous!

Au plaisir de vous présenter éventuellement plein de ces « trucs » dans les formations « Programmation Visual Basic .NET » (MN204) et « Visual Basic .NET avancé : programmation objet » (MN308)

1 Les énumérations (Enum) sont aussi considérées comme des objets valeur parce qu’elles sont toujours construites avec des types primitifs. Mais comme ce sont des constantes, la notion d’initialisation discutée dans cet article ne s’applique pas à leur cas.

2 Ne pas confondre le null d’une variable non initialisée avec le Null dans une base de données.

Bouton-cours-article-environnement-NET

Jacques Bourgeois est un programmeur d'expérience. Il possède plus de 25 ans d'expérience dans le développement et la maintenance d'applications Visual Basic, Access et SQL Server, dans un environnement .NET depuis près de 15 ans. Il a notamment développé un système de gestion des entretiens préventifs pour la compagnie Basell. M. Bourgeois a formé plus de quatre mille cinq cent personnes au Québec depuis 1994 provenant d'organisations diverses. Il s'est intéressé à la programmation objet dès le début des années 90, et aux technologies .NET dès le début des années 2000.