|
C#
versus Java |
Combien de fois n'a t-on pas entendu les questions ou réflexions suivantes : "mais qu'est-ce que ça fait de plus que Java, ce C# ? " ou encore "C# ? Ah bah oui, c'est le Java propriétaire de Microsoft pour contrer Sun" et combien de fois n'avez vous pas eu envie d'expliquer ou de convaincre à ces personnes, au demeurant de bonne foi, que C# méritait plus d'estime car il possédait de multiples facettes et qualités souvent trop longues à énumérer ou à résumer au détour d'un forum de discussions. D'ailleurs, dans la majeure partie des cas, ce dédain face à un nouveau langage, qui plus est développé par Microsoft (quel sacrilège ;-)), n'est qu'un moyen de dissimuler une certaine forme d'ignorance.
Cet article intitulé "C# versus Java" se propose de vous faire connaître C# à travers un ensemble de caractéristiques communes ou différentes de Java (Exceptions, Héritages, ...) avec un regard serein et objectif. Devant la multitude de sujets proposés, nous avons essayé de regrouper les fonctionnalités qui présentaient des similitudes, celles qui différaient de part leur implémentation et enfin, celles qui avaient un caractère novateur.
La plupart des exemples ont été testés avec Microsoft .NET Framework beta 2 et Java 2 Standard Edition.
L'auteur de l'article original est Dare Obasanjo qui a eu la gentillesse de nous autoriser à enrichir et à traduire ses écrits. Certaines parties ont été totalement revisitées par nos soins pour plus de clarté et de simplicité mais en aucun cas le sens et la teneur de l'article original n'ont été modifiés. Nous espérons qu'après avoir lu ces quelques pages vous serez plus à même de comprendre ce qu'est C#, mais aussi d'analyser ou de mieux juger par vous même ses multiples caractéristiques et fonctionnalités.
Index Rapide
Comme Java, C# possède une
super classe, mère de tous les objets : System.Object. La classe
équivalent s'appelle java.lang.object. Les méthodes
présentent dans ces deux classes sont très similaires (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.
Il y a énormément de similitudes entre les deux langages, presque tous les mots-clés Java ont un équivalent en C# à part quelques exceptions telles que transient, throws et strictfp. La table ci-dessous vous illustre les mots-clés présents des deux cotés
|
mot-clé C#
|
mot-clé Java
|
mot-clé C#
|
mot-clé Java
|
mot-clé C#
|
mot-clé Java
|
mot-clé
C# |
mot-clé
Java |
|
abstract |
abstract |
explicit |
N/A |
object |
N/A |
this |
this |
|
as |
N/A |
extern |
native |
operator |
N/A |
throw |
throw |
|
base |
super |
finally |
finally |
out |
N/A |
true |
true |
|
bool |
boolean |
fixed |
N/A |
override |
N/A |
try |
try |
|
break |
break |
float |
float |
params |
N/A |
typeof |
N/A |
|
byte |
N/A |
for |
for |
private |
private |
uint |
N/A |
|
case |
case |
foreach |
N/A |
protected |
N/A |
ulong |
N/A |
|
catch |
catch |
get |
N/A |
public |
public |
unchecked |
N/A |
|
char |
char |
goto |
goto1 |
readonly |
N/A |
unsafe |
N/A |
|
checked |
N/A |
if |
if |
ref |
N/A |
ushort |
N/A |
|
class |
class |
implicit |
N/A |
return |
return |
using |
import |
|
const |
const1 |
in |
N/A |
sbyte |
byte |
value |
N/A |
|
continue |
continue |
int |
int |
sealed |
final |
virtual |
N/A |
|
decimal |
N/A |
interface |
interface |
set |
N/A |
void |
void |
|
default |
default |
internal |
protected |
short |
short |
while |
while |
|
delegate |
N/A |
is |
instanceof |
sizeof |
N/A |
: |
extends |
|
do |
do |
lock |
synchronized |
stackalloc |
N/A |
: |
implements |
|
double |
double |
long |
long |
static |
static |
N/A |
strictfp |
|
else |
else |
namespace |
package |
string |
N/A |
N/A |
throws |
|
enum |
N/A |
new |
new |
struct |
N/A |
N/A |
transient |
|
event |
N/A |
null |
null |
switch |
switch |
N/A |
volatile |
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. .
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).
Le ramasse miettes en C# implémente un algorithme bien connu appelé Mark and Compact garbage collection algorithm.
Dans les langages tels que C ou C++, la dimension de chaque sous tableau doit être identique dans les tableaux à plusieurs dimensions. En Java et C#, les tableaux n'ont pas à se plier à cette contrainte car ils peuvent être créés comme des tableaux à une dimension référençant d'autres tableaux. Ils sont appelés "Jagged Arrays". Pour cette raison, les tailles initiales des lignes et colonnes de ce type de tableaux peuvent-être différentes. C'est ce que montre le code suivant :
int
[][]myArray = new int[2][];
myArray[0]
= new int[3];
myArray[1]
= new int[9];
Le code précédent est valide pour Java et C#.
Comme en Java et contrairement à C++, les méthodes en C# doivent être intégrées dans une classe.
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.
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.
C# Code
string
csString = "Apple Jack";
csString.ToLower();
/* Does not modify string, instead returns lower case
copy of string */
Java Code
String
jString = "Grapes";
jString.toLowerCase();
/* Does not modify string, instead returns lower case
copy of string */
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.
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.
C# Code
sealed
class Student
{
string fname;
string lname;
int
uid;
void attendClass()
{}
}
Java Code
final
class
Student
{
String fname;
String lname;
int
uid;
void attendClass()
{}
}
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. 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.
C# Code
using
System;
using
System.IO;
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
Java Code
class MyException
extends Exception{
public MyException(String message){ super(message); }
public MyException(String message, Exception
innerException){ super(message, innerException); }
}
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 re){ /* parent of ArithmeticException */
throw new MyException("MyException occured", re); /* rethrow new exception with cause specified
*/
}
}finally{
System.out.println("***Finally block executes even though MyException not
caught***");
}
}//main(string[])
} // ExceptionTest
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 de part et d'autre des blocs statiques qui seront exécutés au chargement. Il sont appelés plus communément constructeurs statiques.
C# Code
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();
Console.WriteLine("Completed main()");
}
}
Java Code
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()");
}
} Exécution des deux exemples : In static constructorBeginning main()Invoking static DoStuff() methodInitializing instance variableInitializing static variableIn instance constructorCompleted 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 status alors que celui de Java impose une signature bien précise et retourne void.
C# Code
using
System;
class
A
{
public
static void
Main(String[] args)
{
Console.WriteLine("Hello World");
}
}
Java Code
class
B
{
public
static void
main(String[] args)
{
System.out.println("Hello World");
}
}
Il est généralement recommandé d'insérer dans chaque classe une méthode Main() permettant tester la classe de manière unitaire indépendamment de la méthode Main() générale.
Ainsi, 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 ou à travers un outil. 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). L'utilisation de ces techniques associées à une compilation conditionnelle via les directives de pré-processing vous procure une grande souplesse dans les tests.
Java ExempleC:\CodeSample> javac A.java B.java C:\CodeSample> java AHello World from class A
C:\CodeSample> java BHello World from class B
C# Exemple
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
Ainsi, en Java, contrairement à C#, il n'est pas nécessaire de recompiler les classes pour changer le point d'entrée du programme. D'un autre coté, en C#, les directives de compilation permettent de produire du code paramétrable pour la phase de test ou de release.
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.
C# Code
using
System;
class
B:A, IComparable
{
int CompareTo(){}
public
static void
Main(String[] args)
{
Console.WriteLine("Hello World");
}
}
Java Code
class
B extends A implements Comparable
{
int
compareTo(){}
public
static void
main(String[] args)
{
System.out.println("Hello World");
}
}
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. Dans la pratique, ce n'est pas vraiment un problème dans la mesure où Microsoft définit clairement que les noms d'interfaces doivent commencer par IMyInterface (tel que ICloneable).
En
C#, l'opérateur is
est totalement analogue au mot-clé instanceof de Java. Les deux
bouts de code suivant sont équivalent :
C# Code
if(x
is MyClass)
MyClass
mc = (MyClass) x;
Java Code
if(x
instanceof MyClass)
MyClass
mc = (MyClass) x;
Un
Namespace C# est une manière de regrouper des classes par domaine et s'utilise
de manière similaire par rapport au mot-clé package en Java. Les
développeurs C++ noteront que la syntaxe des namespaces en C# est très proche de
C++. Toutefois, en Java, la structure arborescente des packages dicte la
structure des sources sous-jacentes, ce qui n'est pas le cas de C#.
Examples :
C# Code
namespace
com.carnage4life
{
public
class MyClass
{
int x;
void doStuff(){}
}
}
Java Code
package
com.carnage4life;
public
class MyClass
{
int x;
void
doStuff(){}
}
Enfin, la syntaxe C# autorise la multiplicité des déclarations de namespaces au sein d'un même fichier.
C# Code
using
System;
namespace
Company
{
public class MyClass
{ /*
Company.MyClass */
int x;
void doStuff(){}
}
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
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.finalise()).
C# Code
using
System;
public
class MyClass
{
static
int num_created = 0;
int i =
0;
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();
}
}
Java Code
public
class MyClass
{
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();
}
}
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 mot-clé lock qui est sémantiquement
l'équivalent du synchronized en Java.
C# Code
public
void WithdrawAmount(int num)
{
lock(this)
{
if(num
< this.amount)
this.amount
-= num;
}
}
Java Code
public
void withdrawAmount(int num)
{
synchronized(this)
{
if(num
< this.amount)
this.amount
-= num;
}
}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.
C# Code
using
System;
using
System.Runtime.CompilerServices;
public
class BankAccount
{
[MethodImpl(MethodImplOptions.Synchronized)]
public
void WithdrawAmount(int num)
{
if(num < this.amount)
this.amount - num;
}
}//BankAccount Java Code
public
class BankAccount
{
public
synchronized void withdrawAmount(int num)
{
if(num < this.amount)
this.amount - num;
}
}//BankAccount
La
table ci-dessous vous 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).
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 |
Java access modifier |
|
private |
private |
|
public |
public |
|
internal |
protected |
|
protected |
N/A |
|
internal protected |
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.
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.
C# Code
using
System;
using
System.Xml;
using
System.Reflection;
using
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");
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);
}
}//for (int i...)
}
}
Java Code
import
java.lang.reflect.*;
import
org.w3c.dom.*;
import
javax.xml.parsers.*;
class
ReflectionTest
{
public
static void
main(String[] args)
{
Class c=null;
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());
}
}
}
}
A
la vue du code précédent, vous aurez remarqué que la granularité de la
reflection est plus grande en C# qu'en Java. 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#.
C# CodeType t = typeof(ArrayList); Java Code
Class c = java.util.Arraylist.class; /* Must append ".class" to fullname of class */
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 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).
C# Code
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;
}
}
Java Code
import java.util.*;
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;
}
}
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# .
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 place et d'une rapidité de traitement
moindre.
Ci-dessous, plusieurs manières d'implémenter des valeurs réelles
en C#.
C# Code
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
Java possède deux façons de déclararer un tableau : la première, créé pour la compatibilité avec C et C++ et la deuxième plus simple et plus claire. C# utilise, quant à lui, la deuxième façon.
C# Code
int[]
iArray = new int[100]; //valid, iArray
is an object of type int[]
float
fArray[] = new float[100]; //ERROR: Won't
compile
Java Code
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# et Java appelle implicitement les constructeurs père 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, C# et Java assurent que l'appel des constructeurs se fait dans un ordre bien précis assurant 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".
C# Code
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)
{
this.Id = id;
}
}
Java Code
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);
this.Id = id;
}
}
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 et ne peut posséder de méthodes statiques.
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 classe imbriquée en C# et Java.
C# Code
public
class Car
{
private
Engine engine;
private
class Engine
{
string make;
}
}
Java Code
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.
Les
Threads en Java sont créés par dérivation de la classe java.lang.Thread et en
re-définissant la méthode run() ou par implémentation directe de l'interface the
java.lang.Runnable.
Vous avez le choix entre l'héritage (historique) et la délégation (plus
sûre).
En
C#, la création de Threads se fait à l'aide de l'opération new System.Threading.Thread en
passant en paramètre 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 Tread 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 nous 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# Code
using
System;
using
System.Threading;
using
System.Collections;
public
class WorkerThread
{
private
int idNumber;
private
static int
num_threads_made = 1;
private
ThreadSample owner;
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
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");}
}
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[]) */
}//ThreadSample
Java Code
import
java.util.*;
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;
start(); //calls run and starts the
thread.
}/* WorkerThread() */
//sleeps for a random amount of time to simulate working on
a task
public void
run()
{
Random
r = new Random(System.currentTimeMillis());
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;}
}
// 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++)
{
new WorkerThread(ts);
ts.threadOrderList.add(new Integer(i));
}
Thread.sleep(3600000); //wait for it all to
end
}/* main(String[]) */
}//ThreadSample
Le
mot-clé volatile
permet en Java 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 casses-têtes difficles à résoudre.
Il
existe des différences fondamentales dans la sémantique de l'ordre volatile en
C# et Java qui sont illustrées dans l'exemple ci-dessous tiré de l'article
suivant : The "Double-Checked Locking is Broken"
Declaration
C# Code
/*
Used to lazily instantiate a singleton class */
/*
WORKS AS EXPECTED
*/
class
Foo
{
private
volatile Helper helper = null;
public
Helper getHelper()
{
if (helper == null)
{
lock(this)
{
if (helper == null)
helper = new
Helper();
}
}
return helper;
}
}
Java Code
/*
Used to lazily instantiate a singleton class */
/*
BROKEN UNDER CURRENT SEMANTICS FOR VOLATILE */
class
Foo
{
private
volatile Helper helper = null;
public
Helper getHelper()
{
if (helper == null)