Cours de C# pour l'ESSI - Cours 01 : Java vs. C#

RACINE
Index Rapide

Partie 1 : Introduction et mise en place

Introduction

La confrontation Java/C# étant inévitable, autant commencer par là. Java est arrivé en premier et a essuyé les plâtres des langages précompilés à forte intégration réseau (notamment Internet, mais à quoi bon enfoncer des portes ouvertes ?). C# a été la réponse qu'a opposé Microsoft a Sun. Est-ce que les expériences heureuses et malheureuses de Java ont été exploitées avec C# ? Le temps dont a bénéficié Microsoft et l'expérience ont-ils montré leurs résultats ? Nous allons approfondir ce sujet au long de cette "présentation" de C#.
L'autre raison pour laquelle c'est par cet affrontement que commence cette suite de cours, est qu'à l'ESSI les étudiants de première année n'étudient dans un premier temps que le Java. La présentation d’un autre langage est alors plus attractive et pertinente. La curiosité des étudiants peut ainsi être rapidement comblée.
Il me paraît aussi nécessaire d'ajouter que ce cours s’adresse dans un premier temps à des débutants et plus particulièrement des ESSIens qui ne connaissent pas C#. Ce comparatif n'est donc nullement exhaustif, et seule une petite partie des possibilités du C# est couverte(pas de threading, ...) et certaines notions sont peu développées. Pour avoir un plus large éventail des possibilités du C#, je vous engage à suivre les liens au bas de cette page.


Tout le matériel qui est présenté n'est nullement un cours officiel de l'ESSI ou de Microsoft. Il a été produit par l'auteur et en est l'exclusive propriété.
Toutes remarques pouvant aider à l'améliorer ou à corriger les diverses erreurs pouvant subsister sont les bienvenues. (email auteur )

Prérequis et matériel

J'assume que mon lecteur a les connaissances de base en POO (Programmation Orientée Objet) ainsi qu'une première expérience de la plate-forme Java. De même, certaines connaissances du monde C ou de la programmation non protégée (pointeurs != références) peuvent être nécessaires pour la bonne compréhension du développement.
Vous devez au point de vue matériel utiliser un PC sous un Windows NT/2000/XP et y avoir préalablement installé de .NET Framework SDK. Un bon éditeur de code C# serait également bienvenu. Personnellement, j'utilise Emacs si je ne fais pas d'IHM et Visual Studio .NET si j'en fais, mais cela n'engage que moi !

Nomenclature

Voici la liste des différents styles visuels utilisés dans ce cours (et les suivants) :
Si vous voyez ceci ... ...ça veut dire cela.
IDisposable Interface C'est un lien vers une page d'aide du .NET Framework SDK. Ce lien ne fonctionnera que si le .NET framework SDk est installé sur votre poste courant.
--> téléchargez le source
C'est un lien vers un fichier téléchargeable hosté sur le même site.

Partie 2 : Où les deux se ressemblent ...

Programme de base

Pour toute personne connaissant le Java, apprendre le C# est très facile. La syntaxe est effectivement très similaire et les concepts de base sont les mêmes.
Ainsi, le plus simple des Hello world en C# se code comme suit :
/** Fichier hello.cs **/

using System;

class Hello{
  public static void Main (string [] args) {
    Console.WriteLine ("salut !");
  }
}
csharpindex.com/colorCode
On retrouve ici le même point d'entrée que dans tout programme Java, le static void Main(string []).

En C#, on génère un seul fichier qui peut être soit une DLL soit un programme exécutable. (cf la compilation). C'est du code MSIL qui est interprété par une machine virtuelle CLR sur votre PC. Le code est compilé en code exécutable en mémoire par le JITer (Just In Time compiler) au moment où vous le lancez.

En Java si plusieurs classes implémentent un Main, vous pouvez choisir lequel sera le point d'entrée en nommant sa classe en paramètre de la machine virtuelle (ex : $>java MaClasse). Par contre, comment faire en C# qui fabrique un seul fichier exécutable ?
Cela se fait à la compilation avec l'option /main:xxx qui dit au compilateur que le point d'entrée sera celui de la classe Xxx. Si vous ne le précisez pas et que vous compilez plusieurs classes en un fichier (assembly), le point d'entrée sera le premier Main trouvé.
Pour des informations sur la compilation, visitez la section dédiée.

Différences/ressemblances syntaxiques

Rubrique probablement attendue. Java est un langage dans lequel le programmeur n'est pas libre pour la syntaxe. Certes il est sensible à la casse (différence Majuscule/minuscule), mais les noms de méthodes et classes suivent un certain formalisme. Ce formalisme a été "assoupli" avec C#, du fait qu'il a été développé pour des systèmes Windows non sensibles à la casse (Java étant enfant de Sun, et frère de Solaris).
Ainsi, voici un petit tableau résumant les quelques différences et ressemblances entre Java et C# :
Elément et en Java ?
Les noms de classe.
En C#, peut importe la casse pour les noms de classe. Ainsi toto et Toto sont deux noms de classe valides et différents.
Visitez la section Gestion des classes pour plus de précision.
La majuscule en début de nom de classe est obligatoire pour différencier noms de classe / méthode. Le fichier Toto.java DOIT contenir une classe publique Toto.
Les noms de méthode.
Ceux-ci peuvent commencer ou non par une majuscule. Ainsi, meth01 et Meth01 sont des noms valides et différents.
La minuscule en début de nom de méthode est facultative. Néanmoins le formalisme recommandé par Sun et utilisé par la majeur partie des programmeurs Java suppose une minuscule en début de nom de méthode.

