C# versus Java

Transcription

C# versus Java
C# versus Java
Régis Clouard
1. Introduction
Cet article se propose de vous faire connaître C# à travers un ensemble de caractéristiques
communes ou différentes de Java. L'auteur de l'article original est Dare Obasanjo, traduit
par Alain Vizzini et adapté par mes soins.
2. C# n'est qu'un clone de Java
De manière explicite, les développeurs du langage C# expliquent qu'ils ont
essentiellement développé leur nouveau langage sur le langage Java, en apportant
quelques modifications et ajouts pour combler ce qu'ils considèrent comme des
déficiences. Il existe donc beaucoup de ressemblance entre les deux langages notamment
sur les concepts de base qui fait qu'il est facile d'apprendre un langage si l'on connaît
l'autre.
2.1 Nous sommes tous des objets !
Comme Java, C# possède une super classe, mère de tous les objets : System.Object. La
classe Java équivalent s'appelle java.lang.object. Les méthodes présentent dans ces
deux classes sont très similaires (p. ex: toString()) excepté wait(), notify() et les autres
méthodes liées à la synchronisation.
NOTE : En C#, une classe de type objet peut soit être écrite sous la forme "object" en
minuscule ou "Object". En fait, à la compilation ces deux types sont remplacés par
System.Object.
2.2 À propos des machines virtuelles
De la même manière que Java est compilé en byte-code et s'exécute dans un
environnement d'exécution managé (Machine Virtuelle JVM), C# est compilé en MSIL
s'exécutant dans la CRL (Common Langage Runtime). Les deux plate-formes supportent
la compilation Just In Time (JIT). Toutefois, il existe une légère différence entre les deux
plate-formes, les compilateurs Java permettent de désactiver totalement le JIT en
fonctionnant uniquement en mode interprété alors que les compilateurs .NET en général
intègre nativement le JIT. Enfin, il existe des deux cotés la possibilité de pré-compiler en
code natif le source.
2.3 Gestion de la mémoire (ramasse-miettes)
Tous les objets Java sont créés sur le tas en utilisant le mot-clé new. Il en va de même pour
la plupart des objets C# n'étant pas des types de valeurs (ValueType). La libération de la
mémoire n'est pas explicite elle intervient quand le ramasse-miettes le juge utile et
récupère les objets qui ne sont plus utilisés.
École Nationale Supérieure d'Ingénieurs
& Groupe de Laboratoires, C.N.R.S.
6, Boulevard Maréchal Juin, F-14050 CAEN cedex
http://www.greyc.fr
2 ■ C# versus Java
2.4 Synthèse des mots-clés
Il y a énormément de similitudes entre les deux langages La table ci-dessous dresse la
correspondance entre les mots-clés.
ENSICAEN - Spécialité Informatique
mot-clé
C#
abstract
as
base
bool
break
byte
case
catch
char
checked
class
const
continue
decimal
default
delegate
do
double
else
enum
event
mot-clé
Java
abstract
N/A
super
boolean
break
N/A
case
catch
char
N/A
class
const1
continue
N/A
default
N/A
do
double
else
enum
N/A
mot-clé
C#
explicit
extern
finally
fixed
float
for
foreach
get
goto
if
implicit
in
int
interface
internal
is
lock
long
namespace
new
null
mot-clé
mot-clé
Java
C#
N/A
object
native
operator
finally
out
N/A
override
float
params
for
private
for
protected
N/A
public
goto1
readonly
if
ref
N/A
return
N/A
sbyte
int
sealed
interface
set
protected
short
instanceof sizeof
synchronized stackalloc
long
static
package
string
new
struct
null
switch
mot-clé
Java
N/A
N/A
N/A
N/A
N/A
private
N/A
public
const
N/A
return
byte
final
N/A
short
N/A
N/A
static
N/A
N/A
switch
mot-clé
C#
this
throw
true
try
typeof
uint
ulong
unchecked
unsafe
ushort
using
value
virtual
void
while
:
:
N/A
N/A
N/A
volatile
mot-clé
Java
this
throw
true
try
N/A
N/A
N/A
N/A
N/A
N/A
import
N/A
N/A
void
while
extends
implements
strictfp
throws
transient
volatile
2.5 Pas de méthodes globales
Comme en Java et contrairement à C++, les méthodes en C# doivent être intégrées dans
une classe.
2.6 Les Interfaces, Oui. L'héritage multiple, Non
C#, comme Java, supporte le concept d'interface qui est assimilé à une classe abstraite
pure. De la même façon, C# et Java autorisent l'héritage multiple d'interface et simple
d'implémentation.
2.7 Les chaînes de caractères ne peuvent être modifiées
C# possède une classe System.String qui est équivalente à java.lang.String. Les deux
classes sont "immuables", c’est-à-dire qu'une fois les objets créés, il n'est pas possible de
modifier leur valeur. Ceci dans le but de protéger le mécanisme d'encapsulation ô
combien fondamental.
Code Java
String jString = "Grapes";
/* Does not modify string, instead returns lower case copy of string */
jString.toLowerCase();
Code C#
string csString = "Apple Jack";
C# versus Java ■ 3
/* Does not modify string, instead returns lower case copy of string */
csString.ToLower();
Pour créer une chaîne de caractères autorisant la modification avec la même référence, il
est conseillé d'utiliser les classes System.Text.StringBuilder pour C# et
java.lang.StringBuffer pour Java.
NOTE: En C#, la classe chaîne peut-être écrite sous la forme string ou String.
2.8 Les classes non extensibles
Les deux langages proposent des mécanismes consistant à interdire toute extension d'une
classe ; soit par souci d'optimisation, soit par souci de sécurité. Ainsi, il est interdit de les
dériver pour redéfinir des méthodes ou simplement réutiliser l'implémentation. En C#,
vous utilisez le mot-clé sealed et en Java le mot-clé final.
Code Java
final class Student {
String fname;
String lname;
int uid;
void attendClass() {}
}
sealed class Student {
string fname;
string lname;
int uid;
void attendClass() {}
}
2.9 Levée et capture d'Exceptions
Les exceptions en C# et Java partagent énormément de caractéristiques. Les deux
langages supportent l'utilisation de l'ordre try pour indiquer qu'un bloc est susceptible de
lever une exception et catch pour capturer l'exception en question. De plus, finally est
implémenté de la même manière pour spécifier qu'une région de code doit, dans tous les
cas être exécutée (exception ou pas). Cela permet de libérer des ressources proprement.
Les deux langages proposent une hiérarchie de classes d'Exceptions dérivant d'une super
classe : System.Exception pour C# et java.lang.Exception pour Java. Aussi, il est
possible de chaîner la levée ou la capture d'exception (throw dans un catch) de part et
d'autre. Cela permet, lors de la levée d'une exception, de retourner à l'appelant un type
d'exception correspondant à son contexte et à sa couche d'architecture. Par exemple, une
ligne non trouvée dans une table se traduira par une SQLException que le développeur
prendra soin de renvoyer à l'interface graphique sous la forme d'un
ObjectNotFoundException .
NOTE : Cependant, il existe une différence fondamentale entre C# et Java. Comme en
Python, le mot-clé throws n'existe pas en C# car vous n'êtes pas contraint de spécifier
dans la signature d'une méthode le fait qu'elle est susceptible de lever une exception. Il n'y
a, contrairement à Java, aucune vérification de faite à l'exécution.
Code Java
class MyException extends Exception{
public MyException(String message){ super(message); }
public MyException(String message, Exception innerException){
super(message, innerException); }
ENSICAEN - Spécialité Informatique
Code C#
4 ■ C# versus Java
}
public class ExceptionTest {
static void doStuff(){
throw new ArithmeticException();
}
public static void main(String[] args) throws Exception{
try{
try{
doStuff();
return; //won't get to execute
} catch(RuntimeException e) { /* parent of ArithmeticException
*/
throw new MyException("MyException occured", e);
/* rethrow new exception with cause specified */
}
} finally {
System.out.println("***Finally block executes even though
MyException not caught***");
}
}//main(string[])
} // ExceptionTest
ENSICAEN - Spécialité Informatique
Code C#
class MyException: Exception {
public MyException(string message): base(message){ }
public MyException(string message, Exception innerException):
base(message, innerException){ }
}
public class ExceptionTest {
static void DoStuff() {
throw new FileNotFoundException();
}
public static int Main() {
try {
try {
DoStuff();
return 0; //won't get to execute
} catch(IOException ioe) { /* parent of FileNotFoundException */
throw new MyException("MyException occured", ioe);
/* rethrow new exception with inner exception specified */
}
} finally {
Console.WriteLine("***Finally block executes even though
MyException not caught***");
}
}//Main(string[])
} // ExceptionTest
2.10 Initialisation de membres et constructeurs statiques
Les variables d'instance et les variables statiques peuvent être initialisées dès leur
déclaration, et ce, dans les deux langages. Si la variable membre est une variable
d'instance, alors l'initialisation sera effectuée juste avant l'appel du constructeur. Les
membres statiques, eux, sont initialisés dès le chargement de la classe par le Runtime.
Cela intervient en règle générale avant l'appel du constructeur. Il est aussi possible de
définir des blocs statiques qui seront exécutés au chargement. Ils sont appelés plus
C# versus Java ■ 5
communément constructeurs statiques.
Code Java
}
Code C#
using System;
class StaticInitTest {
string instMember = InitInstance();
string staMember = InitStatic();
StaticInitTest() {
Console.WriteLine("In instance constructor");
}
static StaticInitTest() {
Console.WriteLine("In static constructor");
}
static String InitInstance() {
Console.WriteLine("Initializing instance variable");
return "instance";
}
static String InitStatic() {
Console.WriteLine("Initializing static variable");
return "static";
}
static void DoStuff() {
Console.WriteLine("Invoking static DoStuff() method");
}
public static void Main(string[] args) {
Console.WriteLine("Beginning main()");
StaticInitTest.DoStuff();
StaticInitTest sti = new StaticInitTest();
ENSICAEN - Spécialité Informatique
class StaticInitTest {
String instMember = initInstance();
String staMember = initStatic();
StaticInitTest() {
System.out.println("In instance constructor");
}
static {
System.out.println("In static constructor");
}
static String initInstance() {
System.out.println("Initializing instance variable");
return "instance";
}
static String initStatic() {
System.out.println("Initializing static variable");
return "static";
}
static void doStuff() {
System.out.println("Invoking static DoStuff() method");
}
public static void main(String[] args) {
System.out.println("Beginning main()");
StaticInitTest.doStuff();
StaticInitTest sti = new StaticInitTest();
System.out.println("Completed main()");
}
6 ■ C# versus Java
Console.WriteLine("Completed main()");
}
}
Exécution des deux exemples :
In static constructor
Beginning main()
Invoking static DoStuff() method
Initializing instance variable
Initializing static variable
In instance constructor
Completed main()
2.11 Boxing
Le boxing (ou emboîtage) est la capacité du langage à traiter des objets comme des types
de valeur de manière implicite. L'équivalent en Java sont les classes Wrappers (Integer,
Float, ...) permettant d'encapsuler un type primitif dans une classe afin de le traiter comme
un objet (passage par référence, ...). L'exemple ci-dessous illustre le fonctionnement du
boxing de manière implicite et explicite.
Code Java
ENSICAEN - Spécialité Informatique
class Test {
public static void PrintString(object o) {
System.out.println("Object: " +o);
}
public static void main(string[] args) {
Point2d p = new Point2d(10, 15);
ArrayList list = new ArrayList();
int z = 100;
PrintString(p); //p boxed to object when passed to PrintString
PrintString(z); //z boxed to object when passed to PrintString
// integers and float boxed when stored in collection
// therefore no need for Java-like wrapper classes
list.Add(1);
list.Add(13.12);
list.Add(z);
for(int i =0; i < list.Count; i++)
PrintString(list[i]);
}
}
Code C#
class Test {
public static void PrintString(object o) {
Console.WriteLine("Object: " +o);
}
public static void Main(string[] args) {
Point2d p = new Point2d(10, 15);
ArrayList list = new ArrayList();
int z = 100;
PrintString(p); //p boxed to object when passed to PrintString
PrintString(z); //z boxed to object when passed to PrintString
// integers and float boxed when stored in collection
// therefore no need for Java-like wrapper classes
list.Add(1);
list.Add(13.12);
list.Add(z);
for(int i =0; i < list.Count; i++)
C# versus Java ■ 7
PrintString(list[i]);
}
}
3. La même chose mais en différent
3.1 La méthode Main
Le point d'entrée dans les deux langages est la méthode Main(). Il existe une différence
mineure concernant le nom de la méthode qui commence par un "M" majuscule et les
paramètres utilisés. Ainsi, le main de C# est surchargé et retourne un code de statut alors
que celui de Java impose une signature bien précise et retourne void.
Code C#
using System;
class A {
public static void Main(String[] args) {
Console.WriteLine("Hello World");
}
}
class B {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Il est possible d'avoir deux classes, A et B, contenant toutes deux une méthode Main(). En
Java, pour spécifier la méthode Main appelée, il suffit de spécifier le nom de la classe en
ligne de commande. Ainsi, en Java, contrairement à C#, il n'est pas nécessaire de
recompiler les classes pour changer le point d'entrée du programme.
En C#, le problème est légèrement différent car le processus de compilation génère un
exécutable pouvant contenir plusieurs Main(). C'est pourquoi, un paramètre de
compilation précisera la méthode par défaut qui sera appelée lors de l'exécution (/main).
Exemple Java
C:\CodeSample> javac A.java B.java
C:\CodeSample> java A
Hello World from class A
C:\CodeSample> java B
Hello World from class B
Exemple C#
C:\CodeSample> csc /main:A /out:example.exe A.cs B.cs
C:\CodeSample> example.exe
Hello World from class A
C:\CodeSample> csc /main:B /out:example.exe A.cs B.cs
C:\CodeSample> example.exe
Hello World from class B
ENSICAEN - Spécialité Informatique
Code Java
8 ■ C# versus Java
3.2 La syntaxe de l'héritage
C# utilise la syntaxe du C++ pour l'héritage. Le même mot-clé s'utilise pour l'héritage
d'implémentation et d'interface contrairement à Java qui spécifie extends et implements.
Code Java
class B extends A implements Comparable {
int compareTo(){}
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Code C#
class B:A, Icomparable {
int CompareTo(){}
public static void Main(String[] args) {
Console.WriteLine("Hello World");
}
}
ENSICAEN - Spécialité Informatique
Les développeurs Java pourront toujours argumenter que cela rend les sources
difficilement lisibles car le mot-clé indique si la classe mère est une interface ou pas. Pour
palier ce problème, Microsoft définit clairement que les noms d'interfaces doivent
commencer par IMyInterface (tel que Icloneable), mais cela reste un peu insuffisant d'un
point de vue génie logiciel.
3.3 Les Namespaces ou packages
Un Namespace C# est une manière de regrouper des classes par domaine et s'utilise de
manière similaire au mot-clé package en Java. Toutefois, en Java, la structure
arborescente des packages dicte la structure des sources sous-jacentes, ce qui n'est pas le
cas de C#.
Les développeurs C++ noteront que la syntaxe des namespaces en C# est très proche de
C++.
Code Java
package com.carnage4life;
public class MyClass {
int x;
void doStuff(){}
}
Code C#
namespace com.carnage4life {
public class MyClass {
int x;
void doStuff(){}
}
}
Comme en C++, la syntaxe C# autorise la multiplicité des déclarations de namespaces au
sein d'un même fichier.
Code C#
using System;
C# versus Java ■ 9
namespace Company {
public class MyClass
int x;
void doStuff(){}
}
{ /* Company.MyClass */
namespace Carnage4life {
public class MyOtherClass { /* Company.Carnage4life.MyOtherClass */
int y;
void doOtherStuff(){}
public static void Main(string[] args) {
Console.WriteLine("Hey, I can nest namespaces");
}
}// class MyOtherClass
}// namespace Carnage4life
}// namespace Company
3.4 Accessibilité à la classe
Le mot-clé internal signifie que la fonction membre ne peut être accédée que par
d'autres classes situées dans la même assembly. Associée à protected, la visibilité est
étendue aux classes dérivées.
C# access modifier
private
public
internal
protected
internal protected
Java access modifier
private
public
protected
N/A
N/A
NOTE : La visibilité par défaut d'un champ ou d'une méthode en C# est private alors
qu'en Java elle est protected.
3.5 Constructeurs, Destructeurs et Finaliseurs
La syntaxe et la sémantique des constructeurs en C# est identique à celle de Java. C#
propose aussi le concept de destructeur dans le même esprit que C++ (avec un ~) à ceci
près que la sémantique est strictement la même qu'un finaliseur (Finalize) Java. Bien que
les finaliseurs soient intégrés au langage, il est recommandé de s'en servir avec précaution
car il n'existe aucun contrôle quant à l'ordre dans lequel ils sont appelés. De plus, les
objets possédant des finaliseurs ralentissent le traitement du ramasse-miettes qui est
contraint de les mémoriser plus longtemps dans le mécanisme de libération.
NOTE : En C#, les destructeurs (finalizers) appellent automatiquement les finaliseurs de
leur classe mère contrairement à Java qui impose un appel explicite (super.finalize() ).
Code Java
public class MyClass {
ENSICAEN - Spécialité Informatique
La table ci-dessous synthétise les différents mot-clés permettant de modifier la visibilité et
l'accès à une classe dans le but de protéger l'encapsulation. Les fans de C++ déçus lorsque
Sun a modifié la portée de l'instruction protected seront heureux de noter que C# garde
la même sémantique que C++. Cela signifie donc qu'un membre "protected" ne peut être
accédé que par d'autres méthodes membres situées dans la même classe ou dans une classe
dérivée mais n'est en aucun cas visible de l'extérieur (même package ou non).
10 ■ C# versus Java
static int num_created = 0;
int i = 0;
MyClass() {
i = ++num_created;
System.out.println("Created object #" + i);
}
public void finalize() {
System.out.println("Object #" + i + " is being finalized");
}
public static void main(String[] args) {
for(int i=0; i < 10000; i++)
new MyClass();
}
}
Code C#
using System;
public class MyClass {
static int num_created = 0;
int i = 0;
ENSICAEN - Spécialité Informatique
MyClass() {
i = ++num_created;
Console.WriteLine("Created object #" + i);
}
~MyClass() {
Console.WriteLine("Object #" + i + " is being finalized");
}
public static void Main(string[] args) {
for(int i=0; i < 10000; i++)
new MyClass();
}
}
3.6 Les threads et les membres volatiles
En Java, les threads en Java sont créés soit par héritage (historique) en dérivation de la
classe java.lang.Thread et en redéfinissant la méthode run() soit par délégation (plus
sûre) en implémentant l'interface java.lang.Runnable.
En C#, la création de threads se fait par création d'une instance de la classe
System.Threading.Thread et en passant en paramètre du constructeur un délégué
System.Threading.ThreadStart qui n'est autre qu'un pointeur vers la méthode destinée
à être exécutée. Contrairement à Java qui impose comme point d'exécution d'un thread la
fonction run(), il est possible en C# de spécifier n'importe quelle fonction.
En Java, toutes les classes héritent des méthodes wait(), notify() and notifyAll()
situées dans java.lang.Object. Les méthodes équivalentes en C# sont Wait(), Pulse()
and PulseAll() se trouvant dans la classe System.Threading.Monitor.
L'example ci-dessous déroule le scénario dans lequel des threads sont lancés dans un ordre
bien défini et doivent être traités dans le même ordre. Comme l'exécution des threads est
par nature non-déterministe, ceux qui arriveront après les autres devront attendre leur tour
à l'aide de la méthode wait().
C# versus Java ■ 11
Code Java
class WorkerThread extends Thread {
private Integer idNumber;
private static int num_threads_made = 1;
private ThreadSample owner;
public WorkerThread(ThreadSample owner) {
super("Thread #" + num_threads_made);
idNumber = new Integer(num_threads_made);
num_threads_made++;
this.owner = owner;
}/* WorkerThread() */
//sleeps for a random amount of time to simulate working on a task
public void run() {
Random r = new Random(System.currentTimeMillis());
} // WorkerThread
public class ThreadSample{
private Vector threadOrderList = new Vector();
private Integer nextInLine(){
return (Integer) threadOrderList.firstElement();
}
private void removeNextInLine(){
threadOrderList.removeElementAt(0);
//all threads have shown up
if(threadOrderList.isEmpty())
System.exit(0);
}
public synchronized void workCompleted(WorkerThread worker) {
while(worker.getIDNumber().equals(nextInLine())==false) {
try {
//wait for some other thread to finish working
System.out.println (Thread.currentThread().getName()
+ " is waiting for Thread #"
+ nextInLine() + " to show up.");
wait();
} catch (InterruptedException e) {}
}//while
System.out.println("Thread #"+worker.getIDNumber()+" is home free");
//remove this ID number from the list of threads yet to be seen
removeNextInLine();
//tell the other threads to resume
notifyAll();
}
public static void main(String[] args) throws InterruptedException {
ThreadSample ts = new ThreadSample();
/* Launch 25 threads */
for(int i=1; i <= 25; i++) {
ts.threadOrderList.add(new Integer(i));
ENSICAEN - Spécialité Informatique
int timeout = r.nextInt() % 1000;
if(timeout < 0)
timeout *= -1;
try{
Thread.sleep(timeout);
} catch (InterruptedException e){
System.out.println("Thread #" + idNumber + " interrupted");
}
owner.workCompleted(this);
}/* run() */
public Integer getIDNumber() {return idNumber;}
12 ■ C# versus Java
Thread t = new WorkerThread(ts);
t.start(); //calls run and starts the thread.
}
Thread.sleep(3600000); //wait for it all to end
}/* main(String[]) */
}//ThreadSample
Code C#
public class WorkerThread {
private int idNumber;
private static int num_threads_made = 1;
private ThreadSample owner;
ENSICAEN - Spécialité Informatique
public WorkerThread(ThreadSample owner) {
idNumber = num_threads_made;
num_threads_made++;
this.owner = owner;
}/* WorkerThread() */
//sleeps for a random amount of time to simulate working on a task
public void PerformTask() {
Random r = new Random((int) DateTime.Now.Ticks);
int timeout = (int) r.Next() % 1000;
if(timeout < 0)
timeout *= -1;
//Console.WriteLine(idNumber + ":A");
try {
Thread.Sleep(timeout);
} catch (ThreadInterruptedException e) {
Console.WriteLine("Thread #" + idNumber + " interrupted");
}
//Console.WriteLine(idNumber + ":B");
owner.workCompleted(this);
}/* performTask() */
public int getIDNumber() {return idNumber;}
} // WorkerThread
public class ThreadSample {
private static Mutex m = new Mutex();
private ArrayList threadOrderList = new ArrayList();
private int NextInLine() {
return (int) threadOrderList[0];
}
private void RemoveNextInLine() {
threadOrderList.RemoveAt(0);
//all threads have shown up
if(threadOrderList.Count == 0)
Environment.Exit(0);
}
public void workCompleted(WorkerThread worker) {
try {
lock(this) {
while(worker.getIDNumber() != NextInLine()) {
try {
//wait for some other thread to finish working
Console.WriteLine ("Thread #"
+ worker.getIDNumber()
+ " is waiting for Thread #"
+ NextInLine() + " to show up.");
Monitor.Wait(this, Timeout.Infinite);
} catch (ThreadInterruptedException e) {} }//while
C# versus Java ■ 13
Console.WriteLine("Thread #"
+ worker.getIDNumber() + " is home free");
//remove this ID number from the list of threads
// yet to be seen
RemoveNextInLine();
//tell the other threads to resume
Monitor.PulseAll(this);
}
}
}
} catch(SynchronizationLockException){
Console.WriteLine("SynchronizationLockException occurred");
}
}//ThreadSample
Le mot-clé volatile permet d'interdire aux compilateurs de réaliser certaines
optimisations de code telles que le déplacement des variables du tas vers la pile. Ce genre
d'optimisation, dans la plupart des cas n'a aucun effet nocif sur le déroulement de 99% de
vos applications, mais dans certaines circonstances, cela peut mener à bien des cassestêtes difficiles à résoudre.
3.7 Synchronisation de méthodes
3.7.1 Bloc
En Java, il est possible de spécifier explicitement la synchronisation d'un bloc afin d'éviter
que deux threads modifient simultanément la même section critique. C# fournit un motclé lock qui est sémantiquement l'équivalent du synchronized en Java.
Code Java
public void withdrawAmount(int num) {
synchronized(this) {
if(num < this.amount)
this.amount -= num
}
}
Code C#
public void WithdrawAmount(int num) {
lock(this) {
if(num < this.amount)
this.amount -= num
}
}
ENSICAEN - Spécialité Informatique
}
public static void Main(String[] args) {
ThreadSample ts = new ThreadSample();
/* Launch 25 threads */
for(int i=1; i <= 25; i++) {
WorkerThread wt = new WorkerThread(ts);
ts.threadOrderList.Add(i);
Thread t = new Thread(new ThreadStart(wt.PerformTask));
t.Start();
}
Thread.Sleep(3600000); //wait for it all to end
}/* main(String[]) */
14 ■ C# versus Java
3.7.2 Méthodes
C# et Java supportent tous les deux les méthodes synchronisées. Lorsqu'une méthode
synchronisée est appelée, le premier thread prend la main sur le moniteur d'objets et
bloque l'accès aux autres threads le temps de l'exécution de la méthode. En Java, les
méthodes synchronisées sont précédées du mot-clé synchronized . En C#, il existe
plusieurs manières d'implémenter la synchronisation de méthodes : par Attributs de
Runtime (MethodImplOptions.Synchronized) ou par utilisation du mot-clé
Interlocked .
Code Java
public class BankAccount {
public synchronized void withdrawAmount(int num) {
if(num < this.amount)
this.amount - num;
}
}//BankAccount
Code C#
using System;
using System.Runtime.CompilerServices;
ENSICAEN - Spécialité Informatique
public class BankAccount {
[MethodImpl(MethodImplOptions.Synchronized)]
public void WithdrawAmount(int num) {
if(num < this.amount)
this.amount - num;
}
}//BankAccoun
3.8 L'opérateur d'identification de type (is operator)
En C#, l'opérateur is est totalement analogue au mot-clé instanceof de Java. Les deux
bouts de code suivant sont équivalents :
Code Java
if (x instanceof MyClass) { MyClass mc = (MyClass) x; }
Code C#
if (x is MyClass) { MyClass mc = (MyClass) x; }
3.9 La « Reflection »
La capacité à découvrir les méthodes et champs dans une classe et à invoquer
dynamiquement des méthodes à l'exécution est appelé la reflection. Cette caractéristique
existe aussi bien en Java qu'en C# à la différence près que la reflection s'effectue au
niveau d'une « assembly » en C# et au niveau d'une classe en Java.
Code Java
import java.lang.reflect.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
class ReflectionTest {
public static void main(String[] args) {
Class c=null;
C# versus Java ■ 15
}
}
Code C#
using
using
using
using
System;
System.Xml;
System.Reflection;
System.IO;
class ReflectionSample {
public static void Main( string[] args) {
Assembly assembly=null;
Type type=null;
XmlDocument doc=null;
try {
// Load the requested assembly and get the requested type
assembly = Assembly.LoadFrom("C:\\WINNT\\Microsoft.NET"
+"\\Framework\\»+v1.0.2914\\System.XML.dll");
type = assembly.GetType("System.Xml.XmlDocument", true);
//Unfortunately one cannot dynamically instantiate
// types via the Type object in C#.
doc= Activator.CreateInstance("System.Xml",
"System.Xml.XmlDocument").Unwrap() as XmlDocument;
if(doc != null)
Console.WriteLine(doc.GetType() + " was created at runtime");
else
Console.WriteLine("Could not dynamically create object"
+"at runtime");
} catch(FileNotFoundException) {
Console.WriteLine("Could not load Assembly: system.xml.dll");
return;
} catch(TypeLoadException) {
Console.WriteLine("Could not load Type: System.Xml.XmlDocument"
+ " from assembly: system.xml.dll");
ENSICAEN - Spécialité Informatique
Document d;
try {
c = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument().getClass();
d = (Document) c.newInstance();
System.out.println(d + " was created at runtime from its»
+ "Class object");
} catch(ParserConfigurationException pce) {
System.out.println("No document builder exists that can"
+ "satisfy the requested configuration");
} catch(InstantiationException ie) {
System.out.println("Could not create new Document instance");
} catch(IllegalAccessException iae) {
System.out.println("Cannot access default constructor of " + c);
}
// Get the methods from the class
Method[] methods = c.getMethods();
//print the method signatures and parameters
for (int i = 0; i < methods.length; i++) {
System.out.println( methods[i]);
Class[] parameters = methods[i].getParameterTypes();
for (int j = 0; j < parameters.length; j++) {
System.out.println("Parameters: "
+ parameters[j].getName());
}
}
16 ■ C# versus Java
return;
} catch(MissingMethodException) {
Console.WriteLine("Cannot find default constructor of " + type);
} catch(MemberAccessException) {
Console.WriteLine("Could not create new XmlDocument instance");
}
// Get the methods from the type
MethodInfo[] methods = type.GetMethods();
//print the method signatures and parameters
for(int i=0; i < methods.Length; i++) {
Console.WriteLine ("{0}", methods[i]);
ParameterInfo[] parameters = methods[i].GetParameters();
for(int j=0; j < parameters.Length; j++) {
Console.WriteLine (" Parameter: {0} {1}"
, parameters[j].ParameterType, parameters[j].Name);
}
}
}
}
Quelque fois, vous avez besoin d'obtenir les méta-données d'une classe spécifique
encapsulée sous la forme d'un objet. Cet objet est du type java.lang.Class en Java et
System.Type en C#. Ainsi, pour récupérer une instance de cette classe, vous utilisez
getClass() en Java et GetType() en C#.
ENSICAEN - Spécialité Informatique
Code Java
Class c = java.util.Arraylist.class; /* Append ".class" to fullname of class */
Code C#
Type t = typeof(ArrayList);
3.10 Les types primitifs
Pour chaque type primitif en Java, il existe un correspondant en C# avec le même nom,
excepté byte. Le byte en Java est signé et est ainsi proche du type sbyte de C# (et non
byte). De plus, C# possède des versions non signées pour la plupart des types : ulong,
uint, ushort et byte. La seule différence majeure provient du type decimal qui
n'effectue aucun arrondi au prix d'un surplus de mémoire et de temps de traitement.
Ci-dessous, plusieurs manières d'implémenter des valeurs réelles en C#.
Code C#
decimal dec = 100.44m; //m is the suffix used to specify decimal numbers
double dbl = 1.44e2d; //e is used to specify exponential notation while
// d is the suffix used for doubles
3.11 La déclaration de tableaux
Java possède deux façons de déclarer un tableau : la première, créée pour la compatibilité
avec C et C++ et la deuxième plus simple et plus claire. C# n'utilise que la deuxième
façon.
Code Java
int[] iArray = new int[100]; //valid, iArray is an object of type int[]
float fArray[] = new float[100]; //valid, but isn't clear that fArray
//is an object of type float[]
C# versus Java ■ 17
Code C#
int[] iArray = new int[100]; //valid, iArray is an object of type int[]
float fArray[] = new float[100]; //ERROR: Won't compile
3.12 Déclaration de constantes
Pour déclarer des constantes en Java, vous devez spécifier le mot-clé final. Les variables
"finales" peuvent être initialisées soit à la compilation, soit à l'exécution. Une fois la
variable initialisée, elle devient immuable (non modifiable). Lorsque cette variable est
une référence, la référence ne pourra pointer que sur un seul objet durant sa durée de vie.
Pour déclarer des constantes en C#, le mot-clé const est utilisé pour une initialisation à
la compilation et readonly à l'exécution. const a toujours été demandé par la
communauté de développeurs Java mais n'a jamais vraiment eu d'écho de la part de Sun.
Gageons que la prochaine version du JDK intégrera ce mot-clé au langage car, à l'heure
actuelle, le développeur est contraint de jongler avec le clonage d'objets et le masquage
par interface afin d'éviter de briser l'encapsulation.
Contrairement à C++, il n'est possible ni en Java ni en C# de spécifier une classe
immuable (équivalent du const MyClass* p).
Code Java
public class ConstantTest {
/* Compile time constants */
final int i1 = 10;
//instance variable
static final int i2 = 20; //class variable
/* run time constants */
public static final long l1 = new Date().getTime();
/* object reference as constant */
final Vector v = new Vector();
/* uninitialized final */
final float f;
ConstantTest() {
// unitialized final variable must be initialized in constructor
f = 17.21f;
}
}
Code C#
using System;
public class ConstantTest {
/* Compile time constants */
const int i1 = 10; //implicitly a static variable
// code below won't compile because of 'static' keyword
// public static const int i2 = 20;
/* run time constants */
public static readonly uint l1 = (uint) DateTime.Now.Ticks;
/* object reference as constant */
readonly Object o = new Object();
/* uninitialized readonly variable */
readonly float f;
ConstantTest() {
// unitialized readonly variable must be initialized in constructor
f = 17.21f;
}
ENSICAEN - Spécialité Informatique
import java.util.*;
18 ■ C# versus Java
}
NOTE : Le langage Java supporte aussi les paramètres de méthodes précédés du mot-clé
final. Cela permet aux classes internes déclarées dans la méthode d'accéder aux
paramètres en question. Cette fonctionnalité n'existe pas en C# .
3.13 Chaînage et appel du constructeur père
Java et C# appellent implicitement les constructeurs parents en cas de création d'un objet.
Les deux langages proposent un moyen d'appeler explicitement le constructeur père en lui
passant des paramètres spécifiques. De plus, Java et C# assurent que l'appel des
constructeurs se fait dans un ordre bien précis garantissant ainsi l'impossibilité d'accéder à
des variables non encore initialisées. Enfin les deux langages intègrent la surcharge de
constructeur et l'appel inter-constructeur afin de réutiliser du code existant. Ce procédé est
aussi appelé "chaînage de constructeurs".
Code Java
ENSICAEN - Spécialité Informatique
class MyException extends Exception {
private int _Id;
public MyException(String message) {
this(message, null, 100);
}
public MyException(String message, Exception innerException) {
this(message, innerException, 100);
}
public MyException( String message,Exception innerException, int id) {
super(message, innerException);
_Id = id;
}
}
Code C#
using System;
class MyException: Exception {
private int _Id;
public MyException(string message): this(message, null, 100){ }
public MyException(string message, Exception innerException):
this(message, innerException, 100){ }
public MyException(string message, Exception innerException, int id):
base(message, innerException) {
_Id = id;
}
}
4. Comme un air de "Déjà Vu"
4.1 Les classes imbriquées
En Java et en C# il est possible d'imbriquer des déclarations de classes. En Java, il existe
deux types de classes imbriquées : les classes non statiques connues sous le nom de classe
internes, et les classes statiques. Une classe Java interne peut-être considérée comme une
relation 1,1 avec sa classe englobante.
C# possède l'équivalent des classes statiques Java imbriquées mais n'intègre rien
d'équivalent aux classes internes de Java. Le code suivant nous montre un exemple de
C# versus Java ■ 19
classe imbriquée en C# et Java.
Code Java
public class Car {
private Engine engine;
private class Engine {
String make;
}
}
Code C#
public class Car {
private Engine engine;
private static class Engine {
string make;
}
}
NOTE : En Java, une classe imbriquée peut-être déclarée dans n'importe quels types de
bloc, ce qui n'est pas le cas de C#. La possibilité de créer des classes imbriquées à
l'intérieur de méthodes peut sembler inutile mais combinée aux classes anonymes, cela
peut conduire à concevoir de puissantes applications basées sur les Design Pattern.
La surcharge d'opérateurs autorise la redéfinition de la sémantique d'un opérateur (+,=,...)
dans un contexte particulier. Ainsi, l'égalité de deux chaînes de caractères se résume à
comparer leur valeur. Par défaut, le langage vous fournira une égalité de référence,
difficilement applicable dans ce cas précis.
Cette caractéristique du langage a toujours été source de nombreuses discussions
acharnées de la part des développeurs car son abus peut conduire à divers problèmes. Par
exemple, l'utilisation de la surcharge des opérateurs "++" et "--" dans le cas d'une
connexion ou déconnexion d'une socket réseau peut rendre un programme source
difficilement lisible. D'un autre coté, la surcharge de l'opérateur [] pour renvoyer une
copie d'un tableau membre est une alternative intéressante pour garantir l'encapsulation.
La surcharge d'opérateurs tend à être utile lorsqu'elle est utilisée de manière intuitive en
accord avec le type des classes manipulées. Ainsi, il est judicieux de s'en servir pour les
collections ([]), le calcul matriciel (+ et -), les calculs de nombres complexes ou la
comparaison de types spécifiques induisant la modification de l'égalité sémantique
(chaînes, classes monnaie, ...).
Les développeurs du langage Java ont résolument choisi de ne pas autoriser la surcharge
des opérateurs. En C#, la surcharge d'opérateurs est autorisé, mais contrairement à C++,
pas pour les opérateurs suivants ; new, ( ), ||, &&, =.
Code C#
class OverloadedNumber {
private int value;
public OverloadedNumber(int value) {
this.value = value;
}
public override string ToString() {
return value.ToString();
}
public static OverloadedNumber operator -(OverloadedNumber number) {
return new OverloadedNumber(-number.value);
ENSICAEN - Spécialité Informatique
4.2 La surcharge d'opérateurs
20 ■ C# versus Java
}
public static OverloadedNumber operator +(OverloadedNumber number1,
OverloadedNumber number2) {
return new OverloadedNumber(number1.value + number2.value);
}
public static OverloadedNumber operator ++(OverloadedNumber number) {
return new OverloadedNumber(number.value + 1);
}
}
public class OperatorOverloadingTest {
public static void Main(string[] args) {
OverloadedNumber number1 = new OverloadedNumber(12);
OverloadedNumber number2 = new OverloadedNumber(125);
Console.WriteLine("Increment: {0}", ++number1);
Console.WriteLine("Addition: {0}", number1 + number2);
}
} // OperatorOverloadingTest
4.3 L'instruction "switch"
ENSICAEN - Spécialité Informatique
Il existe deux différences majeures entre l'instruction switch de C# et Java. C# autorise
l'utilisation de chaînes de caractères et interdit le "Fall-through", c’est-à-dire la possibilité
pour un label donné d'exécuter plusieurs lignes lorsque l'instruction "break" est absente.
Cela n'est possible que lorsque le label en question ne propose aucune instruction (cf
exemple ci-dessous). Les "Fall-through" ont été explicitement désactivés en C# car ils
sont source d'erreurs (bugs difficiles à identifier).
Code C#
switch(foo) {
case "A":
Console.WriteLine("A seen");
break;
case "B":
case "C":
Console.WriteLine("B or C seen");
break;
/* ERROR: Won't compile due to fall-through at case "D" */
case "D":
Console.WriteLine("D seen");
case "E":
Console.WriteLine("E seen");
break;
}
4.4 L'instruction "foreach"
La boucle foreach est un moyen efficace et peu verbeux d'effectuer une itération sur une
collection d'objets implémentant l'interface System.Collections.Enumerable en Java
ou System.Collections.IEnumerable en C#. En Java, l instruction d'itération reprend
le mot clé for des boucles, alors qu'en C# on utilise le mot foreach.
Code Java
string[] greek_alphabet = {"alpha", "beta", "gamma", "delta", "epsilon"};
for(string str in greek_alphabet) {
System.out.println(str + " is a letter of the greek alphabet");
}
C# versus Java ■ 21
Code C#
string[] greek_alphabet = {"alpha", "beta", "gamma", "delta", "epsilon"};
foreach(string str in greek_alphabet) {
Console.WriteLine(str + " is a letter of the greek alphabet");
}
4.5 Les Assemblies
Les Assemblies en C# ont de nombreux points communs avec les fichiers archives JAR
de Java. Une Assembly est une unité de déploiement primaire contenant un ou plusieurs
Namespaces (ou packages).
Tout comme en Java, les Assemblies contiennent du code MSIL (byte-code), un ensemble
de méta-données et des ressources requises par les classes.
De plus, plusieurs traitements tels que la gestion de la sécurité, le déploiement ou le
versionning sont réalisés au niveau de l'Assembly. En Java, ces configurations s'effectuent
au niveau du fichier ZIP ou JAR ou directement sur l'ensemble des classes non
compactées. Alors que les compilateurs C# génèrent des Assemblies sous la forme de
fichiers .DLL ou .EXE, les exécutables Java sont des archives (JAR ou ZIP).
Un certain nombre de langages de programmation populaires contiennent des Frameworks
de gestion de Collections consistant à proposer une implémentation de structures de
données pré-définies (Tableaux, Listes chaînées, Dictionnaires, ...). L'avantage de ces
Frameworks est d'éviter au développeur la tâche consistant à coder tous les algorithmes de
parcours, tri et création de collections.
Dans C#, les collections sont contenues dans le namespace System.Collections. Ce
namespace contient l'ensemble des interfaces et classes représentant des types de données
abstraits tels que IList, IEnumerable, IDictionary, ICollection et CollectionBase. Ces
classes permettent de manipuler des structures de données indépendamment de leur
implémentation. De plus, le namespace System.Collections propose des
implémentations concrètes du type ArrayList, Queue, SortedList et Stack. L'avantage de
C# par rapport à Java concernant les collections réside dans la manière dont est gérée la
synchronisation. Ainsi, en C# il suffit d'utiliser un wrapper synchronisé alors qu'en Java
certaines classes sont synchronisées (ArrayList, ...) et d'autres ne le sont pas (Vector,
Hashtable, ..).
Les collections en Java contiennent des classes, interfaces et classes abstraites dans le
package java.util. S'il existe énormément de similitudes avec C#, nous pouvons
considérer que le package Java demeure légèrement plus complet que son homologue en
proposant des caractéristiques supplémentaires. Ainsi, vous trouverez en Java des classes
"ensemblistes" non ordonnées représentées sous l'appellation "set" avec les TreeSet,
HashSet, ...
4.7 Goto (plus considéré comme dangereux)
Contrairement à Java, C# contient l'instruction goto qui peut être utilisée pour réaliser un
saut direct vers un label dans un programme.
ENSICAEN - Spécialité Informatique
4.6 Collections
22 ■ C# versus Java
Code C#
using System;
using System.Net.Sockets;
class GotoSample {
public static void Main(string[] args) {
int ntries = 0;
retry:
try {
ntries++;
Console.WriteLine(ntries+"attempts to connect to network»);
//Attempt to connect to a network times out
//or some some other network connection error that
//can be recovered from
throw new SocketException();
} catch(SocketException) {
if(ntries < 5)
goto retry;
}
}/* Main(string[]) */
}
Pour gérer ce problème dans les boucles Java utilise des étiquettes.
ENSICAEN - Spécialité Informatique
Code Java
label1:
outer-loop { // e.g. for (int i=0; i<10; i++)
inner-loop {
…
break;
// breaks out the inner loop and ends up in the outer loop
…
continue; // moves back to the beginning of the inner loop
…
continue label1; // breaks out the inner loop and the outer loop
// and continues the outer loop
…
break label1; // breaks out of both loops
…
}
4.8 Les méthodes virtuelles (et finales)
Un des concepts les plus importants de la programmation orientée objet est le sacro-saint
Polymorphisme. Le Polymorphisme vous aide à interagir avec les membres d'une
hiérarchie de classes en utilisant le type le plus générique. Concrètement, cela signifie que
vous devez posséder des méthodes dans la classe de base qui seront ensuite redéfinies
dans les classes filles. Par exemple, prenons le cas de la classe MoyenDeLocomotion
contenant la fonction démarrer(), elle-même redéfinie ou implémentée dans la sous-classe
Voiture. Le client utilisant le type générique MoyenDeLocomotion appellera donc la
méthode démarrer() sans se préoccuper du type concret manipulé. La méthode
démarrer() dans cet exemple est appelée méthode virtuelle.
En Java, toutes les méthodes sont virtuelles par défaut, alors qu'en C#, comme en C++, le
développeur doit explicitement l'indiquer. C'est pourquoi, C# propose un mot-clé n'ayant
pas d'équivalent en Java : virtual. Ici encore, cette caractéristique a toujours été source
de moult discussions entre pour et contre. Néanmoins, il faut noter que la nature virtuelle
des méthodes constitue un frein aux optimisations de la machine virtuelle devant prévoir
l'extensibilité de la classe en question. Une autre raison dans le choix de C# pourrait
s'expliquer par le fait que les méthodes virtuelles peuvent être accidentellement redéfinies
C# versus Java ■ 23
par le développeur dans une sous-classe. Dans ce cas, l'appel à la méthode de la classe
mère sera masqué par la méthode fille et pourrait engendrer des résultats inexplicables.
Enfin, Java et C# proposent respectivement les mots-clé final et sealed permettant
d'interdire toute redéfinition d'une méthode ou dérivation d'une classe.
Les exemples ci-dessous vous illustrent l'utilisation des méthodes virtuelles en C# et Java.
Code Java
class Parent {
public void DoStuff(String str) {
System.out.println("In Parent.DoStuff: " + str);
}
}
class Child extends Parent {
public void DoStuff(int n) {
System.out.println("In Child.DoStuff: " + n);
}
public void DoStuff(String str) {
System.out.println("In Child.DoStuff: " + str);
}
}
}//VirtualTest
OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Child.DoStuff: Second Test
Code C#
using System;
public class Parent {
public void DoStuff(string str) {
Console.WriteLine("In Parent.DoStuff: " + str);
}
}
public class Child: Parent {
public void DoStuff(int n) {
Console.WriteLine("In Child.DoStuff: " + n);
}
public void DoStuff(string str) {
Console.WriteLine("In Child.DoStuff: " + str);
}
}
public class VirtualTest {
public static void Main(string[] args) {
Child ch = new Child();
ch.DoStuff(100);
ch.DoStuff("Test");
((Parent) ch).DoStuff("Second Test");
ENSICAEN - Spécialité Informatique
public class VirtualTest {
public static void main(String[] args) {
Child ch = new Child();
ch.DoStuff(100);
ch.DoStuff("Test");
((Parent) ch).DoStuff("Second Test");
}
24 ■ C# versus Java
}
}//VirtualTest
OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Parent.DoStuff: Second Test
Le comportement de l'exemple précédent en Java peut être reproduit en C# en marquant la
méthode DoStuff(string) dans la classe mère virtual et en spécifiant dans la méthode
de la classe fille DoStuff(string) le mot-clé override.
Code C#
using System;
public class Parent {
public virtual void DoStuff(string str) {
Console.WriteLine("In Parent.DoStuff: " + str);
}
}
ENSICAEN - Spécialité Informatique
public class Child: Parent {
public void DoStuff(int n) {
Console.WriteLine("In Child.DoStuff: " + n);
}
public override void DoStuff(string str) {
Console.WriteLine("In Child.DoStuff: " + str);
}
}
public class VirtualTest {
public static void Main(string[] args) {
Child ch = new Child();
ch.DoStuff(100);
ch.DoStuff("Test");
((Parent) ch).DoStuff("Second Test");
}
}//VirtualTest
OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Child.DoStuff: Second Test
L'exemple précédent peut encore être modifié pour reproduire le résultat initial en
modifiant la signature de la méthode DoStuff(string) dans la classe fille ainsi :
public new void DoStuff(string str)
indique que même si la méthode DoStuff de la classe mère est virtuelle, la classe fille peut
implémenter "sa" propre version de la méthode sans redéfinir la précédente (elle peut être
appelée ainsi : parent.DoStuff() )
4.9 Fichiers Entrée/Sortie
Les deux langages supportent les classes d'entrées/sorties. Les exemples ci-dessous
réalisent une copie du contenu d'un fichier appelé "input.txt" vers un autre fichier
"output.txt".
Code Java
import java.io.*;
C# versus Java ■ 25
public class FileIO {
public static void main(String[] args) throws IOException {
File inputFile = new File("input.txt");
File outputFile = new File("output.txt");
FileReader in = new FileReader(inputFile);
BufferedReader br = new BufferedReader(in);
FileWriter out = new FileWriter(outputFile);
BufferedWriter bw = new BufferedWriter(out);
String str;
while((str = br.readLine())!= null)
bw.write(str);
br.close();
bw.close();
}
}//FileIOTest
Code C#
using System;
using System.IO;
}//FileIOTest
4.10 La Sérialisation
La persistance d'objets, connue aussi sous le terme Serialisation est la caractéristique
consistant à lire et à écrire l'état d'un objet (la valeur de ses attributs) dans un flux
quelconque (fichier texte, socket réseau, ...). La Sérialisation est utile lorsqu'il s'agit de
sauvegarder l'état d'un graphe d'objets entre plusieurs invocations de méthodes. Vous êtes
en mesure de reconstruire entièrement l'état de votre application suite à un crash ou tout
autre arrêt inopiné. De plus, la Sérialisation vous permet de faire transiter des objets à
travers le réseau dans le but de transférer des objets entre plusieurs applications. Ce
mécanisme est fortement utilisé dans les architectures distribuées avec RMI,CORBA ou
encore .NET Remoting.
Les objets sérialisables en C# sont marqués par l'attribut de Runtime [Serializable].
L'attribut [Serializable] indique que les membres de la classe ne peuvent pas être
sérialisés. Ces membres sont en règle générale des variables temporaires ou des champs
n'ayant aucune signification lorsqu'elles sont persistantes (Thread, ...). C# fournit deux
formats de sérialisation : XML et un format binaire propriétaire.
Vous avez la possibilité d'implémenter votre propre format de sérialisation par
implémentation de l'interface ISerializable.
En Java, les objets sérialisables sont ceux qui implémentent l'interface Serializable
alors que les champs non sérialisables sont marqués avec le mot-clé transient. Par
défaut, Java supporte la sérialisation d'objets sous un format binaire propriétaire mais
ENSICAEN - Spécialité Informatique
public class FileIOTest {
public static void Main(string[] args) {
FileStream inputFile = new FileStream("input.txt", FileMode.Open);
FileStream outputFile = new FileStream("output.txt", FileMode.Open);
StreamReader sr
= new StreamReader(inputFile);
StreamWriter sw
= new StreamWriter(outputFile);
String str;
while((str = sr.ReadLine())!= null)
sw.Write(str);
sr.Close();
sw.Close();
}
26 ■ C# versus Java
fournit, comme en C#, un mécanisme permettant soit d'étendre le format initial
(readObject et writeObject), soit de ré-écrire entièrement le processus de sérialisation
(interface Externalizable)
private void readObject(java.io.ObjectInputStream stream) throws IOException,
ClassNotFoundException;
private void writeObject(java.io.ObjectOutputStream stream) throws IOException
Code Java
import java.io.*;
ENSICAEN - Spécialité Informatique
class SerializeTest implements Serializable {
transient int x;
private int y;
public SerializeTest(int a, int b) {
x = a;
y = b;
}
public String toString(){
return "{x=" + x + ", y=" + y + "}";
}
public static void main(String[] args) throws Exception{
SerializeTest st = new SerializeTest(66, 61);
System.out.println("Before Write := " + st);
System.out.println("\n Writing SerializeTest object to disk");
FileOutputStream out = new FileOutputStream("serialized.txt");
ObjectOutputStream so = new ObjectOutputStream(out);
so.writeObject(st);
so.flush();
System.out.println("\n Reading SerializeTest object from disk\n");
FileInputStream in
= new FileInputStream("serialized.txt");
ObjectInputStream si
= new ObjectInputStream(in);
SerializeTest fromdisk = (SerializeTest)si.readObject();
/* x will be 0 because it won't be read from disk since transient */
System.out.println("After Read := " + fromdisk);
}
}
Code C#
using
using
using
using
using
using
System;
System.IO;
System.Reflection;
System.Runtime.Serialization;
System.Runtime.Serialization.Formatters.Binary;
System.Runtime.Serialization.Formatters.Soap;
[Serializable]
class SerializeTest {
[NonSerialized]
private int x;
private int y;
public SerializeTest(int a, int b) {
x = a;
y = b;
}
public override String ToString() {
return "{x=" + x + ", y=" + y + "}";
}
C# versus Java ■ 27
}
4.11 Génération de la documentation à partir du code source
Java et C# proposent tous les deux un mécanisme de génération de documentation à partir
des sources. Il suffit de respecter une certaine forme d'écriture des commentaires, lesquels
seront traités (parsing) par un outil afin de produire une documentation sous la forme de
fichiers texte. Le langage Java a été le premier à intégrer cette technique et C# s'en est
inspiré.
Javadoc est l'outil utilisé pour extraire à partir des sources l'ensemble de la
documentation. Il génère du code HTML à partir des commentaires du programme. Le
meilleur exemple d'utilisation de Javadoc est la documentation du JDK.
Les différentes méta-données pouvant être spécifiées sont les suivantes :
► La description de la méthode
► Les exceptions levées par la méthode.
► Les paramètres acceptés
► Les paramètres de retour.
► Les méthodes associées et attributs membres.
ENSICAEN - Spécialité Informatique
public static void Main(String[] args) {
SerializeTest st = new SerializeTest(66, 61);
Console.WriteLine("Before Binary Write := " + st);
Console.WriteLine("\n Writing SerializeTest object to disk");
Stream output = File.Create("serialized.bin");
BinaryFormatter bwrite = new BinaryFormatter();
bwrite.Serialize(output, st);
output.Close();
Console.WriteLine("\n Reading SerializeTest object from disk\n");
Stream input = File.OpenRead("serialized.bin");
BinaryFormatter bread = new BinaryFormatter();
SerializeTest fromdisk = (SerializeTest)bread.Deserialize(input);
input.Close();
/* x will be 0 because it won't be read from disk since nonserialized */
Console.WriteLine("After Binary Read := " + fromdisk);
st = new SerializeTest(19, 99);
Console.WriteLine("\n\nBefore SOAP(XML) Serialization := " + st);
Console.WriteLine("\n Writing SerializeTest object to disk");
output = File.Create("serialized.xml");
SoapFormatter swrite = new SoapFormatter();
swrite.Serialize(output, st);
output.Close();
Console.WriteLine("\n Reading SerializeTest object from disk\n");
input = File.OpenRead("serialized.xml");
SoapFormatter sread = new SoapFormatter();
fromdisk = (SerializeTest)sread.Deserialize(input);
input.Close();
/* x will be 0 because it won't be read from disk since nonserialized */
Console.WriteLine("After SOAP(XML) Serialization := " + fromdisk);
Console.WriteLine("\n\nPrinting XML Representation of Object");
XmlDocument doc = new XmlDocument();
doc.Load("serialized.xml");
Console.WriteLine(doc.OuterXml);
}
28 ■ C# versus Java
► Une indication sur le caractère obsolète de la méthode (deprecated).
► La version de l'API dans laquelle elle a été créée.
L'information sur le caractère obsolète d'une méthode est aussi utilisée par le compilateur
pour générer des messages d'avertissement ou d'erreurs.
Javadoc fournit automatiquement les informations suivantes :
► Les API héritées
► La liste des classe dérivées
► Un classement hiérarchique, alphabétique de vos classes
► Un index contenant la liste des méthodes et variables utilisées
Code Java
/**
ENSICAEN - Spécialité Informatique
* Calculates the square of a number.
* @param num the number to calculate.
* @return the square of the number.
* @exception NumberTooBigException this occurs if the square of the
number
* is too big to be stored in an int.
*/
public static int square(int num) throws NumberTooBigException{}
C# utilise XML comme format pour la génération de documentation. Les fichiers
contiennent les méta-données spécifiées par l'utilisateur ainsi que des informations
supplémentaires générées automatiquement. Il existe très peu de différences entre les
types de méta-données C# et Java à quelques exceptions près (@author, @version, ou
@deprecated, ...). L'avantage indéniable de la génération de documentation en XML
réside dans l'indépendance du format de stockage par rapport à la présentation. Ainsi, il
suffit d'associer une feuille de style XSLT au document pour pouvoir générer du HTML,
des fichiers texte ou encore du PDF. Microsoft fournit par défaut une feuille de style
(HTML).
Code C#
///<summary>Calculates the square of a number.</summary>
///<param name="num">The number to calculate.</param>
///<return>The square of the number. </return>
///<exception>NumberTooBigException - this occurs if the square of the
number is too big to be stored in an int. </exception>
public static int square(int num){}
4.12 Plusieurs classes dans un même fichier
Il est possible dans les deux langages d'inclure plusieurs déclarations de classes dans le
même fichier avec plusieurs différences notables. Ainsi, en Java il ne peut y avoir qu'une
seule classe de visibilité publique par fichier et elle doit posséder absolument le même
nom que le fichier en question. Ces contraintes n'existent pas en C#, vous pouvez définir
plusieurs classes publiques et leur donner un nom quelconque sans rapport avec le fichier.
4.13 L'import de bibliothèques
L'utilisation d'une bibliothèque dans une application est une opération à deux étapes. La
première étape consiste à référencer la bibliothèque dans le programme source en utilisant
les directives using pour C# et import pour Java. La deuxième étape consiste à indiquer
C# versus Java ■ 29
au compilateur le chemin des dites bibliothèques. En Java, cette opération se fait à l'aide
de la variable d'environnement CLASSPATH ou d'une option du compilateur : -classpath .
En C#, seule l'option du compilateur /r est utilisée.
4.14 Les événements
Le modèle de programmation événementielle consiste à mettre en place un mécanisme
permettant de notifier des objets abonnés à un événement particulier. Ce modèle est
souvent associé au design pattern observer/observable et s'utilise en général dans la
conception d'interfaces graphiques (IHM). Java et C# proposent tous les deux des
mécanismes supportant les évènements mais avec une implémentation totalement
différente. Ainsi, en Java, java.util.EventObject est la classe mère de tous les
événements et possède un ensemble de méthodes permettant d'identifier la source de
l’événement. Ensuite, un abonné appelé Listener, identifié à l'aide d'interfaces existantes,
devra implémenter une méthode dite de callback. Vous remarquerez que Java traite la
gestion d'évènements avec un typage fort. Vous trouverez donc dans les APIs
MouseListener, ActionListener, KeyListener, .. contenant toutes des méthodes de callback
en relation avec la sémantique de l'évènement (keyPressed, actionPerformed, ...)
En C#, le mot-clé event spécifie un type particulier de délégué : ceux chargés de traiter
des événements. Pendant la phase de compilation, le compilateur surcharge les opérateurs
+= et -= afin de simplifier l'opération d'enregistrement ou de suppression d'un callback.
En Java, il vous faut passer par des méthodes prévues à cet effet : addActionListener()
et RemoveActionListener().
L'exemple ci-dessous vous illustre une classe générant 20 nombres aléatoires et produisant
un événement à chaque fois qu'un nombre pair est généré.
Code Java
import java.util.*;
class EvenNumberEvent extends EventObject {
public int number;
public EvenNumberEvent(Object source, int number) {
super(source);
this.number = number;
}
}
interface EvenNumberSeenListener {
void evenNumberSeen(EvenNumberEvent ene);
ENSICAEN - Spécialité Informatique
C# utilise les délégués pour fournir un mécanisme explicite permettant de gérer
l'abonnement/notification. Les délégués sont concrètement des pointeurs de fonction et un
événement est généralement un type dérivé de System.EventArgs. Ainsi, lorsqu'un
événement apparaît, il suffit de passer au gestionnaire abonné la source et l’événement en
question de la manière suivante : new YourEventArgs(inits). L'émetteur possède une
méthode protected précédée en général de "On" (e.g OnClick, OnClose, OnInit, etc...)
invoquée lorsqu'un évènement spécifique apparaît. Cette méthode redirige ensuite l'appel
au délégué spécifique en passant l'objet EventArgs préalablement initialisé. Les méthodes
"OnXX" sont protected pour permettre à une classe dérivée d'y faire appel. L'abonné est
une méthode (contrairement à Java où c'est une classe) acceptant les mêmes arguments et
paramètres de retour que le délégué. En règle générale, les délégués possèdent deux
arguments : Object pour l'objet source et EventArgs pour le contenu de l’événement, et
void comme paramètre de retour.
30 ■ C# versus Java
}
ENSICAEN - Spécialité Informatique
class Publisher {
Vector subscribers = new Vector();
private void OnEvenNumberSeen(int num) {
for(int i=0, size = subscribers.size(); i < size; i++)
((EvenNumberSeenListener)subscribers.get(i)).evenNumberSeen(new
EvenNumberEvent(this, num));
}
public void addEvenNumberEventListener(EvenNumberSeenListener ensl) {
subscribers.add(ensl);
}
public void removeEvenNumberEventListener(EvenNumberSeenListener ensl)
{
subscribers.remove(ensl);
}
//generates 20 random numbers between 1 and 20 then causes and
//event to occur if the current number is even.
public void RunNumbers() {
Random r = new Random(System.currentTimeMillis());
for(int i=0; i < 20; i++) {
int current = (int) r.nextInt() % 20;
System.out.println("Current number is:" + current);
//check if number is even and if so initiate callback call
if((current % 2) == 0)
OnEvenNumberSeen(current);
}//for
}
}//Publisher
public class EventTest implements EvenNumberSeenListener {
//callback function that will be called when even number is seen
public void evenNumberSeen(EvenNumberEvent e)
System.out.println("\t\tEven Number Seen:" + ((EvenNumberEvent)e).number);
}
public static void main(String[] args) {
EventTest et = new EventTest();
Publisher pub = new Publisher();
//register the callback/subscriber
pub.addEvenNumberEventListener(et);
pub.RunNumbers();
//unregister the callback/subscriber
pub.removeEvenNumberEventListener(et);
}
}
Code C#
using System;
class EvenNumberEvent: EventArgs {
/* HACK: fields are typically private, but making this internal so it
* can be accessed from other classes. In practice should use
properties.
*/
internal int number;
public EvenNumberEvent(int number):base() {
this.number = number;
}
}
class Publisher {
C# versus Java ■ 31
public delegate void EvenNumberSeenHandler(object sender, EventArgs e);
public event EvenNumberSeenHandler EvenNumHandler;
protected void OnEvenNumberSeen(int num) {
if(EvenNumHandler!= null)
EvenNumHandler(this, new EvenNumberEvent(num));
}
//generates 20 random numbers between 1 and 20 then causes and
//event to occur if the current number is even.
public void RunNumbers() {
Random r = new Random((int) DateTime.Now.Ticks);
for(int i=0; i < 20; i++) {
int current = (int) r.Next(20);
Console.WriteLine("Current number is:" + current);
//check if number is even and if so initiate callback call
if((current % 2) == 0)
OnEvenNumberSeen(current);
}//for
}
}//Publisher
4.15 Interopérabilité inter-language
L'interopérabilité inter-langages est la capacité à intégrer plusieurs applications écrites
dans différents langages. Il existe plusieurs manières de ré-utiliser du code C/C++ en
Java. Tout d'abord, JNI (Java Native Interface) a été créé dans ce but. Les classes natives
(non Java) appelées doivent respecter un certain modèle de développement utilisant des
types bien spécifiques propres à l'API JNI. Pour créer une telle application il vous faut
respecter les étapes suivantes :
1/ Créer un programme Java contenant une méthode identifiée par le mot-clé native
2/ Charger la bibliothèque correspondante au code C/C++ à l'aide de la méthode
loadLibrary()
3/ Compiler la classe contenant la déclaration des méthodes natives
4/ Utiliser le générateur de stubs javah avec l'option -jni afin de générer les ".h"
nécessaires aux invocations
5/ Coder l'implémentation de la méthode native (généralement du C ou du C++).
6/ Compiler les fichiers ".h" et les fichiers source dans une bibliothèque partagée (.dll,.so,
ENSICAEN - Spécialité Informatique
public class EventTest {
//callback function that will be called when even number is seen
public static void EventHandler(object sender, EventArgs e) {
Console.WriteLine("\t\tEven Number Seen:"
+ ((EvenNumberEvent)e).number);
}
public static void Main(string[] args) {
Publisher pub = new Publisher();
//register the callback/subscriber
pub.EvenNumHandler += new
Publisher.EvenNumberSeenHandler(EventHandler);
pub.RunNumbers();
//unregister the callback/subscriber
pub.EvenNumHandler -= new
Publisher.EvenNumberSeenHandler(EventHandler);
}
}
32 ■ C# versus Java
...)
Cette méthode assure que le code source Java est totalement indépendant de la
bibliothèque native utilisée. Ainsi, tout portage vers d'autres plate-formes s'en trouve
simplifié.
Java propose aussi la possibilité d’interagir avec un système distribué via un ORB (Object
Request Broker) tel que Corba ou Rmi. Le middleware est responsable d'assurer la partie
communication et marshalling des paramètres entre un objet Java et un objet C++. Sans
chercher aussi loin, les WebServices avec Soap sont aussi une solution à ce style de
problème. Bien entendu, ces techniques supposent de mettre en place une mécanique
assez lourde et parfois inutile lorsque les deux applications n'ont aucune vocation à être
distribuées.
Avec une interopérabilité binaire, les objets sont en mesure d'interagir entre eux de
manière plus intégré. Ainsi, il est possible d'hériter de l'interface ou de la classe d'un objet
écrit dans un autre langage ou de partager les même types. Les compilateurs et autres
débogueurs se basent uniquement sur le byte-code généré qui est le même pour tous les
langages. C'est l'approche de .NET.
ENSICAEN - Spécialité Informatique
La CLR se charge de l'environnement d'exécution et la CLS assure l'homogénéité du code
binaire exécuté (MSIL) à travers un ensemble de types unifiés (CTS). Il existe à l'heure
actuelle plus d'une dizaine de compilateurs proposant la génération de code binaire .NET.
Un autre aspect de l'interopérabilité inter-langage supporté par C# est l'interaction avec les
objets COM. Le Framework propose des mécanismes permettant d'appeler simplement un
objet COM à partir de code C#. Il suffit d'utiliser des outils fournis par le Framework et
permettant de générer des classes .NET Proxy prenant en charge le passage entre les deux
mondes (tlbimp et tlbexp).
Les programmes C# peuvent aussi appeler une fonction située dans n'importe quelle DLL
en utilisant le mot-clé extern associé à l'attribut DllImport. L'avantage de cette
technique est que la méthode de la DLL en question n'a pas besoin d'être nécessairement
écrite pour C# et l'inconvénient réside dans le caractère non portable de cette technique
puisque l'invocation est fortement liée au format binaire des DLL Win32 spécifiques à
Microsoft. Aussi, c'est un autre moyen simple de ré-utiliser du code "non managé" sans
passer par des wrappers.
Indéniablement, C# propose plus d'alternatives que Java concernant l'intégration et
l'interopérabilité avec les applications développées dans d'autres langages. D'un autre coté,
Java insiste sur le caractère portable de son architecture et rejette totalement toute
instruction liée à une plate-forme spécifique. Mais, il apparaît de plus en plus que le
cloisonnement de la plate-forme Java autour d'un seul langage est une stratégie de moins
en moins payante car la communauté de développeurs ne semble pas encore prête à
accepter le monopole d'un et seul langage. Techniquement, il est tout à fait envisageable
d'intégrer la compilation de code C#, VB ou Eiffel en byte-code Java, mais Sun le veut-il
vraiment ?
5. Maintenant, des choses totalement différentes
5.1 La libération déterministe d'objets (Dispose Design Pattern)
Pour fournir un contrôle total sur la libération des ressources utilisées par les objets, C#
propose l'interface System.IDisposable contenant la méthode Dispose() pouvant être
appelée directement par un utilisateur lorsqu'il décide de libérer une ressource.
C# versus Java ■ 33
L'utilisateur peut ainsi spécifier de manière explicite qu'il désire libérer des objets qui ne
sont plus utilisés. Cette technique est à différencier de la Finalisation proposée par Java et
C# qui n'assure pas une libération "déterministe" des objets. Concrètement, le fait
d'appeler la méthode Dispose() invoque la méthode GC.SuppressFinalisation(this)
qui supprime l'objet de la file de finalisation. Ainsi, l'objet peut-être libéré sans avoir à
attendre que le Thread de finalisation se lance.
Il existe une autre manière plus simple de libérer explicitement des objets en utilisant
l'instruction using tel que décrit dans l'exemple suivant.
Si une classe est "disposable", il est préférable de faire usage de la méthode Dispose()
une et une seule fois. Ainsi, de multiples appels successifs à Dispose() n'auront aucun
effet. Vous pouvez implémenter un mécanisme basé sur des flags permettant de contrôler
le fait que la classe ait été déjà libérée. L'exemple ci-dessous nous illustre un programme
dans lequel une classe référence un fichier ouvert jusqu'à ce que la méthode Dispose()
soit appelée.
Code C#
using System;
using System.IO;
ENSICAEN - Spécialité Informatique
public class MyClass : IDisposable {
bool disposed = false;
FileStream f;
StreamWriter sw;
private String name;
private int numShowNameCalls = 0;
MyClass(string name) {
f = new FileStream("logfile.txt", FileMode.OpenOrCreate);
sw = new StreamWriter(f);
this.name = name;
Console.WriteLine("Created " + name);
}
~MyClass() {
Dispose(false);
}
public void Dispose() {
if(!disposed) {
Dispose(true);
}
}
private void Dispose(bool disposing) {
lock(this) {
/* prevents multiple threads from disposing simultaneously */
/* disposing variable is used to indicate if this method was
called from a
* Dispose() call or during finalization. Since finalization
order is not
* deterministic, the StreamWriter may be finalized before this
object in
* which case, calling Close() on it would be inappropriate so
we try to
* avoid that.
*/
if(disposing) {
Console.WriteLine("Finalizing " + name);
sw.Close(); /* close file since object is done with */
GC.SuppressFinalize(this);
disposed = true;
34 ■ C# versus Java
}
}
}
public string ShowName() {
if(disposed)
throw new ObjectDisposedException("MyClass");
numShowNameCalls++;
sw.Write("ShowName() Call #" + numShowNameCalls.ToString() + "\n");
return "I am " + name;
}
public static void Main(string[] args) {
using (MyClass mc = new MyClass("A MyClass Object")) {
for(int i = 0; i < 10; i++) {
Console.WriteLine(mc.ShowName());
} //for
} /* runtime calls Dispose on MyClass object once "using" code block
is exited, even if exception thrown */
}//Main
}
Ce mécanisme est très proche des destructeurs de C++ et permet de ne pas se soucier de la
gestion des allocations mémoire.
ENSICAEN - Spécialité Informatique
La libération déterministe d'objet est une avancée significative dans une prise en charge
plus fine des Ramasse miettes.
NOTE : Attention, l'appel à la méthode Dispose() ne demande pas directement au
Ramasse miettes de libérer l'objet mais accélère ce processus en outre-passant l'étape de
finalisation. .
5.2 Les Délégués
Les Délégués sont un mécanisme permettant d'implémenter des fonctions de callback. Ils
sont très proches des pointeurs de fonction en C ou C++ et peuvent s'avérer très utiles
dans certaines conditions. Par exemple pour déléguer les opérations de tri de collections
ou de transformation de listes d'objets. Une autre utilisation de cette technique est la
déclaration d’événements sur le modèle émetteur/récepteur
L'exemple ci-dessous illustre le mécanisme de création et d'utilisation des délégués.
Code C#
using System;
//delegate base
public class HasDelegates {
// delegate declaration, similar to a function pointer declaration
public delegate bool CallbackFunction(string a, int b);
//method that uses the delegate
public bool execCallback(CallbackFunction doCallback, string x, int y)
{
Console.WriteLine("Executing Callback function...");
if (doCallback == null)
throw ArgumentException("Callback can't be null!");
return doCallback(x, y);
}
}
public class FunctionDelegates {
public static bool FunctionFoo(string a, int b) {
Console.WriteLine("Foo: {0} {1}", a, b);
C# versus Java ■ 35
return true;
}
}
public class DelegateTest {
public static void Main(string[] args) {
HasDelegates MyDel = new HasDelegates();
//create delegate
HasDelegates.CallbackFunction myCallback = new
HasDelegates.CallbackFunction(FunctionDelegates.FunctionFoo);
//pass delegate to delegate function
MyDel.execCallback(myCallback, "Twenty", 20);
}
} // DelegateTest
L'exemple suivant illustre l'utilisation d'un délégué statique :
Code C#
using System;
//delegate base
}
public class FunctionDelegates {
public static readonly HasDelegates.CallbackFunction BarFuncCallback =
new HasDelegates.CallbackFunction(FunctionBar);
public static bool FunctionBar(string a, int b) {
Console.WriteLine("Bar: {0} {1}", b, a);
return true;
}
}
public class DelegateTest {
public static void Main(string[] args) {
HasDelegates MyDel = new HasDelegates();
// with static delegate, no need to know how to create delegate
MyDel.execCallback(FunctionDelegates.BarFuncCallback, "Thirty
Three", 33);
}
} // DelegateTest
5.3 Qu'est ce qu'un délégué ?
Les délégués du C# sont équivalents aux pointeurs de fonction du C/C++ mais en plus
sécurisé (type-safe). En simplifiant, on peut dire qu’un délégué est un pointeur vers une
méthode mais c’est bien plus que cela.
Un délégué est un intermédiaire entre un émetteur et un récepteur, il permet d’établir un
contrat entre eux. C’est un moyen de communication entre objets qui ne se connaissent
pas forcément. Les délégués sont très utilisés dans l’environnement .NET, notamment le
mécanisme de gestion des événements qui est directement basé sur les délégués ; un
ENSICAEN - Spécialité Informatique
public class HasDelegates {
// delegate declaration, similar to a function pointer declaration
public delegate bool CallbackFunction(string a, int b);
//method that uses the delegate
public bool execCallback(CallbackFunction doCallback, string x, int y)
{
Console.WriteLine("Executing Callback function...");
return doCallback(x, y);
}
36 ■ C# versus Java
événement est d’ailleurs un délégué spécialisé.
La notion de délégué pourrait être comparé à celle d’interface puisque une interface est
aussi un contrat entre classes sauf qu’un délégué n’établit son contrat qu’avec une
méthode et non une classe. En quoi consiste le contrat d’un délégué ? Il consiste lors de la
déclaration de celui-ci à spécifier complètement une méthode : ses paramètres, leur
nombre, leur type et le type de retour de la méthode.
5.3.1 Quel est l’intérêt d’utiliser un délégué ?
ENSICAEN - Spécialité Informatique
L’intérêt des délégués n’est pas forcément visible au premier abord. Cependant, ils
répondent à un problème récurrent en programmation : Fournir un traitement via une
méthode à un objet qui l’exécutera plus tard (traitement asynchrone), c'est-à-dire que
l’objet appellera ce code au bon moment et ce sans forcément le connaître. Par exemple,
une application exécute un objet chargé de gérer des communications via le réseau
utilisera un délégué pour signaler qu’une donnée vient d’arriver et est disponible.
Ce mécanisme que les délégués utilisent de type « rappelle moi plus tard » (système de
rappel, callback en anglais) permet de gérer aisément les traitements asynchrones mais
offre aussi d’autres avantages. Dans l’exemple précédent, la classe communication ne
connaît rien de l’application qui l’utilise, le code de cette classe est indépendant et
réutilisable dans d’autres situations sans aucune modification. De manière générale,
l’utilisation des délégués permet de créer des classes, des contrôles ou composants
utilisateurs indépendants les uns des autres (dit à couplage faible) ce qui facilite la
réutilisation du code et tout ce qui en découle : maintenance, évolution & publication.
5.3.2 Utilisation d’un délégué ; exemple
La classe communication est chargée de gérer les communications réseaux, d’envoyer et
de recevoir des données. La réception des données est indépendante du fonctionnement de
l’objet, elle ne dépend que des émetteurs. Elle sera gérée par un délégué qui sera associé à
une méthode dans l’application. Cette méthode affichera la donnée reçue.
C# versus Java ■ 37
Un délégué est un type, une signature de méthode, qu’il faut définir. Utiliser un délégué
nécessite de déclarer une variable du type du délégué à utiliser. Le délégué déclaré cidessous défini une signature de méthode appelée DataReceived, pleinement précisée : la
méthode comporte un paramètre de type chaîne de caractères, la valeur de retour est de
type entier.
// définition d’un type délégué
delegate int DataReceivedHanlder(data);
// le délégué, initialisé à null
DataReceivedHandler dataReceivedHandler=null;
L’utilisation d’un délégué passe par plusieurs phases : la connexion ou l’abonnement, le
déclenchement puis la notification. L’abonnement consiste à instancier le délégué et à le
relier à une méthode dans la classe qui utilise l’objet ayant le délégué.
// instancie l'objet pour communiquer
comm= new Communication();
// instancie le délégué et le relie à une méthode de l’application
comm.dataReceivedHandler= new
Communication.DataReceivedHandler(this.DonneeRecue);
Le déclenchement du délégué consiste à l’appeler comme un simple appel de méthode
lorsque c’est nécessaire.
La notification est le fait que le délégué déclenche automatiquement lorsqu’il est appelé la
méthode associée dans l’application.
//----------------------------------------------------------// déclenché par le délégué lorsque une donnée est recue
private void DonneeRecue(string data) {
// affiche le message recu
MessageBox.Show("donnée recue: "+data);
}
Le délégué a fait son travail d’intermédiaire, il relaye la notification à la méthode associé
dans l’application.
Code de l’exemple Communication commenté
Ci-dessous, se trouve les extraits de codes principaux utilisé pour expliquer le
fonctionnement des délégués.
Classe communication
//----------------------------------------------------------public class Communication {
// déclaration type délégué
public delegate int DataReceivedHandler(string data);
// variable du type délégué donnée recue
public DataReceivedHandler dataReceivedHandler=null;
// ...
//----------------------------------------------------------// traitement d'attente en réception des données
// effectué dans une boucle, exécuté par un thread
public void RunReceptionData() {
string data= null;
// boucle d'attente
while(true) {
// donnée en reception
// ... remplir variable data
// appel délégué pour signaler présence donnée recue
if( dataReceivedHandler != null)
dataReceivedHandler(data);
ENSICAEN - Spécialité Informatique
// appel délégué pour signaler présence donnée recue
if( dataReceivedHandler != null)
// appel si délégué est connecté
dataReceivedHandler(data);
38 ■ C# versus Java
// suite traitement
}
}
}
Application principale
private void Form1_Load(object sender, System.EventArgs e) {
// instancie l'objet pour communiquer
comm= new Communication();
// instancie le délégué
comm.dataReceivedHandler= new
Communication.DataReceivedHandler(this.DonneeRecue);
}
//----------------------------------------------------------// déclenché par le délégué lorsque une donnée est recue
private void DonneeRecue(string data) {
// affiche le message recu
MessageBox.Show("donnée recue: "+data);
}
5.4 Les types valeur (ValueTypes et Structs)
ENSICAEN - Spécialité Informatique
En Java et C#, les données sur le tas (heap) sont libérées par le ramasse miettes
lorsqu'elles ne sont plus utilisées, alors que les données situées sur la pile ne rentrent pas
sous la responsabilité du ramasse miettes et sont instantanément supprimées au retour d'un
appel de méthode. De plus, les allocations sur la pile sont beaucoup plus rapide que celles
dans le tas. Enfin, les problèmes liés à la fragmentation de la mémoire n'existent pas dans
la pile.
C'est pourquoi, pour éviter d'utiliser inutilement le tas (heap), C# propose de stocker
directement certains objets dans la pile. Ces objets sont appelés types de valeur
(ValueType). D'ailleurs, en C#, l'ensemble des types primitifs sont des ValueType stockés
sous la forme de structures (cf. Int32). Contrairement aux classes, les ValueType sont
toujours passées par valeur et ne sont jamais libérées par le ramasse-miettes. De plus, les
tableaux de ValueType contiennent des valeurs et non des références d'objets, ce qui
permet d'économiser de la mémoire et du temps.
Pour déclarer un type de valeur, il suffit de d'utiliser le mot-clé struct au lieu de class .
Pour instancier un type de valeur, le mot-clé new doit être utilisé tout comme une classe
normale. Les structures et les constructeurs se comportent différemment par rapport aux
classes. Dans le cas d'une classe, la création d'une instance doit toujours précéder
l'utilisation de l'objet. Si le mot clé new est omis, la création n'a pas lieu et la référence est
nulle.
Tous les types struct héritent implicitement de la classe object. Une déclaration de struct
peut spécifier une liste d'interfaces implémentées, mais il n'est pas possible qu'elle spécifie
une classe de base. C'est pourquoi les structs ne sont jamais abstraits et sont toujours
implicitement finales (sealed). Comme l'héritage n'est pas géré dans les structs, la
visibilité par défaut déclarée est protected ou internal protected. et les membres de
fonctions ne peuvent être abstract ou virtual.
Code C#
using System;
struct Point {
public int x;
public int y;
C# versus Java ■ 39
public Point( int x, int y) {
this.x = x;
this.y = y;
}
public override string ToString() {
return String.Format("({0}, {1})", x, y);
}
public static void Main(string[] args) {
Point start = new Point(5, 9);
Console.WriteLine("Start: " + start);
/* The line below wouldn't compile if Point was a class */
Point end = new Point();
Console.WriteLine("End: " + end);
}
} // Point
5.5 L'identification de type (opérateur "as")
Cet opérateur ne possède pas d'équivalent en Java mais se rapproche du fonctionnement
du dynamic_cast de C++. Son fonctionnement consiste à exécuter une conversion
explicite d'une référence vers un type donné et en cas d'échec renvoie la valeur null. Cela
permet en une seule opération d'effectuer la conversion et l'assignation.
MyClass mc = o as MyClass;
if(mc != null)
//check if cast successful
mc.doStuff();
NOTE : L'opérateur as s'utilise aussi pour convertir des types de valeur.
5.6 Les propriétés
Les propriétés sont un moyen d'accéder aux propriétés membres d'une classe en respectant
les règles d'encapsulation. Dans le monde Java, les accesseurs (getteurs et setteurs) sont
utilisés à ces fins. L'avantage des propriétés en C# est de permettre à l'utilisateur d'accéder
aux attributs d'un objet de la même manière que s'il effectuait directement l'opération
object.attribut alors qu'en réalité il appelle une méthode de manière totalement
transparente.
Il est aussi possible de créer des propriétés en lecture seule (read-only), écriture seule ou
lecture/écriture si les accesseurs associés sont implémentés ou pas.
L'exemple ci-dessous nous illustre les différents types de propriétés :
Code C#
using System;
public class User {
public User(string name) {
this.name = name;
}
private string name;
//read-only property for name member variable
public string Name {
get {
return name;
}
}
ENSICAEN - Spécialité Informatique
Code C#
40 ■ C# versus Java
ENSICAEN - Spécialité Informatique
private static int minimum_age = 13;
//read-write property for class member, minimum_age
public static int MinimumAge {
get {
return minimum_age;
}
set {
if(value > 0 && value < 100)
minimum_age = value;
else
Console.WriteLine("{0} is an invalid age, so minimum age remains
at {1}", value, minimum_age);
}
}
public static void Main(string[] args) {
User newuser = new User("Bob Hope");
User.MinimumAge = -5; /* prints error to screen since value invalid
*/
User.MinimumAge = 18;
//newuser.Name = "Kevin Nash"; Causes compiler error since Name
property is read-only
Console.WriteLine("Minimum Age: " + User.MinimumAge);
Console.WriteLine("Name: {0}", newuser.Name);
}
} // User
L'inconvénient des propriétés C# est qu'elles peuvent mener à des situations pour le moins
bizarre lorsqu'une exception se produit. Imaginez une contrainte sur une propriété
définissant le fait qu'une valeur ne doit pas dépasser un certain seuil. En cas de
débordement, une exception va être levée à l'intérieur de l'accesseur et le client devra
traiter cette erreur alors qu'il réalise simplement une affectation. C'est ce qui est représenté
dans l'exemple ci-dessous avec la classe Clock :
Code C#
try {
myClock.Hours
= 28;
hour value */
myClock.Minutes = 15;
myClock.Seconds = 39;
/* setter throws exception because 28 is an invalid
} catch(InvalidTimeValueException itve) {
/* figure out which field was invalid and report error */
}
Pour éviter ce genre de situation, il est préférable dans la mesure du possible de traiter les
exceptions dans le corps des accesseurs et si ce n'est pas possible, indiquez clairement
dans la documentation que cette méthode est susceptible de lever une exception que
l'utilisateur devra catcher.
5.7 Attributes
Les Attributs de Runtime fournissent un moyen élégant d'insérer des annotations (i.e
méta-données) dans un module, une classe, une méthode, un paramètre ou une variable
membre. Les attributs sont très populaires dans un certain nombre de langages de
programmation tels que le noyau Linux ou les objets COM (Component Object Model) .
Ces attributs sont un moyen de fournir des informations au runtime dans le but d'exécuter
des tâches additionnelles ou d'étendre les caractéristiques d'un type donné.
Les quelques exemples ci-dessous vous illustrent les différents types attributs pouvant être
rencontrés.
C# versus Java ■ 41
[MethodImpl(MethodImplOptions.Synchronized)]:
Similaire
au
mot-clé
synchronized de Java.
[Serializable]:
Similaire
java.io.Serializable de Java.
à
l'implémentation
de
l'interface
Code C#
//declaration of bit field enumeration
[Flags]
enum ProgrammingLanguages{
C
= 1,
Lisp = 2,
Basic = 4,
All
= C | Lisp | Basic
}
aProgrammer.KnownLanguages = ProgrammingLanguages.Lisp; //set known
languages ="Lisp"
aProgrammer.KnownLanguages |= ProgrammingLanguages.C; //set known languages
="Lisp C"
aProgrammer.KnownLanguages &= ~ProgrammingLanguages.Lisp; //set known
languages ="C"
[WebMethod]:
utilisé en combinaison avec ASP.NET permet de spécifier qu'une méthode
est un web service.
Il est possible d'accéder aux attributs d'un module, d'une classe ou d'une méthode par
l'intermédiaire des APIs de reflection. C'est une fonctionnalité très utile pour vérifier à
l'exécution si une classe supporte un type d'attribut spécifique ou pour extraire des valeurs
données.
Les développeurs peuvent créer leurs propres attributs de Runtime en sous-classant
System.Attribute. L'exemple suivant nous indique comment implémenter son propre
attribut fournissant des informations sur l'auteur des sources et comment récupérer les
valeurs correspondantes à l'exécution.
Code C#
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Class)]
public class AuthorInfoAttribute: System.Attribute {
string author;
string email;
string version;
public AuthorInfoAttribute(string author, string email) {
this.author = author;
this.email = email;
}
public string Version {
get {
return version;
}
set {
version = value;
}
}
ENSICAEN - Spécialité Informatique
if((aProgrammer.KnownLanguages & ProgrammingLanguages.C) > 0){ //if
programmer knows C
//.. do something
}
42 ■ C# versus Java
public string
get {
return
}
}
public string
get {
return
}
}
Email {
email;
Author {
author;
}
ENSICAEN - Spécialité Informatique
[AuthorInfo("Dare Obasanjo", "[email protected]", Version="1.0")]
class HelloWorld { }
class AttributeTest {
public static void Main(string[] args){
/* Get Type object of HelloWorld class */
Type t = typeof(HelloWorld);
Console.WriteLine("Author Information for " + t);
Console.WriteLine("=================================");
foreach(AuthorInfoAttribute att in
t.GetCustomAttributes(typeof(AuthorInfoAttribute), false)) {
Console.WriteLine("Author: " + att.Author);
Console.WriteLine("Email: " + att.Email);
Console.WriteLine("Version: " + att.Version);
}//foreach
}//Main
}
5.8 Indexeurs
Les indexeurs ont une syntaxe spécifique permettant de surcharger l'opérateur [] d'une
classe. Ils sont utiles lorsqu'une classe est un conteneur pour d'autres types d'objets. Les
indexeurs supportent divers types comme index : entiers, chaines de caractère, ... Il est
aussi possible de créer des indexeurs supportant les tableaux multi-dimensionels. Enfin,
les indexeurs peuvent être surchargés. En Java, les indexeurs sont implémentés à travers
des accesseurs. Ainsi, en Java on écrirait : myList.getElement(3) et en C# : myList[3]
Code C#
using System;
using System.Collections;
public class IndexerTest: IEnumerable, IEnumerator {
private Hashtable list;
public IndexerTest () {
index = -1;
list = new Hashtable();
}
//indexer that indexes by number
public object this[int column] {
get {
return list[column];
}
set {
list[column] = value;
}
}
/* indexer that indexes by name */
public object this[string name] {
get {
C# versus Java ■ 43
return this[ConvertToInt(name)];
}
set {
this[ConvertToInt(name)] = value;
}
ENSICAEN - Spécialité Informatique
}
/* Convert strings to integer equivalents */
private int ConvertToInt(string value) {
string loVal = value.ToLower();
switch(loVal) {
case "zero": return 0;
case "one": return 1;
case "two": return 2;
case "three": return 3;
case "four": return 4;
case "five": return 5;
default:, return 0;
}
return 0;
}
/**
* Needed to implement IEnumerable interface.
*/
public IEnumerator GetEnumerator(){ return (IEnumerator) this; }
/**
* Needed for IEnumerator.
*/
private int index;
/**
* Needed for IEnumerator.
*/
public bool MoveNext() {
index++;
if(index >= list.Count)
return false;
else
return true;
}
/**
* Needed for IEnumerator.
*/
public void Reset() {
index = -1;
}
/**
* Needed for IEnumerator.
*/
public object Current {
get {
return list[index];
}
}
public static void Main(string[] args) {
IndexerTest it = new IndexerTest();
it[0] = "A";
it[1] = "B";
it[2] = "C";
it[3] = "D";
it[4] = "E";
Console.WriteLine("Integer Indexing: it[0] = " + it[0]);
44 ■ C# versus Java
Console.WriteLine("String Indexing: it[\"Three\"] = " +
it["Three"]);
Console.WriteLine("Printing entire contents of object via
enumerating through indexer :");
foreach( string str in it) {
Console.WriteLine(str);
}
}
} // IndexerTest
5.9 Les Directives de pré-processing
C# intègre un pré-processeur proposant un sous-ensemble limité des fonctionnalités
fournies par les pré-processeurs C/C++. Par exemple, la directive #include n'existe pas.
Les seules directives présentes sont liées aux opérations d'affectations d'identificateurs
permettant de réaliser de la compilation conditionnelle avec #define,#undef et
#if,#elif. Les directives #error et #warning provoquent l'affichage de messages
d'erreurs ou de warning pendant la phase de compilation. Enfin, #line permet de
spécifier la ligne correspondante au fichier source afin de reporter le numéro de ligne en
cas d'erreur.
Code C#
ENSICAEN - Spécialité Informatique
#define DEBUG /* #define must be first token in file */
using System;
class PreprocessorTest {
public static void Main(string[] args) {
#if DEBUG
Console.WriteLine("DEBUG Mode := On");
#else
Console.WriteLine("DEBUG Mode := Off");
#endif
}
}
5.10 Les Aliases
Le mot-clé using peut être utilisé pour assigner à un nom donné un type correspondant.
Le mécanisme est assez similaire aux typedef en C ou C++. Son comportement a été
détourné en C# pour permettre une meilleure lisibilité du code source, mais aussi pour
résoudre les problèmes de conflits entre les noms des namespaces.
Code C#
using Terminal = System.Console;
class Test {
public static void Main(string[] args) {
Terminal.WriteLine("Terminal.WriteLine is equivalent to
System.Console.Writeline");
}
}
5.11 La génération de code à l'exécution
Le package Reflection.Emit contient un ensemble de classes pouvant être utilisées afin
de générer des instructions .NET à l'exécution. Ces instructions sont ensuite compilées en
C# versus Java ■ 45
mémoire et peuvent être stockées physiquement sur disque sous la forme d'une assembly.
Ce mécanisme existe dans Java mais n'est pas proposé en standard dans les APIs. Les
moteurs de Servlets/JSP l'utilise pour générer le source d'une servlet et pour la charger en
mémoire. Les conteneurs EJB s'en servent pour générer l'implémentation des classes
techniques (EjbObject) ou le code de persistance des Entity. Malheureusement, ces classes
se trouvent dans les packages sun.tools.* (tools.jar), ce qui limite fortement leur
utilisation.
5.12 Pointeur et code non protégé (unsafe)
Bien que l'ensemble du langage C# soit très proche de Java et que les pointeurs ne soient
pas gérés de manière intégré, leur utilisation n'est pas proscrite dans le cadre de blocs
"unsafe" ou non protégé. Il faut garder à l'esprit que tout code s'exécutant dans ce type de
bloc ne bénéficie pas de tous les services du Framework (vérification de types, ...). Cela
signifie que le programme doit faire une confiance aveugle dans ce genre de code. Leur
utilisation est appropriée lorsqu'il est nécessaire de faire appel à une zone de mémoire
spécifique, par exemple dans le cas de drivers utilisant des adresses d'entrées/sorties
particulières.
Code C#
using System;
class UnsafeTest {
public static unsafe void Swap(int* a, int*b) {
int temp = *a;
*a = *b;
*b = temp;
}
public static unsafe void Sort(int* array, int size) {
for(int i= 0; i < size - 1; i++)
for(int j = i + 1; j < size; j++)
if(array[i] > array[j])
Swap(&array[i], &array[j]);
}
public static unsafe void Main(string[] args) {
int[] array = {9, 1, 3, 6, 11, 99, 37, 17, 0, 12};
Console.WriteLine("Unsorted Array:");
foreach(int x in array)
Console.Write(x + " ");
fixed( int* iptr = array ) { // must use fixed to get address of
array
Sort(iptr, array.Length);
}//fixed
Console.WriteLine("\nSorted Array:");
foreach(int x in array)
Console.Write(x + " ");
}
}
5.13 Passage par référence
En Java, les arguments sont passés par valeur, cela signifie que lorsqu'une méthode est
appelée, les arguments sont copiés sur la pile (copie des valeurs primitives mais aussi des
ENSICAEN - Spécialité Informatique
De plus, le garbage collector pouvant à tout moment ré-allouer des variables "managés", il
est absolument nécessaire de "sticker" ou figer ces variables le temps du traitement par le
bloc unsafe. Cette opération est réalisée à l'aide du mot-clé "fixed"
46 ■ C# versus Java
références). En C#, comme en C++ et d'une certaine manière en C, il est possible de
spécifier qu'une liste d'arguments doit être passée par référence et non par valeur. Ce type
de caractéristique est particulièrement intéressante lorsqu'une méthode doit retourner une
liste d'objets modifiés. Ainsi, en Java il est impossible de swaper (intervertir) deux
nombres entiers sans passer par leur type Wrapper.
En C#, les mot-clés pour spécifier qu'un paramètre doit être passé par valeur ou par
référence sont ref et out. Bien entendu, tout paramètre passé avec ref doit avoir été au
préalable initialisé, dans le cas où la méthode se charge de créer l'objet, le mot-clé out
doit être précisé.
Code Java
ENSICAEN - Spécialité Informatique
class PassByRefTest {
public static void changeMe(String s) {
s = "Changed";
}
public static void swap(int x, int y) {
int z = x;
x = y;
y = z;
}
public static void main(String[] args) {
int a = 5, b = 10;
String s = "Unchanged";
swap(a, b);
changeMe(s);
System.out.println("a := " + a + ", b := " + b + ", s = " + s);
}
}
OUTPUT
a := 5, b := 10, s = Unchanged
Code C#
using System;
class PassByRefTest {
public static void ChangeMe(out string
s = "Changed";
}
public static void Swap(ref int x, ref
int z = x;
x = y;
y = z;
}
public static void Main(string[] args)
int a = 5, b = 10;
string s;
Swap(ref a, ref b);
ChangeMe(out s);
Console.WriteLine("a := " + a + ",
}
}
OUTPUT
a := 10, b := 5, s = Changed
s) {
int y) {
{
b := " + b + ", s = " + s);
C# versus Java ■ 47
5.14 Les listes variables de paramètres
En C et C++, il est possible de spécifier qu'une fonction prend un nombre variable de
paramètres, cette fonctionnalité est fortement utilisée dans le cadre des fonctions printf()
et scanf(). En C#, le fonctionnement est le même excepté qu'il faut spécifier le mot-clé
param pour indiquer que le nombre de paramètres est variable.
Code C#
using System;
class ParamsTest {
public static void PrintInts(string title, params int[] args) {
Console.WriteLine(title + ":");
foreach(int num in args)
Console.WriteLine(num);
}
public static void Main(string[] args) {
PrintInts("First Ten Numbers in Fibonacci Sequence", 0, 1, 1, 2, 3,
5, 8, 13, 21, 34);
}
}
5.15 Les caractères spéciaux
Code C#
using System;
class VerbatimTest {
public static void Main() {
//verbatim string
string filename = @"C:\My Documents\My Files\File.html";
Console.WriteLine("Filename 1: " + filename);
//regular string
string filename2 = "C:\\My Documents\\My Files\\File.html";
Console.WriteLine("Filename 2: " + filename2);
string snl_celebrity_jeopardy_skit = @"
Darrell Hammond (Sean Connery) : I'll take ""Swords"" for $400
Will Farrell
(Alex Trebek) : That's S-Words, Mr Connery.
";
Console.WriteLine(snl_celebrity_jeopardy_skit);
}
}
5.16 Détection de débordement
C# fournit une option pour explicitement détecter ou ignorer les débordements dans le cas
de conversion vers un type plus faible. Le débordement lève une exception du type
System.OverFlowException et comme la détection met en place des mécanismes
pouvant affecter les performances, il est possible de l'activer ou de la désactiver en
spécifiant les mots-clés checked et unchecked au début de blocs.
ENSICAEN - Spécialité Informatique
C# fournit un moyen élégant pour résoudre le problème d'échappement des caractères
spéciaux dans une chaîne. Ainsi, les backslashes, tabulations, quotes et autres newlines
peuvent être intégrés à la chaîne sans avoir à utiliser une séquence d'échappement
particulière. La seule contrainte est de préfixer la déclaration de la chaîne avec le symbole
@.
48 ■ C# versus Java
Code C#
using System;
class CheckedTest {
public static void Main() {
int num = 5000;
/* OVERFLOW I */
byte a = (byte) num; /* overflow detected only if /checked compiler
option on */
/* OVERFLOW II */
checked {
byte b = (byte) num; /* overflow ALWAYS detected */
}
/* OVERFLOW III */
unchecked {
byte c = (byte) num; /* overflow NEVER detected */
}
}//Main
}
5.17 L'implémentation explicite d'interface
ENSICAEN - Spécialité Informatique
Quelquefois, lorsqu'il est nécessaire d'implémenter une interface, il se peut que vous
soyez confronté à des problèmes de conflits de noms. Par exemple, imaginons la classe
FileRepresentation qui implémente les interfaces IWindow et IFileHandler. Les deux
interfaces possèdent une méthode Close() qui dans le cas de l'interface IWindow permet
de refermer une fenêtre et dans le cas de IFileHandler permet de refermer un fichier. En
Java, il n'existe aucune solution à ce genre de problème à part écrire deux méthodes
possédant chacune des noms différents. C# propose une alternative intéressante au
développeur en lui permettant d'implémenter explicitement les deux interfaces dans la
classe FileRepresentation. Ainsi, lorsque le client veut fermer un fichier, il spécifie
l'interface utilisée de la manière suivante : (IFileHandler) obj.Close() et ou (IWindow)
obj.Close().
NOTE : Les méthodes de ces interfaces sont privées et ne peuvent être appelées sans
passer par une conversion explicite
Code C#
using System;
interface Ivehicle {
//identify vehicle by model, make, year
void IdentifySelf();
}
interface Irobot {
//identify robot by name
void IdentifySelf();
}
class TransformingRobot : IRobot, Ivehicle {
string model;
string make;
short year;
string name;
TransformingRobot(String name, String model, String make, short year) {
this.name = name;
this.model = model;
this.make = make;
this.year = year;
C# versus Java ■ 49
}
void Irobot.IdentifySelf() {
Console.WriteLine("My name is " + this.name);
}
void Ivehicle.IdentifySelf() {
Console.WriteLine("Model:" + this.model + " Make:" + this.make + "
Year:" + this.year);
}
public static void Main() {
TransformingRobot tr = new TransformingRobot("SedanBot", "Toyota",
"Corolla", 2001);
// tr.IdentifySelf(); ERROR
IVehicle v = (IVehicle) tr;
IRobot r
= (IRobot) tr;
v.IdentifySelf();
r.IdentifySelf();
}
}
OUTPUT
Model:Toyota Make:Corolla Year:2001
My name is SedanBot
6.1 Portabilité multi-plateforme (Write Once, Run Anywhere)
Une notion importante qui a contribué à la réussite du langage Java est sa capacité à
s'exécuter sur n'importe quelle plate-forme sans avoir à ré-écrire le code. Sun
officiellement supporte Linux, Windows, Solaris et d'autres éditeurs tiers assurent le
portage de la machine virtuelle sur OS/2, AIX et MacOS.
A l'heure actuelle, la plate-forme .NET n'est vraiment utilisable que sur les systèmes
Windows. Plusieurs efforts de portage ont été entrepris à travers le monde. Citons dans ce
domaine, les projets Mono (http:///www.go-mono.org), Halcyon (conversion .NET vers
Java) ou encore Microsoft avec le portage de .NET sur FreeBSD (nom de code : rotor).
6.2 Les extensions
Les extensions Java sont un moyen d'enrichir les APIs internes du JDK dans le but de
fournir une implémentation spécifique de certains packages système. Cela signifie que
l'extension est intégrée au sein des classes java.lang, java.util, java.net, etc, et
peut-être utilisée par l'ensemble des applications utilisant Java.
Pour faire un parallèle avec C#, c'est comme si l'extension proposée s'intégrait dans le
namespace System contenu dans l'assembly System.dll.
6.3 Le chargement dynamique de classe
Une des fonctions les plus intéressantes de Java est la possibilité de pouvoir redéfinir le
comportement du chargeur de classes associé à une procédure d'invocation distante. Le
chargement dynamique de classe permet en Java de télécharger une classe à partir de
n'importe quelle source (HTTP, Fichier, FTP, JMS, ...) et d'introduire ainsi de nouveaux
types dans la machine cible. Il est donc possible d'étendre à loisir les fonctionnalités d'un
système sans avoir à redéployer l'application toute entière.
ENSICAEN - Spécialité Informatique
6. Concepts avancés
50 ■ C# versus Java
Code Java
public class MyRMIServer extends UnicastRemoteObject implements
SomeInterface {
public MyRMIServer() throws RemoteException{ super();}
public String obtainName(IStockTicker ticker){
String stock_ticker = ticker.getTicker();
if (stock_ticker.equalsIgnoreCase("MSFT"))
return "Microsoft Corporation";
else if(stock_ticker.equalsIgnoreCase("SUNW"))
return "Sun Microsystems";
else
return "Unknown Stock Ticker";
}/* obtainName(IStockTicker) */
}
La méthode distante obtainName() dans la classe ci-dessus accepte des types qui
implémentent l'interface StockTicker. Le client doit juste se contenter de passer un objet
du type de l'interface attendue. Par exemple, le type CAC40Stock qui n'existe pas sur le
serveur peut-être téléchargé à partir d'une source quelconque et s'utilise comme s'il avait
toujours existé sur le serveur, et cela sans arrêter ou relancer l'application.
ENSICAEN - Spécialité Informatique
En C#, l'API .NET Remoting fournit un mécanisme similaire permettant de télécharger à
distance du code à partir de n'importe quelle machine à partir du moment où le client
publie l'URL de l'assembly.
6.4 Les interfaces contenant des champs
En Java, il est possible de déclarer des constantes dans les interfaces qui seront
disponibles aux classes dérivées. Cette technique est souvent utilisée dans Java pour
pallier l'absence de mécanisme intégré de gestion d'énumérations.
En C#, cette caractéristique n'existe pas et ne constitue pas réellement un problème dans
la mesure où ce langage fournit déjà un mécanisme d'énumération (enums)
6.5 Les classes anonymes (Inner classes)
Une classe anonyme est une classe instanciée à l'initialisation du type et ne possédant pas
de nom. Les classes anonymes sont souvent utilisées lorsqu'il est nécessaire d'avoir une et
une seule instance d'un type donné existant dans l'application. En Java, leur utilisation est
souvent cantonnée à la gestion des événements, les classes callback sont la plupart du
temps anonyme.
L'exemple qui suit illustre l'implémentation du Design Pattern State (automate à états
finis) en utilisant des classes anonymes
Code Java
/* An instance of this class represents the current state of a ClientView GUI.
*/
public abstract class ClientState {
// This instance of the class is used to signify that the user is not
// logged in. The only thing a user can do in this state is login and
exit.
public static ClientState NOT_LOGGED_IN = new ClientState() {
public void setMenuState(ClientView cv) {
cv.setMenuEnabledState(false); /* disable all menus */
cv.menuLogin.setEnabled(true);
cv.menuExit.setEnabled(true);
C# versus Java ■ 51
//can't type
cv.textArea.setEnabled(false);
}
public String toString(){
return "ClientState: NOT_LOGGED_IN";
}
};
public String toString() {
return "ClientState:EDITTING_DOCUMENT";
}
};
// Default constructor private to stop people from
// directly creating instances of the class.
private ClientState() {;}
// This disables various elements of the ClientView's menu dependent on
// which ClientState object this is.
public abstract void setMenuState(ClientView cv);
} // ClientState
Below is an example of the code that would utilize the above ClientState class.
bool loginUser(String username, String passwd) {
//check if already logged in
if (myGUI.state == ClientState.NOT_LOGGED_IN)
return true;
//enable parts of the GUI if the user authenticates
if (userAuthenticated(username, passwd)){
myGUI.state = ClientState.NO_OPEN_DOCUMENT;
myGUI.state.setMenuState(myView);
return true;
}
return false;
}/* loginUser(String, String) */
ENSICAEN - Spécialité Informatique
// This instance is used to signify that the user is logged in
// but has not yet created a document to work with.
// The user cannot type or save anything in this mode.
public static ClientState NO_OPEN_DOCUMENT = new ClientState() {
public void setMenuState(ClientView cv) {
cv.setMenuEnabledState(false); /* disable all menus */
cv.menuLogin.setEnabled(true);
cv.menuExit.setEnabled(true);
cv.menuOpenFile.setEnabled(true);
cv.menuNewFile.setEnabled(true);
//can't type
cv.textArea.setEnabled(false);
}
public String toString(){
return "ClientState: NO_OPEN_DOCUMENT";
}
};
// This instance of the class is used to signify that the user is
editting a file.
// In this mode the user can use any functionality he/she sees fit.
public static ClientState EDITTING_DOCUMENT = new ClientState() {
public void setMenuState(ClientView cv) {
cv.setMenuEnabledState(true); /* enable all menus */
cv.textArea.setEnabled(true);
}
52 ■ C# versus Java
6.6 Le versionning des Assemblies
La version des Assemblies est une des fonctionnalités les plus intéressantes de C# et de
.NET en général. Ainsi, vous avez la possibilité d'exécuter dans un même domaine
d'application plusieurs versions différentes d'une même classe. Cela est possible du fait
que les programmes C# référencent statiquement des bibliothèques de classes à l'aide de
leur nom mais aussi de leur version. Vous ne risquez donc pas d'avoir des problèmes de
cohérence entre différentes versions de DLL s'exécutant sur la même machine. Les
développeurs Java connaissent bien les âfres du déploiement d'applications Web (JSP et
servlets) référençant de multiples versions de parseur XML (xerces) ou de libraries
ant.jar. Cela est dû au fait que les ClassLoaders Java fonctionnent sur le principe de
premier chargé premier servi. Même si le modèle Java du chargement dynamique de
classes est, il faut l'avouer plus riche que celui de C#, il n'en demeure pas moins que cette
fonctionnalité manque cruellement dans le JDK.
7. Conclusion
ENSICAEN - Spécialité Informatique
Microsoft déclare de façon explicite que le langage C# est directement basé sur le langage
Java dont il a repris la plupart des choses en apportant de nouvelles solutions aux choses
jugées manquantes à l'époque. Entre -temps, Java à développé aussi des solutions à ses
lacunes. Néanmoins aujourd'hui peu de choses diffèrent ces deux langages comme nous
l'avons montré précédemment.
Java est aujourd'hui le langage plus utilisé dans le monde de la programmation logiciel.
Java est un langage portable sur tous les types de systèmes d'exploitation, alors que C#
reste très dépendant de Windows, même si lr projet Open-Source Mono a pour objectif de
porter C# sur Linux et MacOS mais les performances restent très médiocre. De plus, la
machine virtuelle « HotSpot » développée par Sun est la machine virtuelle la plus
puissante sur le marché et avec un version 7 qui augmente encore ses performances.
Le langage C# pour sa part a repris d'autres langages des concepts intéressants notamment
autour de :
•
Un mécanisme de versionning de classes : possibilité d'avoir plusieurs versions
différentes de classes chargées en même temps en mémoire.
•
Un mécanisme intégré permettant d'étendre les fonctionnalités de base d'une
classe au niveau des sources (cf Attributs de runtime).
De plus, le langage C# bénéficie de la puissance de l'éditeur Microsoft qui en font
certainement un langage qui va compté dans le monde du développement logiciel.
La plupart des développeurs, spécialement ceux possédant un background C ou C++
apprécieront à leur juste valeur les caractéristiques de surcharge d'opérateurs, pointeurs,
délégués et autre libération de mémoire déterministe faisant de C# un langage
véritablement riche. Aussi, les développeurs Java désirant se former à ce nouveau langage
seront agréablement surpris par le fait que certaines caractéristiques manquantes à Java
sont supportées par C#, à savoir les types de valeurs, le mot-clé const, les délégués ou
autre héritage explicite d'interface. D'un autre coté, le manque de support d'exception
typées, l'absence de classes anonymes, la portabilité multi-plateforme ou le fait que la
classe ne soit pas la plus petite unité de déploiement (c'est l'assembly) font que le choix
entre C# et Java n'est plus aussi clair.
C# versus Java ■ 53
Révision
Version
0.1
Date
Commentaires
Basé sur l'article de Dare Obasanjo (traduction de Alain Vizzini
Dec 23, 2012
http://alain.vizzini.free.fr/)
ENSICAEN - Spécialité Informatique