C# versus Java
Transcription
C# versus Java
Différences en Java et C# Apprendre le C# à partir de Java Contenu 1. Introduction..........................................................................................................................1 2. C# n'est qu'un clone de Java...............................................................................................1 3. La même chose mais en différent.......................................................................................7 4. Comme un air de "Déjà Vu"..............................................................................................20 5. Maintenant, des choses totalement différentes..............................................................37 6. Concepts avancés...............................................................................................................50 7. Conclusion..........................................................................................................................53 1. Introduction Cet article se propose de vous faire connaître C# à travers un ensemble de caractéristiques communes ou divergentes 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 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 dans la classe Object. Remarque : En C#, la classe de base peut être écrite soit en minuscule "object" soit en capital "Object". En fait, à la compilation ces deux types sont remplacés par System.Object. 2.2 Nommage C# se distingue de Java par le nom des méthodes qui commencent conventionnellement par une majuscule. Par exemple, la méthode main de Java se nomme main() et Main() en C#. Le reste des noms utilisent la même convention. 2.3 À 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 É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.ensicaen.fr 2 ■ Différences en Java et C# 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 plateformes, 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.4 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 primitifs (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. 2.5 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 mot-clé Java abstract N/A super boolean break N/A case catch char N/A class const1 continue N/A default N/A mot-clé C# explicit extern finally fixed float for foreach get goto if implicit in int interface internal is 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 synchronize stackalloc d long static do do lock double double else else enum event enum N/A long namespac package e new new null null 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 mot-clé mot-clé C# Java this this throw throw true true try try typeof N/A uint N/A ulong N/A unchecked N/A unsafe N/A ushort N/A using import value N/A virtual N/A void void while while : extends N/A : implements static N/A strictfp string N/A N/A throws struct switch N/A switch N/A volatile transient volatile 2.6 Pas de fonctions globales Comme en Java et contrairement à C++, les méthodes en C# doivent être intégrées dans une classe. Différences en Java et C# ■ 3 2.7 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.8 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. Code Java String jString = "Grapes"; // Ne modifie pas la chaîne, mais retourne une copie jString.toLowerCase(); Code C# 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 StringBuffer pour Java et StringBuilder pour C#. Remarque : En C#, la classe chaîne peut-être écrite sous la forme string ou String. 2.9 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#, on utilise le mot-clé sealed (scellée en français) et en Java le mot-clé final. Code Java final class Student { String fname; String lname; int uid; void attendClass() {} } Code C# sealed class Student { string fname; string lname; int uid; void AttendClass() {} } ENSICAEN - Spécialité Informatique string csString = "Apple Jack"; // Ne modifie pas la chaîne, mais retourne une copie csString.ToLower(); 4 ■ Différences en Java et C# 2.10 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. Remarque : 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 ENSICAEN - Spécialité Informatique 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; // Ne sera pas exécuté } catch(RuntimeException e) { // parent de ArithmeticException throw new MyException("MyException levée", e); } } finally { System.out.println("***Block executé même si MyException est lévée"); } } } Code C# class MyException: Exception { public MyException(string message): base(message){ } public MyException(string message, Exception innerException): base(message, innerException){ } } public class ExceptionTest { Différences en Java et C# ■ 5 static void DoStuff() { throw new FileNotFoundException(); } public static int Main() { try { try { DoStuff(); return 0; // Ne sera pas exécuté } catch(IOException ioe) { // parent de FileNotFoundException throw new MyException("MyException levée", ioe); } } finally { Console.WriteLine("***Block executé même si MyException est lévée"); } } } 2.11 Initialisation de membres et constructeurs statiques Code Java 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("Main() exécuté"); } } ENSICAEN - Spécialité Informatique 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 communément constructeurs statiques. 6 ■ Différences en Java et C# Code C# class StaticInitTest { string instMember = InitInstance(); string staMember = InitStatic(); StaticInitTest() { Console.WriteLine("In instance constructor"); } ENSICAEN - Spécialité Informatique 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("Main() exécuté"); } } 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.12 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 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); Différences en Java et C# ■ 7 ArrayList list = new ArrayList(); int z = 100; printString(p); //p convertit en object printString(z); //z convertit en object list.add(1); list.add(13.12); list.add(z); for(int i =0; i < list.Count; i++) printString(list[i]); } } Code C# 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 Java class B { public static void main(String[] args) { System.out.println("Hello World"); } } Code C# using System; class A { public static void Main(String[] args) { Console.WriteLine("Hello World"); ENSICAEN - Spécialité Informatique 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 convertit en object PrintString(z); //z convertiti en object list.Add(1); list.Add(13.12); list.Add(z); for(int i =0; i < list.Count; i++) PrintString(list[i]); } } 8 ■ Différences en Java et C# } } 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 ENSICAEN - Spécialité Informatique 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 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"); } } Les développeurs Java pourront toujours argumenter que cela rend les sources Différences en Java et C# ■ 9 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(){} } 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# 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 3.4 Accessibilité à la classe La table ci-dessous synthétise les différents mot-clés permettant de modifier la visibilité ENSICAEN - Spécialité Informatique Code C# 10 ■ Différences en Java et C# 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 le 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. ENSICAEN - Spécialité Informatique 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 { 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(); } } Différences en Java et C# ■ 11 Code C# 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(); } } 3.6 Les threads et les membres volatiles 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 ainsi 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'exemple 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(). 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; } // dort pendant un temps aléatoire pour simuler l'exécution d'une tâche public void run() { Random r = new Random(System.currentTimeMillis()); ENSICAEN - Spécialité Informatique En Java, les threads 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. 12 ■ Différences en Java et C# 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); } public Integer getIDNumber() {return idNumber;} } ENSICAEN - Spécialité Informatique public class ThreadSample{ private Vector threadOrderList = new Vector(); private Integer nextInLine(){ return (Integer) threadOrderList.firstElement(); } private void removeNextInLine(){ threadOrderList.removeElementAt(0); // tous les threads ont fini leur travail if(threadOrderList.isEmpty()) System.exit(0); } public synchronized void workCompleted(WorkerThread worker) { while(worker.getIDNumber().equals(nextInLine())==false) { try { // attend la fin d'un thread System.out.println (Thread.currentThread().getName() + " is waiting for Thread #" + nextInLine() + " to show up."); wait(); } catch (InterruptedException e) {} } System.out.println("Thread #"+worker.getIDNumber()+" is home free"); // suprimer le thread n° D de la liste des déjà vus removeNextInLine(); // indique aux autres threads de continuer notifyAll(); } public static void main(String[] args) throws InterruptedException { ThreadSample ts = new ThreadSample(); // Lance 25 threads for(int i=1; i <= 25; i++) { ts.threadOrderList.add(new Integer(i)); Thread t = new WorkerThread(ts); t.start(); // lance les threads } Thread.sleep(3600000); // attend la fin de tous les threads } } Code C# public class WorkerThread { private int idNumber; private static int num_threads_made = 1; private ThreadSample owner; Différences en Java et C# ■ 13 public WorkerThread(ThreadSample owner) { idNumber = num_threads_made; num_threads_made++; this.owner = owner; } // dort pendant un temps aléatoire pour simuler l'exécution d'une tâche 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); } public int getIDNumber() {return idNumber;} } ENSICAEN - Spécialité Informatique 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); // tous les threads ont fini leur travail if(threadOrderList.Count == 0) Environment.Exit(0); } public void workCompleted(WorkerThread worker) { try { lock(this) { while(worker.getIDNumber() != NextInLine()) { try { // attend la fin d'un thread 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"); // supprime le numero” ID de la liste des thread // déjà vus RemoveNextInLine(); // indique à tous les threads de reprendre Monitor.PulseAll(this); } } } } catch(SynchronizationLockException){ Console.WriteLine("SynchronizationLockException occurred"); } 14 ■ Différences en Java et C# } public static void Main(String[] args) { ThreadSample ts = new ThreadSample(); // Lance 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); // attend tous les threads } } 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 casses-têtes difficiles à résoudre. 3.7 Synchronisation de méthodes ENSICAEN - Spécialité Informatique 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 mot-clé 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 ; } } } 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. Différences en Java et C# ■ 15 Code Java public class BankAccount { public synchronized void withdrawAmount(int num) { if(num < this.amount) { this.amount - num; } } } Code C# using System; using System.Runtime.CompilerServices; public class BankAccount { [MethodImpl(MethodImplOptions.Synchronized)] public void WithdrawAmount(int num) { if(num < this.amount) { this.amount – num; } } } 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 « Réflexion » La capacité à découvrir les méthodes et attributs dans une classe et à invoquer dynamiquement des méthodes à l'exécution est appelée la réflexion. Cette caractéristique existe aussi bien en Java qu'en C# à la différence près que la réflexion 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; Document d; try { c = DocumentBuilderFactory.newInstance() ENSICAEN - Spécialité Informatique 3.8 L'opérateur d'identification de type 16 ■ Différences en Java et C# .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); } // Récupère les methodes de la classe Method[] methods = c.getMethods(); // affiche la signature des methods et des parametres 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()); } } ENSICAEN - Spécialité Informatique } } 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 { // Charge l'assembly and recupère le type assembly = Assembly.LoadFrom("C:\\WINNT\\Microsoft.NET" +"\\Framework\\»+v1.0.2914\\System.XML.dll"); type = assembly.GetType("System.Xml.XmlDocument", true); // Malheureusement on ne peut pas instancier dynamiquement // des types via the type object en 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) { Différences en Java et C# ■ 17 Console.WriteLine("Cannot find default constructor of " + type); } catch(MemberAccessException) { Console.WriteLine("Could not create new XmlDocument instance"); } // Récupère les methodes MethodInfo[] methods = type.GetMethods(); // Affiche la signature des méthodes et des paramètres 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, on a 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, on utilise getClass() en Java et GetType() en C#. 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 défini en C# qui n'effectue aucun arrondi au prix d'un surplus de mémoire et de temps de traitement. L'équivalent Java est la classe BigDecimal. Ci-dessous, plusieurs manières d'implémenter des valeurs réelles en C#. Code C# decimal dec = 100.44m; //m est le suffixe utilisé pour les décimaux double dbl = 1.44e2d; 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]; float fArray[] = new float[100]; ENSICAEN - Spécialité Informatique Code Java 18 ■ Différences en Java et C# Code C# int[] iArray = new int[100]; float fArray[] = new float[100]; // ERROR: ne compile pas. 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. 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 import java.util.*; ENSICAEN - Spécialité Informatique public class ConstantTest { // Constantes définies à la compilation final int i1 = 10; static final int i2 = 20; // constante de classe // Constante définie à l'exécution public static final long l1 = new Date().getTime(); // référence d'objet constante final Vector v = new Vector(); // Constante blanche définie à la première utilisation final float f; ConstantTest() { // définition de la constante au moment de l'instanciation. if (test) { f = 17.21f; } else { f = 21.12f; } } } Code C# using System; public class ConstantTest { // Constantes définies à la compilation const int i1 = 10; // implicitement une variable statique // La ligne suivante ne compile pas à cause du mot clé 'static' // public static const int i2 = 20; // Constante définie à l'exécution public static readonly uint l1 = (uint) DateTime.Now.Ticks; // référence d'objet constante readonly Object o = new Object(); // variable en lecture seule définie à la première utilisation readonly float f; Différences en Java et C# ■ 19 ConstantTest() { // définition de la constante au moment de l'instanciation. if (test) { f = 17.21f; } else { f = 21.12f; } } } NOTE : Le langage Java supporte aussi les paramètres de méthode 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 Code Java 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# 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; } } ENSICAEN - Spécialité Informatique 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". 20 ■ Différences en Java et C# 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 classe imbriquée en C# et Java. Code Java public class Car { private Engine _engine; private class Engine { String _make; } } ENSICAEN - Spécialité Informatique 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 patrons de conception. 4.2 La surcharge d'opérateurs 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. Tout programmeur C++ s'est un jour demandé au moment d'utiliser une API, si les opérateurs d'un type donné étaient redéfinies ou non conduisant au développement d'un code différent dans un cas ou dans l'autre. D'un autre côté, 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 Différences en Java et C# ■ 21 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# 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); } } 4.3 L'instruction "switch" 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 cidessous). 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; ENSICAEN - Spécialité Informatique 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); } } 22 ■ Différences en Java et C# // ERREUR: Ne compile pas dû l'absence de break entre “D” et “E”. 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# elle utilise le mot foreach. Code Java string[] greek_alphabet = {"alpha", "beta", "gamma", "delta", "epsilon"}; for(string str : greek_alphabet) { System.out.println(str + " is a letter of the greek alphabet"); } ENSICAEN - Spécialité Informatique 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 versioning 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). 4.6 Collections 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 Différences en Java et C# ■ 23 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 (Vector, Hashtable , …) et d'autres ne le sont pas (ArrayList, HashMap, …). 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. 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”); throw new SocketException(); } catch(SocketException) { if (ntries < 5) { goto retry; } } } } Pour gérer ce problème dans les boucles, Java utilise des étiquettes. 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 … ENSICAEN - Spécialité Informatique Code C# 24 ■ Différences en Java et C# } 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 dérivées. 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. ENSICAEN - Spécialité Informatique 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. Pour permettre cette optimisation en Java, il faut adjoindre le mot clé final pour les méthodes non redéfinissables. Une autre raison dans le choix de C# pourrait s'expliquer par le fait que les méthodes virtuelles peuvent être accidentellement redéfinies par le développeur dans une sous-classe. Dans ce cas, l'appel à la méthode de la classe parent sera masqué par la méthode de la classe dérivée et pourrait engendrer des résultats inexplicables. Toutefois, les IDE permettent aujourd'hui d'éviter ces erreurs, par exemple en Java en obligeant mettre l'annotation @Override sur une méthode redéfinie. On peut ainsi détecter le masquage. 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); } @Override public void doStuff(String str) { System.out.println("In Child.doStuff: " + str); } } public class VirtualTest { public static void main(String[] args) { Child ch = new Child(); ch.doStuff(100); Différences en Java et C# ■ 25 ch.doStuff("Test"); ((Parent) ch).doStuff("Second Test"); } } Résultat : In Child.DoStuff: 100 In Child.DoStuff: Test In Child.DoStuff: Second Test Code C# using System; public class VirtualTest { public static void Main(string[] args) { Child ch = new Child(); ch.DoStuff(100); ch.DoStuff("Test"); ((Parent) ch).DoStuff("Second Test"); } } 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); } } public class Child: Parent { ENSICAEN - Spécialité Informatique 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); } } 26 ■ Différences en Java et C# 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"); } } Résultat : ENSICAEN - Spécialité Informatique 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 dérivée 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 dérivée 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 bâties sur le patron de conception Décorateur. On retrouve pratiquement les mêmes classes. Les exemples cidessous réalisent une copie du contenu d'un fichier appelé "input.txt" vers un autre fichier "output.txt". Code Java import java.io.*; 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(); } } Différences en Java et C# ■ 27 Code C# using System; using System.IO; 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(); } } 4.10 La Sérialisation Les objets sérialisables en C# sont marqués par l'attribut de Runtime [Serializable]. L'attribut [NonSerializable] 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 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 La persistance d'objets, connue aussi sous le terme sérialisation 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. 28 ■ Différences en Java et C# 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 sera 0 parce qu'il ne sera pas lu puisqu'il est 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 + "}"; } 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(); Différences en Java et C# ■ 29 SerializeTest fromdisk = (SerializeTest)bread.Deserialize(input); input.Close(); // x sera 0 parce qu'il ne sera pas lu puisqu'il est non-serialized 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 sera 0 parce qu'il ne sera pas lu puisqu'il est non-serialized 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); } } 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 analysé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. ► 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 classes dérivées ► Un classement hiérarchique, alphabétique de vos classes ENSICAEN - Spécialité Informatique 4.11 Génération de la documentation à partir du code source 30 ■ Différences en Java et C# ► Un index contenant la liste des méthodes et variables utilisées Code Java /** * 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, ...). Code C# ENSICAEN - Spécialité Informatique ///<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 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#, c'est l'option du compilateur /r qui 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 parent 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 ». Java traite la gestion d’événements avec un typage fort. Vous trouverez donc Différences en Java et C# ■ 31 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, ...) C# utilise les délégués (voir section 38) 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 de visibilité 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. 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 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); _number = number; } } interface EvenNumberSeenListener { void evenNumberSeen(EvenNumberEvent ene); } 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 l) { _subscribers.add(l); } public void removeEvenNumberEventListener(EvenNumberSeenListener l) { _subscribers.remove(l); } // genere 20 nombres aléatoires entre 1 et 20 et then genere un // evenement si le nomber est paire. public void RunNumbers() { Random r = new Random(System.currentTimeMillis()); ENSICAEN - Spécialité Informatique RemoveActionListener(). 32 ■ Différences en Java et C# for(int i=0; i < 20; i++) { int current = (int) r.nextInt() % 20; System.out.println("Current number is:" + current); if((current % 2) == 0) OnEvenNumberSeen(current); } } } 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(); pub.addEvenNumberEventListener(et); pub.RunNumbers(); pub.removeEvenNumberEventListener(et); } } ENSICAEN - Spécialité Informatique Code C# using System; class EvenNumberEvent: EventArgs { // Remarque: en pratique l'attribut serait une propriété internal int _number; public EvenNumberEvent(int number):base() { _number = number; } } class Publisher { 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)); } // genere 20 nombres aléatoires entre 1 et 20 et then genere un // evenement si le nomber est paire. 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); if((current % 2) == 0) OnEvenNumberSeen(current); } } } public class EventTest { public static void EventHandler(object sender, EventArgs e) { Console.WriteLine("\t\tEven Number Seen:" + ((EvenNumberEvent)e).number); Différences en Java et C# ■ 33 } public static void Main(string[] args) { Publisher p = new Publisher(); p.EvenNumHandler+=new Publisher.EvenNumberSeenHandler(EventHandler); pub.RunNumbers(); p.EvenNumHandler-=new Publisher.EvenNumberSeenHandler(EventHandler); } } 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++ compilé à l'aide de la méthode loadLibrary(). 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, …). 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ée. 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êmes 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. La CLR (Common Language Specification) 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 ENSICAEN - Spécialité Informatique 3/ Compiler la classe contenant la déclaration des méthodes natives. 34 ■ Différences en Java et C# 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#. 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 côté, 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 seul langage. Techniquement, il est tout à fait envisageable d'intégrer la compilation de code C#, C++ ou Eiffel en byte-code Java, mais Sun le veut-il vraiment ? 4.16 Attributs ENSICAEN - Spécialité Informatique 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 en C# : ► [MethodImpl(MethodImplOptions.Synchronized)] : synchronized de Java. Similaire au mot-clé ► [Serializable] : Similaire à l'implémentation de l'interface java.io.Serializable de Java. ► [WebMethod] : utilisé en combinaison avec ASP.NET permet de spécifier qu'une méthode est un web service. En Java, les Attributs sont implémentés par des annotations. Leur syntaxe utilise le métacaractère @. Les annotations les plus courantes sont : ► @Override : déclare qu'une méthode redéfinie une autre. ► @SuppressWarnings("unchecked") : pour supprimer les messages d'alerte du compilateur. ► Toutes les annotations utilisées par JUnit et tous les frameworks JEE. Code C# //declaration of bit field enumeration [Flags] enum ProgrammingLanguages{ C = 1, Lisp = 2, Différences en Java et C# ■ 35 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" if((aProgrammer.KnownLanguages & ProgrammingLanguages.C) > 0){ //if programmer knows C //.. do something } 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. Code C# 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; } } public string Email { get { return email; } } public string Author { get { return author; } } } [AuthorInfo("Dare Obasanjo", "[email protected]", Version="1.0")] ENSICAEN - Spécialité Informatique Les développeurs peuvent créer leurs propres attributs dans les deux langages. Par exemple, en XC# il faut dériver la classe System.Attribute.L'exemple suivant nous 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. 36 ■ Différences en Java et C# 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 } 4.17 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(). Cela est aussi possible en Java et en C#. ENSICAEN - Spécialité Informatique En Java, cette nouvelle fonctionnalité implique une nouvelle notation pour préciser la répétition d'un type d'argument. Cette nouvelle notation utilise trois petits points : ... public class TestVarargs { public static void main(String[] args) { System.out.println("valeur 1 = " + additionner(1,2,3)); System.out.println("valeur 2 = " + additionner(2,5,6,8,10)); } public static int additionner(int ... valeurs) { int total = 0; for (int val : valeurs) { total += val; } return total; } } 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# 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); } } Différences en Java et C# ■ 37 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. 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. 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. using System; using System.IO; 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) { // empeche plusieurs threads d'entrer ici en meme temps. if(disposing) { Console.WriteLine("Finalizing " + name); sw.Close(); GC.SuppressFinalize(this); disposed = true; } } } ENSICAEN - Spécialité Informatique Code C# 38 ■ Différences en Java et C# 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()); } } // appel de Dispose() de l'objet MyClass des que le bloc // de code est terminé, meme si une exception est levée } } 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. La libération déterministe d'objet est une avancée significative dans une prise en charge plus fine des ramasse-miettes. ENSICAEN - Spécialité Informatique 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 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 é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.2.1 Quel est l’intérêt d’utiliser un délégué ? 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 Différences en Java et C# ■ 39 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.2.2 Exemple 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(DonneeRecue); Le déclenchement du délégué consiste à l’appeler comme un simple appel de méthode lorsque c’est nécessaire. ENSICAEN - Spécialité Informatique 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. 40 ■ Différences en Java et C# // appel délégué pour signaler présence donnée recue if( dataReceivedHandler != null) { // appel si délégué est connecté dataReceivedHandler(data); } 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. ENSICAEN - Spécialité Informatique 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 du délégué pour signaler présence donnée recue if( dataReceivedHandler != null) dataReceivedHandler(data); // 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 Différences en Java et C# ■ 41 MessageBox.Show("donnée recue: "+data); } 5.3 Les types valeur (ValueTypes et Structs) 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 rapides 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 du temps. 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 finals (sealed). Comme l'héritage n'est pas géré dans les struct, 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# struct Point { public int x; public int y; 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); // La ligne suivante ne compilerait pas si Point était une classe. Point end = new Point(); Console.WriteLine("End: " + end); } } ENSICAEN - Spécialité Informatique Pour déclarer un type de valeur, il suffit 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. 42 ■ Différences en Java et C# 5.4 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. Code C# MyClass mc = o as MyClass; if(mc != null) { // test si la conversion est possible mc.doStuff(); } NOTE : L'opérateur as s'utilise aussi pour convertir des types de valeur. 5.5 Les propriétés ENSICAEN - Spécialité Informatique 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 et les mutateurs 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 illustre les différents types de propriétés : Code C# public class User { public User(string name) { this.name = name; } private string name; // propriete en lecture/écriture pour l'attribut name public string Name { get { return name; } } 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"); Différences en Java et C# ■ 43 User.MinimumAge = -5; // affiche une erreur (valeur est invalide) User.MinimumAge = 18; // newuser.Name = "Kevin Nash"; cause une erreur de compilation // puisque la propriété Name est en lecture seule Console.WriteLine("Minimum Age: " + User.MinimumAge); Console.WriteLine("Name: {0}", newuser.Name); } } L'inconvénient des propriétés C# est qu'elles peuvent mener à des situations pour le moins bizarres 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# 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 attraper. 5.6 Les 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, chaînes 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(); } // indexeur par le numéro public object this[int column] { get { return list[column]; } set { list[column] = value; ENSICAEN - Spécialité Informatique try { myClock.Hours = 28; // exception puisque 28 est une heure invalide myClock.Minutes = 15; myClock.Seconds = 39; } catch(InvalidTimeValueException itve) { // détetermine quel attribut est invalide et reporte l'erreur } 44 ■ Différences en Java et C# } } // indexeur par le nom public object this[string name] { get { return this[ConvertToInt(name)]; } set { this[ConvertToInt(name)] = value; } } ENSICAEN - Spécialité Informatique 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; } public IEnumerator GetEnumerator(){ return (IEnumerator) this; } private int index; public bool MoveNext() { index++; if(index >= list.Count) return false; else return true; } public void Reset() { index = -1; } 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]); 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); } } } Différences en Java et C# ■ 45 5.7 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# #define DEBUG /* #define must be first token in file */ using System; En Java, les directives de compilation peuvent être implémentées par les annotations. De plus, l'utilisation des directives de compilation pour introduire des messages de débogage est proscrite dans java. Il est lui préférée l'utilisation de test unitaire distinct du code notamment en utilisant le framework JUnit. 5.8 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.9 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 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 ENSICAEN - Spécialité Informatique class PreprocessorTest { public static void Main(string[] args) { #if DEBUG Console.WriteLine("DEBUG Mode := On"); #else Console.WriteLine("DEBUG Mode := Off"); #endif } } 46 ■ Différences en Java et C# 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.10 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. De plus, le garbage collector pouvant à tout moment ré-allouer des variables "managés", il est absolument nécessaire de figer ces variables le temps du traitement par le bloc unsafe. Cette opération est réalisée à l'aide du mot-clé "fixed". Code C# ENSICAEN - Spécialité Informatique 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.11 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 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 Différences en Java et C# ■ 47 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 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 + ", b } } OUTPUT a := 10, b := 5, s = Changed s) { int y) { { := " + b + ", s = " + s); 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); } } 48 ■ Différences en Java et C# 5.12 Les caractères spéciaux 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 @. Code C# ENSICAEN - Spécialité Informatique 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.13 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 motsclés checked et unchecked au début de blocs. Code C# using System; class CheckedTest { public static void Main() { int num = 5000; /* DEBORDEMENT I */ byte a = (byte) num; // débordement detecté si l'option /checked // du compilateur est poisitionnée /* DEBORDEMENT II */ checked { byte b = (byte) num; /* débordement TOUJOURS détecté */ } /* DEBORDEMENT III */ unchecked { byte c = (byte) num; /* débordement JAMAIS détecté */ } } } Différences en Java et C# ■ 49 5.14 L'implémentation explicite d'interface 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; ENSICAEN - Spécialité Informatique interface Ivehicle { //identifie le vehicle par le modele, la marque, et l'année void IdentifySelf(); } interface Irobot { //identifie le robot par son nom 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; } 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(); ERREUR IVehicle v = (IVehicle) tr; IRobot r = (IRobot) tr; v.IdentifySelf(); r.IdentifySelf(); } } 50 ■ Différences en Java et C# OUTPUT Model:Toyota Make:Corolla Year:2001 My name is SedanBot 6. Concepts avancés 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. À 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 ENSICAEN - Spécialité Informatique 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 paquets 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. 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"; } } La méthode distante obtainName() dans la classe ci-dessus accepte des types qui Différences en Java et C# ■ 51 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. 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 comme alternative aux énumérations notamment dans des interfaces utilisées entre équipes de développement différentes et ainsi regrouper toutes les informations dans l'interface. 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 ) L'exemple qui suit illustre l'implémentation du patron de conception État (automate à états finis) en utilisant des classes anonymes Code Java // Une 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); cv.menuLogin.setEnabled(true); cv.menuExit.setEnabled(true); //can't type cv.textArea.setEnabled(false); } public String toString(){ return "ClientState: NOT_LOGGED_IN"; } }; // 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); ENSICAEN - Spécialité Informatique 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. 52 ■ Différences en Java et C# 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 is used to signify that the user is editing 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); cv.textArea.setEnabled(true); } public String toString() { return "ClientState:EDITTING_DOCUMENT"; } }; ENSICAEN - Spécialité Informatique // 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); } Voici un exemple de code utilisant la classe ClientState définie ci-dessus : 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; } 6.6 La gestion de version 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 affres du déploiement d'applications Web (JSP et servlets) référençant de multiples versions de parseur XML (xerces) ou de bibliothèque ant.jar. Cela est dû au fait que les ClassLoaders Java fonctionnent sur le principe de premier Différences en Java et C# ■ 53 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 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 a développé aussi des solutions pour ces lacunes. Aujourd'hui peu de choses diffèrent entre 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 le projet Open-Source Mono a pour objectif de porter C# sur Linux et MacOS mais les performances restent très médiocres. De plus, la machine virtuelle « HotSpot » développée par Sun est la machine virtuelle la plus puissante sur le marché et avec une version 7 qui accroît encore ses performances. Le langage C# pour sa part a repris d'autres langages des concepts intéressants notamment autour de : ► 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 compter dans le monde du développement logiciel. La plupart des développeurs, spécialement ceux possédant une culture 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 ou les délégués. D'un autre côté, 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. ENSICAEN - Spécialité Informatique ► Un mécanisme de gestion de version de classes avec la possibilité d'avoir plusieurs versions différentes de classes chargées en même temps en mémoire. 54 ■ Différences en Java et C# Révision Version 0.1 0.2 Date Commentaire Basé sur l'article de Dare Obasanjo (traduction de Alain Vizzini 23 dec, 2012 http://alain.vizzini.free.fr/) 3 mar, 2014 Révision. ENSICAEN - Spécialité Informatique