Ces assouplissements de la syntaxes sont bienvenus et n'obligent d'avoir 1 classe publique par fichier en C# comme c'était le cas en Java. Toutefois, par soucis de lisibilité, il est toujours plus que conseillé de prendre un certain formalisme et de s'y tenir, quel qu'il soit !!

Quelques mots réservés :
C# Java
using*import*
Mainmain
Console.WriteLineSystem.out.println
Console.WriteSystem.out.print
string / StringString
System.Objectjava.lang.Object
isinstanceof
publicpublic
privateprivate
internal**protected**
sealedfinal
staticstatic
thisthis
basesuper
unObjet as UneClasse(UneClasse)unObjet cast
ToString()toString()
......
*: allez faire un tour sur la section des Assembly et namespace pour plus de détail. Les deux mots réservés ont une utilisation semblable mais cache des notions différentes.
**: allez faire un tour sur la section des types d'accès pour comprendre. Il existe en effet un accès protected en C# mais ...


Ce tableau est tiré en majeur partie de celui que l'on peut trouver dans le comparatif C# vs. Java de DotGuru. Allez le voir pour une liste plus détaillée.

Utilisation des string

L'utilisation des string en C# diffère de l'utilisation faite en Java. En effet, en Java les String sont des instances de la classe String pour lesquels certains opérateurs ont été redéfinis (=, +=).
En C#, la syntaxe C/C++ transpire plus et on a moins l'impression de manier des objets que des types valeur. Les String appartiennent au Namespace (== package) System.
Ainsi, pour copier un string :
/*** Java ***/
String s = "salut";
String s2;
s2 = s.clone();

/*** C# ***/
String s = "salut";
String s2;
s2 = string.Copy(s)
//la méthode Copy est STATIQUE!!!
Cela a de quoi faire plaisir aux nostalgiques du strcpy du C ... s'il en est. Toutefois, les String sont gérés comme des objets (à valeur) et le reste des méthodes est globalement le même qu'en Java. Ainsi on retrouve avec la même syntaxe Equals (attention à la Majuscule), Substring, ... Mais Length est un attribut en C#.

Il est à noter une très sympathique "innovation" en C#, c'est la possibilité de définir des string en omettant les codes de caractères spéciaux ('\n \t \\' et autres). Il suffit de mettre au début de la définition du string un @. Ainsi les lignes suivantes sont équivalentes :
string chemin01 = "C:\\mon répertoire\\projet2\\csharp.cs";
string chemin02 = @"C:\mon répertoire\projet2\csharp.cs";

string texte01 = "c'est pas toujours très\nlisible\t\tdes textes formatés\ncomme ça";
string texte02 = @"c'est pas toujours très
lisible
      des textes formatés
comme ça";
Le @ a un peu le rôle du <pre> du HTML.
On notera aussi que le toString() du Java est ici remplacé par ToString() (!!)

Garbage collecting

C# comme Java utilise le principe du ramasse miette (garbage collecting) pour assurer la libération propre des objets et la restauration de leur espace mémoire aux système. Ce n'est pas le programmeur qui gère la destruction des objets du programme.
Il existe dans les deux langages la possibilité de "forcer" le processus, en sachant que la manière du C# est plus directe que celle de Java. Ceci se fait au moyen de l'interface System.IDisposable et de la méthode Dispose. Pour de plus amples détails, consultez l'aide du .Net Framework SDK section IDisposable Interface.

Héritage/implementation

Java et C# proposent la même vision de la POO (Programmation Orientée Objet), soit PAS D'HERITAGE MULTIPLE, mais la possibilité d'implémenter plusieurs interfaces.
Syntaxiquement parlant, là où Java fait une distinction entre implémenter et dériver, C# n'en fait pas.
/*** JAVA ***/
class A {}
interface I {}
interface J {}

class B extends A implements I, J {}

/*** C# ***/
class A {}
interface I {}
interface J {}

class B : A, I, J {}
Notez aussi qu'en C# les constructeurs ne sont pas hérités (comme en Java) ! Ainsi une classe n'a comme constructeurs que ceux qu'elle déclare ! Pour appeler ceux de la super-classe, il faut propager avec un MonConstructeur (PaRaMeTrEs) : base (PaRaMeTrEs){}.

Assembly et namespace

Terme important en C# et dans tout le monde .NET, l'assembly désigne un ensemble de code/resources : c'est l'équivalent de la notion physique d'un package Java. Par exemple si votre projet comporte n classes qui compilées forment un exécutable, ces n classes forment un(e) assembly.
Une assembly comporte 3 parties :
  1. Le manifest qui réunit une liste des classes contenues, leurs droits nécessaires d'exécutions, les versions des assemblies avec lesquelles elles fonctionnent, leur noms, une signature (mais ceci sort du cadre de ce cours).
  2. Des classes de n'importe quel langage .NET (VB.NET, C#)
  3. Des meta-données : images, textes, videos, etc...
L'outil indispensable pour faire des assemblies par soi-même est al.exe (doc .NET Framework Assembly Linker (Al.exe)). Sachez qu'en compilant votre programme en DLL ou EXE, vous créez un assembly.


