![]() |
![]() |
![]() |
Cours de C# pour l'ESSI - Cours 01 : Java vs. C# |
RACINE | |
|
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 ) |
| 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. |
Hello world en C# se code comme suit :using System; class Hello{ public static void Main (string [] args) { Console.WriteLine ("salut !"); } } |
| csharpindex.com/colorCode |
static void Main(string []).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 ?/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é. | 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. |
| C# | Java |
using* | import* |
Main | main |
Console.WriteLine | System.out.println |
Console.Write | System.out.print |
string / String | String |
System.Object | java.lang.Object |
is | instanceof |
public | public |
private | private |
internal** | protected** |
sealed | final |
static | static |
this | this |
base | super |
unObjet as UneClasse | (UneClasse)unObjet cast |
ToString() | toString() |
... | ... |
protected en C# mais ...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 (=, +=)./*** 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#.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.toString() du Java est ici remplacé par ToString() (!!)System.IDisposable et de la méthode Dispose. Pour de plus amples détails, consultez l'aide du .Net Framework SDK section IDisposable Interface.
/*** 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){}.package Java. Par exemple si votre projet comporte n classes qui compilées forment un exécutable, ces n classes forment un(e) assembly.al.exe (doc .NET Framework Assembly Linker (Al.exe)). Sachez qu'en compilant votre programme en DLL ou EXE, vous créez un assembly.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.using alias = classe;using K = System.Console;
...
K.WriteLine("..."); //équivalent à System.Console.WriteLine("...");
using unnamespace.*;. Vous devez préciser chaque namespace utilisé.
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 !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 |
| 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) |
csc.exe /out:chemin_de_lexe\nom_de_lexe.EXE nom_du_source.cs| 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 |
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 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 |
/* 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. out qui indique que la méthode CRÉE l'objetref qui indique que la méthode MODIFIE l'objet (qui doit donc avoir été créé/instancié AVANT l'appel)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 |
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++.
// ** Code C# ** //
public unsafe static void nastyPointerSwap (int *a, int *b){
int c;
c = *a;
*a = *b;
*b = c;
}

///<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 |
/// (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. |
deuxTotos.Object en Java et en C#. De ce fait, pour faire une pile d'entier on doit faire tenir ces derniers dans des objets.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 :
//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 |
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.
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 |
virtual indique que la méthode est virtuelle et peut donc être surchargée. La surcharge est effective au moyen du mot clé override. 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). MonConstructeur (PaRaMeTrEs) : base (PaRaMeTrEs){}.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 |
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)
csc.exe /out:.\chose.exe machin.csClass01 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 !!.
//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 |
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.

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.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.
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.
// *** 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 !
Attention toutefois, ceci est différent de la réflexion où les méthodes sont des entités à part entière !delegate et peut être faite dans un namespace ou dans une classe. Voici un petit exemple illustrant le propos :
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 |
Un exemple d'utilisation de *delegate* Alain a 21 ansSi 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.
//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 6Rien 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#)
par Alain Vizzini (vizzini@essi.fr
)
pour l'ESSI & Microsoft, création 07-02-2002, dernière màj 12-01-2003