|
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)
{
synchronized(this)
{
if (helper == null)
helper
= new Helper();
}
}
return
helper;
}
}
Bien que le code précédent paraît identique excepté l'utilisation de synchronized en Java et du lock en C#, il n'est pas garantie que la version Java fonctionne sur toutes les machines virtuelles. A l'heure actuelle, le modèle mémoire de Java n'interdit pas le ré-agencement dans l'ordre des affectations de variables volatile ou pas, et il ainsi possible qu'un objet soit créé avant que la référence helper ne pointe vers le nouvel objet. Dans ce cas, deux objets sont créées et une référence vers un objet incomplet peut être retourné. En C#, la sémantique de l'ordre volatile prend en compte ce genre de problème et l'ordre des opérations de lecture et écriture ne peut être modifiée avant une affectation de variable volatile. De plus, en C#, annoter une variable volatile interdit au compilateur JIT de placer des variables dans des registres et assure qu'elles seront stockées en mémoire (tas).
Pour plus d'informations sur ce sujet bien connu des développeurs Java, n'hésitez pas à parcourir l'article de Javaworld sur le thème "Double Checked locking mecanism" : Double-checked locking: Clever, but broken.
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, ...).
Le code ci-dessous vous illustre différentes utilisations de la surcharge d'opérateurs en C#.
NOTE:
Contrairement à C++, C# n'autorise pas la surcharge des opérateurs suivants ; new, ( ), ||, &&, =.
C# Code
using System;
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);
}
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
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).
C# Code
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;
}
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 Java, les Assemblies contiennent du code MSIL (byte-code), un ensemble de méta-données et des ressources requises par les classes. N'hésitez pas à consulter l'article sur la structure des Assemblies dans ce même site pour plus d'informations.
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 chainé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 interf