Un namespace est un "ensemble de nom", c'est à dire l'équivalent logique du package Java. Ceci permet de définir une imbrication logique de classes et d'autres namespace. Ce sont eux qui apparaissent dans les clauses using.
Vous pouvez aliaser des noms de namespace pour éviter des ambiguités ou pour raccourcir des noms de classes. Il suffit d'utiliser la syntaxe :
using alias = classe;
Exemple :
using K = System.Console;
...
K.WriteLine("..."); //équivalent à System.Console.WriteLine("...");

En C#, utiliser un namespace ne donne pas accès aux namespaces contenants ce dernier (compréhensible), ni à ceux contenus. Il n'existe pas de syntaxe using unnamespace.*;. Vous devez préciser chaque namespace utilisé.
Le programme suivant est correct, et se compile en une assembly, contenant 3 namespaces (A, A.AA et K) :
using A.AA;
using A;
using K;

namespace A{
  namespace AA{
  public class C{}    
  }
public class B{}
}

namespace K{
  public class Z{}    
}

public class test{
  public static void Main(String [] a){
    C c = new C();
    B b = new B();
    Z z = new Z();
  }
}
Notez le fait que tout les namespaces définis dans ce fichier doivent être déclarés dans des clauses using. En effet, la classe test est extérieure à ces namespaces, et ne connait pas leur contenu !

Acces

Point d'importance ici pour les programmeurs venant de Java. Le C# propose en effet des accès plus variés que le Java, mais a une autre définition des accès protected.
Mode Explication
public pareil que Java
private pareil que Java
internal accessible pour tout ce qui est dans la même assembly
protected accessible par toutes les sous-classes seulement (différent de Java car en Java les membres protected sont aussi visibles à l'intérieur d'un même fichier)
internal protected équivalent au protected de Java. Les droits sont égaux à ceux de internal + ceux de protected

Ce qui suit sont pas exactement des types d'accès pour tout membres, c'est pour cela qu'ils sont présentés à part :
Mode Explication
readonly Ne s'utilise qu'avec des attributs. Inexistant en Java, permet d'avoir des constantes objets. En effet, un attribut readonly a sa valeur définitivement fixée après le constructeur (d'instance pour les dynamiques, de classe pour les statiques).(petit exemple)

Compilation

En C#, on peut comme en Java générer du code précompilé : du MSIL (Microsoft Intermediate Language). C'est la base de l'environnement .NET. Ce MSIL est en effet le même pour tout les langages de la famille .NET et garantie l'interopérabilité entre ces derniers. Le code MSIL est généré à partir de l'exécutable (voir section MSIL et Ildasm)
On peut tout aussi bien générer une DLL (librairie dynamique) qu'un EXE. Toutefois ces derniers ne sont pas de réels exécutables Windows : ils nécessitent la machine virtuelle du .NET Framework Runtime.
Pour générer un EXE à partir de votre code C#, il suffit de taper :
csc.exe /out:chemin_de_lexe\nom_de_lexe.EXE nom_du_source.cs

Voici les options les plus courantes du C# qui ont été traitées dans ce comparatif :
Option Explication
/main:xxx Le point d'entrée du programme / DLL sera le Main de la classe Xxx
/unsafe Nécessaire si vous utilisez des pointeurs / blocs unsafe. C'est une manière de prévenir toute personne recompilant ce programme que celui-ci utilise du code non-protégé et qu'il est donc potentiellement dangereux ou instable
/out:xxx.yyy Le fichier assembly produit se nommera xxx.yyy
/doc:aaa.xml On désire produire de la doc à partir des sources. Elle sera produite dans le fichier aaa.xml
/recurse Compile les fichiers dans les sous répertoires. À utiliser avec des meta-caractères (ex : *.cs, MonProjet*03.cs, Classe0?.cs)
/? Affiche une liste des paramètres possibles pour le compilateur

Vous pourrez trouver tout ce que vous voulez savoir dans la documentation du .NET Framework SDK à la section C# Compiler Options.

Exceptions

Si vous savez utiliser des exception en Java, alors vous savez en C# c'est *p a r e i l* SAUF que le mot clé throws n'existe pas. Par conséquence, on ne déclare pas les exceptions pouvant être levées dans la signature de la méthode. Petit exemple :
class MonException : Exception {}

class ExceptionTest {

  public static void Main (String [] args){
    int i = 0;

    try{
      while (true){
    afficheA10 (i++);
      }
      Console.WriteLine ("jamais exécuté ici");
    }
    catch (MonException e){ /*rien, c'est juste fini*/ }
    //si on ne catch pas MonException, une erreur est levée à l'exec
    finally{
      Console.WriteLine ("Exception ou pas, toujours exécuté");
    }

    Console.WriteLine ("par contre exécuté QUE si l'erreur est *catchée*");
  }

  private static void afficheA10 (int n){
    if (n > 10)
      throw new MonException();
    Console.WriteLine (n);
  }
}
csharpindex.com/colorCode
--> téléchargez le source (http://www.essisharp.ht.st/cours01.html)

Partie 3 : ... et où ils diffèrent

Pointeurs/unsafe

Une des traits marquants du C# est le retour des bons vieux pointeurs. Alors que ces derniers étaient bannis aux rangs de reliquats de la programmation des gurus par les RADiens et autres POOiens, les revoilà. Avec leur lot d'obscurité, leur esotérisme pour les néophytes et leur fâcheuse tendance à nous laisser faire des bêtises. Mais soyons sérieux.
Les pointeurs ont en effet été bannis, et ceux depuis Java. La transition c'était déjà effectuée depuis 95 avec notamment ADA95 dont la notion de pointeur tenait déjà vers la référence. Dans des langages comme C (procédural) et le C++ (presqu'objet) les pointeurs ne subissent pas de vérification. Voir même, le problème est ailleur : un pointeur est une adresse mémoire un-point-c'est-tout. Bien souvent, il est géré en fait comme un entier long (32 à 64 bits). C'est là sa force et sa faiblesse : on peut lui donner n'importe quelle valeur, l'additionner (!!), faire passer des entiers pour des pointeurs, et bien d'autres amusantes possibilités qui ont causé beaucoup de nuits blanche.
Les références elle, certes contiennent un pointeur sur l'objet, mais outre elle contiennent des informations sur lui, et surtout son type. On ne peut pas le faire pointer sur n'importe quoi (type différent, adresse farfelue ou interdite) et on ne peut pas l'utiliser n'importe comment. Mais hélas, la contre partie est que certains bouts de code tenant en une poignée de ligne avec des pointeurs, perdent en lisibilité et/ou efficacité et/ou facilité d'implémentation avec des références.
Notation des pointeurs :
/* Rappel sur les pointeurs en C/C++/C# */

//un entier "a" de valeur 4
int a = 4;             

//un pointeur sur un entier. Remarquez "*" qui signifie "pointé"
int* ptr_a;     

//ptr_a pointe sur a <==> la valeur contenue dans ptr_a est l'adresse de a
//Remarquez le "&" qui signifie "adresse de"
ptr_a = &a; 

//a = a + 1. En effet on ajoute 1 à ce qui est pointé par un pointeur sur a, donc a
(*ptr_a)++;
	
Un des classiques pour dénoter la différence pointeurs/références est le swap ou échange. En Java il est difficile à réaliser par une méthode. En effet, on ne peut accéder aux valeurs des références mais seulement les consulter (en fait c'est des copies, mais c'est pareil ici). Si on veut échanger a et b, on doit taper dans la méthode le corps de l'échange, soit :
int a = 3;
int b = 4;
int c;
...

//échange de a et b
c = a;
a = b;
c = c;
...
      
Avec des pointeurs, une seul méthode écrite une fois pour toute suffit !
// ** Code C/C++ --> ne marche pas en C# ** //
void swap (int *x, int *y) {
  int c;
  c = *a;
  *a = *b;
  *b = c;
}
      
C'est tout en fait une histoire de valeur/référence. En Java, on ne passe les paramètres que par valeur que ce soit des types primitifs ou des références. Cela ne veut pas dire que les objets sont passés par valeur, mais les références sur eux oui, là est le problème.
Par contre en C# on peut les passer aussi par référence, ce qui permet d'accéder à tout les bénéfices des pointeurs, tout en programmant le reste du temps avec de références.
Et si l'on ne veut pas toucher aux pointeurs en C#, ont peut préciser de quelle manière passer les paramètres (référence ou valeur) ! Ceci ce fait au moyen de deux mots réservés :
class Toto {

  private int age;

  public Toto (int n) { age = n; }

  public static void deuxToto (out Toto un, out Toto deux){
    un = new Toto(12);
    deux = new Toto(14);
  }

  public static void echangeToto (ref Toto un, ref Toto deux) {
    Toto x;
    x = un;
    un = deux;
    deux = x;
  }

  public static void Main (string [] args){
    Toto a, b;
    deuxToto (out a, out b);
    echangeToto (ref a, ref b);
  }
}
csharpindex.com/colorCode
--> téléchargez le source (http://www.essisharp.ht.st/cours01.html)
On ne peut en Java echanger au moyen d'une méthode la valeur de deux référence. La seule parade consiste à avoir des boites refermant des références et ainsi échanger les valeurs, mais ce n'est pas universel ...
Les pointeurs ont vu leur utilisation restreinte, et il faut délibérément décider de programmer avec. Ainsi, ne peuvent apparaître des pointeurs que dans des blocs marqués unsafe et en compilant le programme avec l'option /unsafe.
De plus, les pointeurs sont réservées aux types primitifs (int, double, ...) et INTERDIT pour les objets. C'est une des leçons tirées du passé du C/C++.
Ainsi, le swap en C# est :
// ** Code C# ** //
public unsafe static void nastyPointerSwap (int *a, int *b){
   int c;
   c = *a;
   *a = *b;
   *b = c;
}
      

Javadoc vs. XML

Java est bien connu pour sa Javadoc. En incluant dans le code source des commentaires formatés pouvant incorporer des chevrons HTML ou Javadoc, on obtenait une doc très bien faite, très belle aussi en HTML. Enormément de choses étaient possible.
En C#, bien entendu ce mécanisme à été repris. Toutefois, ce n'est pas du HTML que l'on génère mais du XML (eXtensible Markup Language). L'avantage du XML étant qu'en intégrant simplement une feuille de style (type .xsl) on peut avoir un formatage du code indépendant de la structure de la doc. Microsoft fournit d'ailleur une feuille de style standard pour le C# (à télécharger ici). Mais malheureusement, cette dernière n'arrive pas à la cheville d'une Javadoc. Toutefois, ce "problème" n'en est pas un, car les données étant indépendantes de la feuille de style, certaines bien plus belles verront bientôt le jour. Ces dernières changeront completement l'apparence de votre doc et ce, sans que vous ayez à la retoucher.
Voyez ci-dessous à gauche la version "brut" (sans feuille de style) et à droite, celle avec la feuille de style par défaut.
 
Cette doc est le résultat de ce qui est produit par le code ci-dessous (l'exemple commenté des références/valeur).
/* par Alain VIZZINI / version 1.0 */

///<summary>Classe de test pour le passage par référence/valeur</summary>
///<remark>une remarque (je test aussi l'aide XML</remark>

class Toto {

  private int age;

  ///<summary>Constructeur</summary>
  ///<param name="n">(Equivalent au @param de JAVA) l'age du Toto</param>
  public Toto (int n) { }

  ///<summary>crée 2 TotoS en <i>out</i></summary>
  ///<param name="un">Toto #1</param>
  ///<param name="deux">Toto #2</param>
  public static void deuxToto (out Toto un, out Toto deux){ }

  ///<summary>Intervertis deux TotoS avec des <i>ref</i></summary>
  ///<param name="un">Toto #1</param>
  ///<param name="deux">Toto #2</param>
  public static void echangeToto (ref Toto un, ref Toto deux) { }

  ///<summary>Main</summary>
  ///<remark>Le main qui utilise les deux méthodes statiques pour crée 2 Totos et les échanger.
  ///Sert à mettre en évidence l'utilisation des ref et out en C#.</remark>

  public static void Main (string [] args){ }
}
csharpindex.com/colorCode
--> téléchargez le source (http://www.essisharp.ht.st/cours01.html)

Le code pour indiquer de la doc XML est /// (pendant du /**) et se place en début de ligne. Voici ci-dessous la description des chevrons les plus courants :
Chevron Explication
<summary> Résumé en une ligne. Simple description.
<remark> Contient une description plus longue du comportement de la classe/méthode/...
<param name="xxx"> Décrit le rôle tenu par le paramètre xxx dans la méthode.
<returns> Décrit ce que retourne la méthode.
<exceptions cref="xxx"> Indique que la méthode peut lever une exception de type xxx
<example> Donne un commentaire sur un code exemple d'utilisation
<c> ou <code> Sert à donner un exemple de code (equivalent au <code> du HTML)
<see cref="url"> Permet de faire des renvois. Sont dans une section Voir.
<see also cref="url"> Permet de faire des renvois. Sont dans une section Voir Aussi.
Vous trouverez un tutoriel complet sur les commentaires dans votre .Net Framework sous la section XML Documentation Tutorial, ainsi que la liste complète des chevrons "officiels" du C#.
Nota Bene : le code produit est du XML, pas du HTML. Par conséquent, il faut fermer ses balises, même celles non englobantes ! Le XML est sensible à la casse : <Summary> est inconnu ! De plus, il n'aime pas particulièrement le code HTML inséré au milieu du texte : ainsi si vous regardez l'example ci-dessus, ce qui est entre chevrons <i></i> est ignoré dans la méthode deuxTotos.

Boxing/unboxing

Dans les langages objet, on appelle boxing le fait de prendre un type primitif (valeur) et de le faire tenir dans un objet (référence). Par exemple, dans les implémentations générales des piles, on empile des Object en Java et en C#. De ce fait, pour faire une pile d'entier on doit faire tenir ces derniers dans des objets.
La grande différence entre le Java et le C#, c'est qu'en C# la manière de le faire est plus homogène qu'en Java. En Java on met des int dans des Integer, des double dans des Double, et on accède aux valeurs par des méthodes. Pas de cela en C#, on fait beaucoup plus simple :
  public static void primitiveToObjects (int i, double d, string s) {
    //autrement appelé Boxing / unboxing

    object o_i = i;
    object o_d = d;
    object o_s = s;
    //Java : Integer o_i = new Integer(i); ...etc...

    Console.WriteLine ("i -->" +(int)o_i + ", d -->" + (double)o_d +", s -->" + (string)o_s);
    //Java : "i -->" + o_i.intValue() + ...
  }
csharpindex.com/colorCode
Un cast en entrée, un autre en sortie et on a tout homogénéisé ! C'est quand même plus sympa comme ça.

Virtual vs. surcharge

Un peu de technique. On dit qu'une méthode m est virtuelle quand (merci polymorphisme) appelée sur une instance x d'une classe X on va descendre l'arbre de l'héritage de X pour trouver la dernière méthode m redéfinie. Pas clair ? B sous classe de A. Soit x un objet de instancié en tant que B mais utilisé en tant que A (par exemple, passé en paramètre à une méthode, on a du l'upcaster). Si sur ce x j'appelle une méthode qui est définie ET dans A ET dans B, alors c'est celle de B qui sera exécutée. (désolé mais le but est d'illustrer le virtuel pas de l'expliquer).
En Java toute les méthodes sont virtuelles. Pas besoin de préciser. En C++ par contre il fallait préciser ou donner l'option -fallvirtual au compilateur.
En C#, les fonctions ne sont virtuelles qu'à la demande. On doit le préciser avec les mot réservé virtual et override. Ca a pour avantage d'accélérer les programmes (plus de recherches à l'execution dans l'arbre des dépendances) mais cela force le programmeur venant de Java à être vigilant !! De plus, avoir toute les méthodes virtuelles était à mon sens un grand avantage chez Java.
class virtu {
  public static void Main (String []args){
    Personne p = new Personne();
    Toto t = new Toto();
    Personne tp = new Toto();

    Console.WriteLine ("p dit :" + p.TuEsQui());   //-> pers
    Console.WriteLine ("t dit :" + t.TuEsQui());   //-> toto
    Console.WriteLine ("tp dit :" + tp.TuEsQui()); //-> pers NO !!

    Console.WriteLine ("p dit :" + p.WhoRU());     //-> pers
    Console.WriteLine ("t dit :" + t.WhoRU());     //-> toto
    Console.WriteLine ("tp dit :" + tp.WhoRU());   //-> toto ok !
  }
}

class Personne {
  public Personne(){}

  public String TuEsQui(){
    return "je suis une personne";
  }
  public virtual String WhoRU(){
    return "I'm a person";
  }
}


class Toto : Personne{
  public String TuEsQui(){
    return "je suis un Toto !!";
  }
  public override String WhoRU(){
    return "I'm a Toto !!";
  }
}
csharpindex.com/colorCode
--> téléchargez le source (http://www.essisharp.ht.st/cours01.html)
Le mot clé virtual indique que la méthode est virtuelle et peut donc être surchargée. La surcharge est effective au moyen du mot clé override.
Si on crée une sous classe de Toto appelée Gars, si l'on veut redéfinir la méthode WhoRU il ne faut pas oublier le override ! Sinon, la machine virtuelle va s'arrêter au premier override trouvé, soit celui de Toto : d'où un Gars, upcasté en Toto ou Personne répondrait "I'm a Toto !!". (§ source de l'exemple où on a implementé la classe Gars).

Notez aussi qu'en C# les constructeurs ne sont pas hérités ! Ainsi une classe n'a comme constructeurs que ceux qu'elle déclare ! Pour appeler ceux de la super-classe, il faut propager avec un MonConstructeur (PaRaMeTrEs) : base (PaRaMeTrEs){}.

Constructeurs de classe

En C# il est très facile d'utiliser des constructeurs de classes (comme en Java me direz vous). Cela va très bien avec le nouveau mode d'accès Read-only qui permet de bloquer la valeur d'un objet passé le constructeur (impossible en Java, il faut tricher un peu).
Voici un exemple de l'utilisation de constructeurs de classe avec de l'initialisation de champs readonly :
class Bidon {}/* juste pour avoir un objet à créer et illustrer le readonly */

class ClassLoaderTest{
  private static int compteur;
  private static readonly int constante; //équivalent à une constante pas initialisée
  private static readonly Bidon bidon;

  /* CONSTRUCTEUR DE CLASSE : remarquez le static !!*/
  static ClassLoaderTest(){
    Console.WriteLine("Constructeur de classe (compteur <-- 1)");
    constante = 12345;
    compteur = 1;
    bidon = new Bidon();
  }

  /* UN CONSTRUCTEUR D'INSTANCE */
  public ClassLoaderTest(){
    Console.WriteLine(constante + ": Constructeur d'instances #"+ compteur++);
  }

  public static void Main (String[] args){
    Console.WriteLine ("Dans le main");

    ClassLoaderTest clt01 = new ClassLoaderTest();
    ClassLoaderTest clt02 = new ClassLoaderTest();
  }
}
csharpindex.com/colorCode
--> téléchargez le source (http://www.essisharp.ht.st/cours01.html)
Ce code produit la sortie suivante (couleur <==> commentaires) :
d:\C#Work\classloader>clld
Constructeur de classe (compteur <-- 1)  ##==> Le constructeur de classe n'est appelé qu'une fois !
Dans le main
12345: Constructeur d'instances #1       ##==> compteur n'est pas readonly donc incrémenté
12345: Constructeur d'instances #2

d:\C#Work\classloader>
Le constructeur de classe a crée une instance de la classe Bidon et l'associe a l'attribut readonly bidon. Celui ci, sortit du constructeur de classe (respectivement d'instance pour les readonly d'instances) se comportera comme ne constante et toute tentative de le modifier se soldera par une superbe exception. Pour les incrédules, voici ce que vous suscurera le compilateur :
roattr.cs(30,5): error CS0198: A static readonly field cannot be assigned to (except in a static constructor or a variable initializer)
Et pour ceux qui ont envie de pointer du doigt un faiblesse de C# et d'utiliser des ref/out, voilà pour vous :
roattr.cs(28,20): error CS0199: A static readonly field cannot be passed ref or out (except in a static constructor)

Gestion des classes

Le C# a levé la restriction de Java imposant une et une seule classe publique par fichier .java.

Ainsi le fichier machin.cs peut contenir la classe Toto et Alfred, puis être compilé en chose.exe !! Ceci est impossible en Java. Ainsi, si un tel fichier machin.cs exite, avec une compilation comme suit :
csc.exe /out:.\chose.exe machin.cs
...on obtient le fichier chose.exe.

De plus, il n'y a pas de formalisme pour le nom des classes. Ainsi, Class01 et class01 sont des noms de classe valides, pouvant être contenu dans n'importe quel fichier. Par contre, ce sont bel et bien 2 classes différentes pour le compilateur !!.

Integration Windows

Quasi-inexistante dans Java car voulu abstrait, on la trouve très forte et assez simple dans C#. Le but n'est pas ici de la traiter longuement mais d'étayer le propos par un petit exemple (ce sera certainement le but d'un autre cours).
using System;

//necessaire pour le DllImport
using System.Runtime.InteropServices;

class SndPlay{

  [DllImport("winmm.dll")]
    public static extern long PlaySound(String lpszName,
long hModule, long dwFlags);

  public static void Main (String []args) {
    String fName = @"d:\C#Work\callapi\snd.wav";

    PlaySound(fName, 0, 0);
  }
}
csharpindex.com/colorCode
--> téléchargez le source (http://www.essisharp.ht.st/cours01.html)
Ce petit exemple permet de lire un son sous Windows au moyen de l'API PlaySound contenu dans WinMM.dll [doc Playsound ICI]. (un autre étant sndPlaySound de MMSystem.dll). On remarquera au passage que l'on passe un String en paramètre de PlaySound et non un lpzstr ou autre qui ravissent les utilisateurs des autres langages (Delphi & Ada pour moi). Le marshaller s'occupe de la conversion sans qu'on ait rien à faire.
Pour bien comprendre, travaillez le ! Et puis utilisez aussi le saint win32api.hlp qui contient la liste des appels systems standards de Windows (disponible avec la majeur partie des langages) ou allez sur la MSDN Library. Et chez vous sur la doc .NET Framework section Platform Invoke Tutorial et Default Marshaling for Strings.

MSIL et Ildasm

La clef de voute du monde .NET est l'interaction entre le différents langages présents. On peut "voir" le programme (classes, méthodes, ressources, code MSIL) à partir du programme dénommé ildasm (Intermediate Language Disasembler). Lancez le et chargez un programme .NET (C# ou VBS) COMPILÉ et vous obtiendrez quelque chose comme ceci :

Pour voir le code MSIL correspondant à une méthode, double cliquez sur celle-ci. Par exemple, pour la méthode getAge:

Le code MSIL est une sorte d'assembleur pour une machine n'existant pas (virtuelle, d'où son nom) qui comprendrait ce code. On remarque les adresses sur la gauche et les mnémoniques sur la droite. C'est ce code qui est précompilé et contenu dans le fichier (EXE ou DLL) que vous avez créé et qui est interprêté par la C# Virtual Machine.
Vous pouvez evidemment produire le code MSIL de tout le programme en cliquant sur File-->Dump. Mais vous trouverez tout ce qu'il vous faut dans la documentation .NET Framework SDK section ILDasm Tutorial et MSIL Disassembler.

Liste de paramètres

En C3 vous pouvez préciser qu'une fonction prends une suite de paramètre d'un certain type mais en nombre inconnu. Ceci rappellera au programmeurs C la syntaxe des printf/scanf. Cette notion est d'ailleur implémentée dans la méthode System.Console.WriteLine qui supporte la syntaxe suivante :
Console.WriteLine("M. {0} d'age {1} a {2} enfants et {3} Euros sur son compte", 
                  "Toto",
                  43,
                  nbEnfants, 
                  argentSurCompte);
La syntaxe {x} indique qu'il faut insérer à la place le xième paramètre.
Pour faire cela vous même utilisez le mot réservé params dans la signature de votre méthode. Ainsi, la méthode suivante accepte en paramètre un String et une suite d'entiers (marche aussi très bien avec des objets) :
public void maMethode (String s, params int[] p){
  int somme = 0;
  foreach (int n in p){ 
    somme += n;
  }
  Console.WriteLine (s + " " + somme);
  Console.WriteLine ("{0} {1}", s, somme);
  //les deux lignes ci-dessus on le même résultat  
}
Notez que l'on utilise souvent avec les tableaux le foreach.

Foreach

Syntaxe ô combien pratique pour les tableau dont on ne connais pas la taille ou dont on veut parcourrir tout les éléments. Ceci permet d'obtenir une syntaxe beaucoup plus claire et lisible pour le même résultat.
  Personne [] tabPersonne;
  int [] tabInt;

  //... remplissage des tableaux

  foreach (Personne p in tabPersonne) {
    p.meth01 ();
    
    foreach (int n in tabInt) {
      p.meth02 (n);
    }
  }
De manière plus générale, foreach fonctionne avec tout objet implémentant l'interface ICollection (les tableaux dans l'exemple). Comme toujours, .Net Framwork Documentation pour en savoir plus sur ICollection.

Get & Set

Voici une simplification agréable apportée par le C#. Elle permet de ne plus avoir pour tout attribut XXX de définir de getXXX et setXXX. On tape toujours le code correspondant, mais on peut se contenter d'écrire comme ceci :
// *** JAVA ***
class bidon {
  private int xxx;

  public int getXXX () {return xxx;}
  public void setXXX (int n) { 
    if (n > 0 && n < 123)
      xxx = n;
  }
}

// *** C# ***
class bidon {
  private int xxx;

  public int XXX {
    get {
      return xxx;
    }
    set {
      //NB : la valeur à affecter à xxx est TOUJOURS value
      if (value > 0 && value < 123)
        xxx = value;
    }
  }
}
Et cela s'utilse comme ça :
bidon b = new bidon();

b.XXX = 12;      //ok
toto = b.XXX;    //ok
b.XXX = 1232245; //pas ok !
Notez que comme dans tout code, vous pouvez lever une exception de valeur interdite, hors limite ou autre ! Pensez simplement à prévenir les utilisateurs de vos classes, car une affectation qui lève une exception ça peut surprendre !

Delegate / Signatures de fonctions

En C# on retrouve la possibilité de définir des signatures de fonctions. C'est à dire, on peut préciser par exemple qu'une méthode prends en paramète une méthode de type void et prenant deux paramètres entiers. On passe en fait l'addresse de cette fonction.

Attention toutefois, ceci est différent de la réflexion où les méthodes sont des entités à part entière !

La déclaration d'une signature se fait au moyen du mot clé delegate et peut être faite dans un namespace ou dans une classe. Voici un petit exemple illustrant le propos :
//la déclaration de la delegate
public delegate void Void_IntString (int n, String s);

//une classe qui utilise le delegate.
//Vous pouvez ainsi choisir quelle type d'action associer à un même code
class Test {
  public static Void_IntString  TraiterDonnees =
                          new Void_IntString(Deleg01.afficheAge);


  public static void Main (String [] args) {
    Console.WriteLine("Un exemple d'utilisation de *delegate*");

    TraiterDonnees (21, "Alain");
  }
}

class Deleg01 {
  public static void afficheAge (int x, String y) {
    //affichage à la console
    Console.WriteLine ("{1} a {0} ans", x, y);
  }
}

class Deleg02 {
  public static void enregistreFicher (int x, String y) {
    //enregistrement des données dans un fichier
    //...
  }
}
csharpindex.com/colorCode
--> téléchargez le source (http://www.essisharp.ht.st/cours01.html)
Le code en l'etat affiche le résultat suivant :
Un exemple d'utilisation de *delegate*
Alain a 21 ans
Si vous remplacez la définition de la delegate de la classe Test par le delegate de Deleg02 pour la même action il n'y aura pas de sortie mais un écriture des données dans un fichier.
public static Void_IntString  TraiterDonnees= new Void_IntString(Deleg01.afficheAge);
deviendrait
public static Void_IntString  TraiterDonnees= new Void_IntString(Deleg02.enregistreDonnees);
Les delegate sont notamment utilisées dans la gestion des evenements.

Evenements

Un tout petit-peu de programmation evenementielle (on reverra ça dans le détail une autre fois). Pas de rappel ici des design pattern Observer/Observable ou Source/Event/Listner. Un simple petit exemple commenté pour illustrer le concept.
//la signature d'un gestionnaire d'evènement
public delegate void MonEventHandler (int ancien, int nouveau);

class Surveillee {
   public event MonEventHandler OnModif;

   private int x =0;
   public int X {
     get {return x;}
     set { 
       OnModif (x, value); //on appelle le(s) gestionnaire(s) branchés sur l'event OnModif
       x = value;
     }
   }
}


class Surveillant {
   public void surveiller (Surveillee s){
     s.OnModif += new MonEventHandler (gestionnaireOnModif);
     //on vient d'ajouter un gestionnaire d'evenement à l'objet s sur l'event OnModif
     //Remarquez que l'on peut brancher PLUSIEURS gestionnaires !
   }
   
   //la méthode ci-dessous à la signature de l'evenement !! 
   private void gestionnaireOnModif (int o, int n) {
     Console.WriteLine ("avant {0}; dorévavant {1}", o, n);
   }
}


class Test {
  public static void Main (String []args) {
    Surveillee s = new Surveillee();
    Surveillant w = new Surveillant ();

    w.surveiller (s);

    s.X = 2;
    s.X = 6;
  }
}
--> téléchargez le source (http://www.essisharp.ht.st/cours01.html)
Ce programme génère la sortie suivante :
avant 0; dorénavant 2
avant 2; dorénavant 6
Rien ne vous empèche d'avoir plusieurs gestionnaires sur un même evènement. De même, pourquoi ne pas avoir un paramètre rendu de type bolléen servant à savoir si l'on doit où non autoriser l'opération ? (un exemple de ceci est présenté dans le comparatif A Comparative Overview of C#)

Partie 4 : Le mot de la fin

Conclusion

J'espère à la suite de ce comparatif avoir réussis à présenter le C#. C'est un langage aux possibilités plus étendues que le Java car postérieur et moins multi-plateforme.
C# doit être vu dans un tout, c'est à dire intégré dans le .NET Framework, l'environnement où vont évoluer vos futurs applications Windows. C# en effet n'est qu'une brique d'un tout : .NET Framework c'est une plus grande sécurité, une intégration au web grâce à SOAP et XML, une complicité avec le système d'exploitation et d'autres langages inégalée en Java.
Microsoft par le C# a su tirer parti de l'avancée Java, et a su écouter les développeurs pour leur proposer un outil plus proche de leur besoins. Ainsi C# a une syntaxe et des possibilités plus riches que Java. La gestion de nombreux modèles est largement simplifiées et allège le travail du développeur.
Si vous souhaitez pour l'instant (au stade de développement et de déploiement de .NET) créer des applications réellement multi-plateforme, alors vous préfererez encore Java pour un temps. Si dès aujourd'hui, pour demain, vous décidez de faire des programmes proches du système, avec une intégration à la fois Web et Windows, c'est C# qu'il vous faut.

Remerciements

Merci d'abord à Laurent ELLERBACH de Microsoft France pour m'avoir fourni cette opportunité et pour son aide.
Merci aussi aux professeurs de l'ESSI : M. Michel RIVEILL qui m'a conseillé et suivit, ainsi que Mme. Anne-Marie PINNA-DERRY et Mme. Mireille BLAY-FORNARINO pour leur aide très précieuse et leur gentille relecture.
Mes remerciements vont aussi à tout ceux qui m'ont relus et apporté leur critiques et corrections : Nico, Stef, Tophe, Ben, Remaï.
Un merci tout particulier aux visiteurs de ce site qui m'ont apporté leurs remarques, et parmis eux : Raymond Galindo, Eric Klassen, Tanguy Monfort, Pascal Rapicault et Michel Thomas.

Autres comparatifs





par Alain Vizzini (vizzini@essi.fr )
pour l'ESSI & Microsoft, création 07-02-2002, dernière màj 12-01-2003


pages vues depuis le 14 oct. 2002