C# versus Java

Introduction

Combien de fois n'a t-on pas entendu les questions ou réflexions suivantes : "mais qu'est-ce que ça fait de plus que Java, ce C# ? " ou encore "C# ? Ah bah oui, c'est le Java propriétaire de Microsoft pour contrer Sun" et combien de fois n'avez vous pas eu envie d'expliquer ou de convaincre à ces personnes, au demeurant de bonne foi, que C# méritait plus d'estime car il possédait de multiples facettes et qualités souvent trop longues à énumérer ou à résumer au détour d'un forum de discussions. D'ailleurs, dans la majeure partie des cas, ce dédain face à un nouveau langage, qui plus est développé par Microsoft (quel sacrilège ;-)), n'est qu'un moyen de dissimuler une certaine forme d'ignorance.

Cet article intitulé "C# versus Java" se propose de vous faire connaître C# à travers un ensemble de caractéristiques communes ou différentes de Java (Exceptions, Héritages, ...) avec un regard serein et objectif. Devant la multitude de sujets proposés, nous avons essayé de regrouper les fonctionnalités qui présentaient des similitudes, celles qui différaient de part leur implémentation et enfin, celles qui avaient un caractère novateur.

La plupart des exemples ont été testés avec  Microsoft .NET Framework beta 2 et Java 2 Standard Edition.

L'auteur de l'article original est Dare Obasanjo qui a eu la gentillesse de nous autoriser à enrichir et à traduire ses écrits. Certaines parties ont été totalement revisitées par nos soins pour plus de clarté et de simplicité mais en aucun cas le sens et la teneur de l'article original n'ont été modifiés. Nous espérons qu'après avoir lu ces quelques pages vous serez plus à même de comprendre ce qu'est C#, mais aussi d'analyser ou de mieux juger par vous même ses multiples caractéristiques et fonctionnalités.  

 

Index Rapide

  1. Plus les choses évoluent plus elles tendent à se ressembler
    Cette section décrit les concepts et les caractéristiques très proches de C# et Java
    1. Nous sommes tous des objets
    2. Synthèse des mots-clés
    3. A propos des machines virtuelles
    4. Gestion de la mémoire (ramasse miettes)
    5. Les tableaux
    6. Pas de méthodes globales
    7. Les interfaces, Oui. L'héritage multiple, Non.
    8. Les chaînes de caractères ne peuvent être modifiées
    9. Les classes non extensibles
    10. Levée et Capture d'Exceptions
    11. Initialisation de membres et constructeurs statiques
  2. La même chose mais en différent
    Cette section décrit les concepts et caractéristiques des langages qui diffèrent soit au niveau de la syntaxe soit dans la façon d'être implémentés.
    1. La méthode Main
    2. La syntaxe de l'héritage  
    3. L'opérateur d'identification de type  (is operator)
    4. Les Namespaces ou packages
    5. Constructeurs, Destructeurs et Finaliseurs
    6. Synchronisation de méthodes
    7. Accessibilité à la classe
    8. La Reflection
    9. Déclaration de constantes
    10. Les types primitifs
    11. La déclaration de tableaux
    12. Chaînage et appel du constructeur père
  3. Comme un air de "Déjà Vu
    Cette section décrit les concepts et les caractéristiques de C# existants dans JAVA mais avec une importante différence au niveau de leur implémentation.
    1. Les classes imbriquées
    2. Les threads et les membres volatiles
    3. La surcharge d'opérateurs
    4. L'instruction "switch"
    5. Les Assemblies
    6. Collections
    7. Goto (plus considéré comme dangereux)
    8. Les méthodes virtuelles (et finales)
    9. Fichiers Entrée/Sortie
    10. La sérialisation
    11. La génération de la documentation à partir du code source
    12. Plusieurs classes dans un même fichier
    13. L'import de bibliothèques
    14. Les évènements
    15. Intéropérabilité inter-langages
  4. Maintenant, des choses totalement différentes
    Cette section décrit des fonctionnalités existantes dans C# n'ayant aucun équivalent en Java.
    1. La libération déterministe d'objets
    2. L'ordre Delegate
    3. Enumérations
    4. Les types valeur
    5. Le Boxing et Unboxing
    6. Identification de type dynamique (operator "as")
    7. L'instruction "for each"
    8. Les propriétés
    9. Les attributs
    10. Les indexeurs
    11. Les directives de pré-processing
    12. Les aliases
    13. Génération de code dynamique
    14. Pointeur et code non protégé(unsafe)
    15. Passage par référence
    16. Les listes variables de paramètres
    17. Les caractères spéciaux
    18. Détection de débordement
    19. Implémentation explicite d'interface
  5. Concepts avancés
    Cette section décrit les concepts avancés de C# et Java
    1. La portabilité multi-plateformes (Write Once, Run Anywhere)
    2. Les extensions
    3. Le chargement dynamique de classe
    4. Les interfaces contenant des champs
    5. Les classes anonymes
    6. Le versionning des Assemblies
  1. Conclusion
  2. Ressources
  3. Remerciements

q       Plus les choses évoluent plus elles tendent à se ressembler

1.     Nous sommes tous des objets !

Comme Java, C# possède une super classe, mère de tous les objets : System.Object. La classe équivalent s'appelle java.lang.object. Les méthodes présentent dans ces deux classes sont très similaires (ex: toString()) excepté wait(), notify() et les autres méthodes liées à la synchronisation.

NOTE: En C#, une classe de type objet peut soit être écrite sous la forme "object" en minuscule ou "Object". En fait, à la compilation ces deux types sont remplacés par System.Object.

2.     Synthèse des mots-clés

Il y a énormément de similitudes entre les deux langages, presque tous les mots-clés Java ont un équivalent en C# à part quelques exceptions telles que transient, throws et strictfp. La table ci-dessous vous illustre les mots-clés présents des deux cotés

mot-clé C#

mot-clé Java

mot-clé C#

mot-clé Java

mot-clé C#

mot-clé Java

mot-clé C#

mot-clé Java

abstract

abstract

explicit

N/A

object

N/A

this

this

as

N/A

extern

native

operator

N/A

throw

throw

base

super

finally

finally

out

N/A

true

true

bool

boolean

fixed

N/A

override

N/A

try

try

break

break

float

float

params

N/A

typeof

N/A

byte

N/A

for

for

private

private

uint

N/A

case

case

foreach

N/A

protected

N/A

ulong

N/A

catch

catch

get

N/A

public

public

unchecked

N/A

char

char

goto

goto1

readonly

N/A

unsafe

N/A

checked

N/A

if

if

ref

N/A

ushort

N/A

class

class

implicit

N/A

return

return

using

import

const

const1

in

N/A

sbyte

byte

value

N/A

continue

continue

int

int

sealed

final

virtual

N/A

decimal

N/A

interface

interface

set

N/A

void

void

default

default

internal

protected

short

short

while

while

delegate

N/A

is

instanceof

sizeof

N/A

:

extends

do

do

lock

synchronized

stackalloc

N/A

:

implements

double

double

long

long

static

static

N/A

strictfp

else

else

namespace

package

string

N/A

N/A

throws

enum

N/A

new

new

struct

N/A

N/A

transient

event

N/A

null

null

switch

switch

N/A

volatile

3.     A propos des machines virtuelles

De la même manière que Java est compilé en byte-code et s'exécute dans un environnement d'exécution managé (Machine Virtuelle JVM), C# est compilé en MSIL s'exécutant dans la CRL (Common Langage Runtime). Les deux plate-formes supportent la compilation Just In Time (JIT). Toutefois, il existe une légère différence entre les deux plate-formes, les compilateurs Java permettent de désactiver totalement le JIT en fonctionnant uniquement en mode interprété alors que les compilateurs .NET en général intègre nativement le JIT. Enfin, il existe des deux cotés la possibilité de pré-compiler en code natif le source. .

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 de valeurs (ValueType).

Le ramasse miettes en C# implémente un algorithme bien connu appelé Mark and Compact garbage collection algorithm.

5.     Les tableaux

Dans les langages tels que C ou C++, la dimension de chaque sous tableau doit être identique dans les tableaux à plusieurs dimensions. En Java et C#, les tableaux n'ont pas à se plier à cette contrainte car ils peuvent être créés comme des tableaux à une dimension référençant d'autres tableaux. Ils sont appelés "Jagged Arrays". Pour cette raison, les tailles initiales des lignes et colonnes de ce type de tableaux peuvent-être différentes. C'est ce que montre le code suivant :

int [][]myArray = new int[2][];

myArray[0] = new int[3];

myArray[1] = new int[9];

 

Le code précédent est valide pour Java et C#.

6.     Pas de méthodes globales

Comme en Java et contrairement à C++, les méthodes en C# doivent être intégrées dans une classe.

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.

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 ô combien fondamental.

C# Code

string csString = "Apple Jack";

csString.ToLower(); /* Does not modify string, instead returns lower case copy of string */

 
Java Code

String jString = "Grapes";

jString.toLowerCase(); /* Does not modify string, instead returns lower case copy of string */

 

Pour créer une chaîne de caractères autorisant la modification avec la même référence, il est conseillé d'utiliser  les classes System.Text.StringBuilder pour C# et java.lang.StringBuffer pour Java.


NOTE: En C#, la classe chaîne peut être écrite sous la forme string ou String.

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#, vous utilisez le mot-clé sealed  et en Java le mot-clé final.

C# Code

sealed class Student

{

         string fname;

         string lname;

         int uid;

         void attendClass() {}

}

 

Java Code

final class Student

{

    String fname;

    String lname;

    int uid;

    void attendClass() {}

}

 

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.

NOTE: Cependant, il existe une différence fondamentale entre C# et Java. Le mot-clé throws n'existe pas en C# car vous n'êtes pas contraint de spécifier dans la signature d'une méthode le fait qu'elle est susceptible de lever une exception. Il n'y a, contrairement à Java, aucune vérification de faite à l'exécution.

C# Code

using System;

using System.IO;

 

class MyException: Exception

{

  public MyException(string message): base(message){ }

  public MyException(string message, Exception innerException):

         base(message, innerException){ }

}

 

public class ExceptionTest

{

  static void DoStuff()

  { 

         throw new FileNotFoundException(); 

  }

  public static int Main()

  {

         try

         {           

                 try

                 {       

                         DoStuff();

                         return 0;  //won't get to execute

                 }

                 catch(IOException ioe)

                 { /* parent of FileNotFoundException */

                         throw new MyException("MyException occured", ioe);
                    /* rethrow new exception with inner exception specified */

                 }

         }

         finally

         {

                 Console.WriteLine("***Finally block executes even though MyException not caught***");

         }

  }//Main(string[])

} // ExceptionTest

 
Java Code

class MyException extends Exception{

    public MyException(String message){ super(message); }

    public MyException(String message, Exception innerException){ super(message, innerException); }

}

 

public class ExceptionTest {

    static void doStuff(){ 

    throw new ArithmeticException();   

    }

 

    public static void main(String[] args) throws Exception{

    try{           

        try{       

        doStuff();

        return;  //won't get to execute

        }catch(RuntimeException re){ /* parent of ArithmeticException */

        throw new MyException("MyException occured", re); /* rethrow new exception with cause specified */

        }

    }finally{

        System.out.println("***Finally block executes even though MyException not caught***");

    }

}//main(string[])

} // ExceptionTest

 

11.                       Initialisation de membres et constructeurs statiques

Les variables d'instance et les variables statiques peuvent être initialisées dès leur déclaration, et ce, dans les deux langages. Si la variable membre est une variable d'instance, alors l'initialisation sera effectuée juste avant l'appel du constructeur. Les membres statiques, eux,  sont initialisés dès le chargement de la classe par le Runtime. Cela intervient en règle générale avant l'appel du constructeur. Il est aussi possible de définir de part et d'autre des blocs statiques qui seront exécutés au chargement. Il sont appelés plus communément constructeurs statiques.

C# Code

using System;

 

class StaticInitTest

{

  string instMember = InitInstance();

  string staMember = InitStatic();

 

  StaticInitTest()

  {

         Console.WriteLine("In instance constructor");

  }

 

  static StaticInitTest()

  {

         Console.WriteLine("In static constructor");

  }

 

  static String InitInstance()

  {

         Console.WriteLine("Initializing instance variable");

         return "instance";

  }

 

  static String InitStatic()

  {

         Console.WriteLine("Initializing static variable");

         return "static";

  }

 

  static void DoStuff()

  {

         Console.WriteLine("Invoking static DoStuff() method");

  }

 

  public static void Main(string[] args)

  {

         Console.WriteLine("Beginning main()");

         StaticInitTest.DoStuff();

         StaticInitTest sti = new StaticInitTest();

         Console.WriteLine("Completed main()");

  }

  }
 
Java Code

class StaticInitTest

{

         String instMember = initInstance();

         String staMember = initStatic();

         StaticInitTest()

         {

                 System.out.println("In instance constructor");

         }

         static

         {

                 System.out.println("In static constructor");

         }

    static String initInstance()

    {

           System.out.println("Initializing instance variable");

   return "instance";

    }

    static String initStatic()

    {

           System.out.println("Initializing static variable");

           return "static";

    }

    static void doStuff()

    {

           System.out.println("Invoking static DoStuff() method");

    }

    public static void main(String[] args)

    {

           System.out.println("Beginning main()");

           StaticInitTest.doStuff();

           StaticInitTest sti = new StaticInitTest();

           System.out.println("Completed main()");

    }

}
 
Exécution des deux exemples : 
In static constructor
Beginning main()
Invoking static DoStuff() method
Initializing instance variable
Initializing static variable
In instance constructor
Completed main()
 

q       La même chose mais en différent

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 status alors que celui de Java impose une signature bien précise et retourne void.

C# Code

using System;

 

class A

{

  public static void Main(String[] args)

  {

         Console.WriteLine("Hello World");

  }

}

 
Java Code

class B

{

  public static void main(String[] args)

  {

         System.out.println("Hello World");

  }

}

Il est généralement recommandé d'insérer dans chaque classe une méthode Main() permettant tester la classe de manière unitaire indépendamment de la méthode Main() générale.

Ainsi, il est possible d'avoir deux classes, A et B, contenant toutes deux une méthode Main(). En Java, pour spécifier la méthode Main appelée, il suffit de spécifier le nom de la classe en ligne de commande ou à travers un outil. En C#, le problème est légèrement différent car le processus de compilation génère un exécutable pouvant contenir plusieurs Main(). C'est pourquoi, un paramètre de compilation précisera la méthode par défaut qui sera appelée lors de l'exécution (/main). L'utilisation de ces techniques associées à une compilation conditionnelle via les directives de pré-processing vous procure une grande souplesse dans les tests.

Java Exemple
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
 
C# Exemple
C:\CodeSample> csc /main:A /out:example.exe A.cs B.cs 
 
C:\CodeSample> example.exe
 Hello World from class A
 
C:\CodeSample> csc /main:B /out:example.exe A.cs B.cs 
 
C:\CodeSample> example.exe
 Hello World from class B
 

Ainsi, en Java, contrairement à C#, il n'est pas nécessaire de recompiler les classes pour changer le point d'entrée du programme. D'un autre coté, en C#, les directives de compilation permettent de produire du code paramétrable pour la phase de test ou de release.

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.

C# Code

using System;

 

class B:A, IComparable

{

         int CompareTo(){}

  public static void Main(String[] args)

  {

         Console.WriteLine("Hello World");

  }

}

 
Java Code

class B extends A implements Comparable

{

int compareTo(){}

  public static void main(String[] args)

  {

         System.out.println("Hello World");

  }

}

Les développeurs Java pourront toujours argumenter que cela rend les sources difficilement lisibles car le mot-clé indique si la classe mère est une interface ou pas. Dans la pratique, ce n'est pas vraiment un problème dans la mesure où Microsoft définit clairement que les noms d'interfaces doivent commencer par IMyInterface (tel que ICloneable).

3.     L'opérateur d'indentification de type (is operator)

En C#, l'opérateur is est totalement analogue au mot-clé instanceof de Java. Les deux bouts de code suivant sont équivalent :

C# Code

if(x is MyClass)

MyClass mc = (MyClass) x;

 
Java Code

if(x instanceof MyClass)

MyClass mc = (MyClass) x;

 

4.     Les Namespaces ou packages

Un Namespace C# est une manière de regrouper des classes par domaine et s'utilise de manière similaire par rapport au mot-clé package en Java. Les développeurs C++ noteront que la syntaxe des namespaces en C# est très proche de C++. Toutefois, en Java, la structure arborescente des packages dicte la structure des sources sous-jacentes, ce qui n'est pas le cas de C#.

Examples :

C# Code

namespace com.carnage4life

{

  public class MyClass

  {

         int x;

         void doStuff(){}

  }

}

 
Java Code

package com.carnage4life;

 

public class MyClass

{

  int x;

  void doStuff(){}

}

 

Enfin, la syntaxe C#  autorise la multiplicité des déclarations de namespaces au sein d'un même fichier.

C# Code

using System;

 

namespace Company

{

  public class MyClass

  { /* Company.MyClass */

         int x;

         void doStuff(){}

  }

 

  namespace Carnage4life

  {

         public class MyOtherClass

         {  /* Company.Carnage4life.MyOtherClass */

                 int y;

                 void doOtherStuff(){}

                 public static void Main(string[] args)

                 {

                         Console.WriteLine("Hey, I can nest namespaces");

                 }

         }// class MyOtherClass

  }// namespace Carnage4life

}// namespace Company

 

 

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.finalise()).

C# Code

using System;

 

public class MyClass

{

  static int num_created = 0;

  int i = 0;   

 

  MyClass()

  { 

         i = ++num_created;

         Console.WriteLine("Created object #" + i);   

  }

 

  ~MyClass()

  {

         Console.WriteLine("Object #" + i + " is being finalized"); 

  }

 

  public static void Main(string[] args)

  {

    for(int i=0; i < 10000; i++)

                 new MyClass();

         }

}

 
Java Code

public class MyClass

{

  static int num_created = 0;

  int i = 0;   

 

  MyClass()

  { 

         i = ++num_created;

         System.out.println("Created object #" + i);   

  }

 

  public void finalize()

  {

         System.out.println("Object #" + i + " is being finalized"); 

  }

 

  public static void main(String[] args)

  {

         for(int i=0; i < 10000; i++)

                 new MyClass();

  }

}

 

6.     Synchronisation de méthodes

En Java, il est possible de spécifier explicitement la synchronisation d'un bloc afin d'éviter que deux threads modifient simultanément la même section critique. C# fournit un mot-clé lock qui est sémantiquement l'équivalent du synchronized en Java.

C# Code

public void WithdrawAmount(int num)

{

lock(this)

{

if(num < this.amount)

this.amount -= num;

}

}

 
Java Code

public void withdrawAmount(int num)

{

synchronized(this)

{

if(num < this.amount)

this.amount -= num;

}

}
 

C# et Java supportent tous les deux les méthodes synchronisées. Lorsqu'une méthode synchronisée est appelée, le premier thread prend la main sur le moniteur d'objets et bloque l'accès aux autres threads le temps de l'exécution de la méthode. En Java, les méthodes synchronisées sont précédées du mot-clé synchronized. En C#, il existe plusieurs manières d'implémenter la synchronisation de méthodes : par Attributs de Runtime (MethodImplOptions.Synchronized) ou par utilisation du mot-clé Interlocked.

C# Code

using System;

using System.Runtime.CompilerServices;

 

public class BankAccount

{

  [MethodImpl(MethodImplOptions.Synchronized)]

  public void WithdrawAmount(int num)

  {

         if(num < this.amount)

                 this.amount - num;

  }

}//BankAccount
 
Java Code

public class BankAccount

{

  public synchronized void withdrawAmount(int num)

  {

         if(num < this.amount)

         this.amount - num;

  }

}//BankAccount

7.     Accessibilité à la classe

La table ci-dessous vous synthétise les différents mot-clés permettant de modifier la visibilité et l'accès à une classe dans le but de protéger l'encapsulation. Les fans de C++ déçus lorsque Sun a modifié la portée de l'instruction protected seront heureux de noter que C# garde la même sémantique que C++. Cela signifie donc qu'un membre "protected" ne peut être accédé que par d'autres méthodes membres situées dans la même classe ou dans une classe dérivée mais n'est en aucun cas visible de l'extérieur (même package ou non).

Le mot-clé internal signifie que la fonction membre ne peut être accédée que par d'autres classes situées dans la même assembly. Associée à protected, la visibilité est étendue aux classes dérivées.

C# access modifier

Java access modifier

private

private

public

public

internal

protected

protected

N/A

internal protected

N/A

NOTE: La visibilité par défaut d'un champ ou d'une méthode en C# est private alors qu'en Java elle est  protected.

8.     La Reflection

La capacité à découvrir les méthodes et champs dans une classe et à invoquer dynamiquement des méthodes à l'exécution est appelé la reflection. Cette caractéristique existe aussi bien en Java qu'en C# à la différence près que la reflection s'effectue au niveau d'une assembly en C# et au niveau d'une classe en Java.

C# Code

using System;

using System.Xml;

using System.Reflection;

using System.IO;

 

class ReflectionSample

{

  public static void Main( string[] args)

  {

         Assembly assembly=null;

         Type type=null;

         XmlDocument doc=null;

         try

         {

                 // Load the requested assembly and get the requested type

                 assembly = Assembly.LoadFrom("C:\\WINNT\\Microsoft.NET\\Framework\\v1.0.2914\\System.XML.dll");

                 type = assembly.GetType("System.Xml.XmlDocument", true);

 

                 //Unfortunately one cannot dynamically instantiate types via the Type object in C#.

                 doc  =  Activator.CreateInstance("System.Xml","System.Xml.XmlDocument").Unwrap() as XmlDocument;

                 if(doc != null)

                         Console.WriteLine(doc.GetType() + " was created at runtime");

                 else

                         Console.WriteLine("Could not dynamically create object at runtime");

         }

         catch(FileNotFoundException)

         {

                 Console.WriteLine("Could not load Assembly: system.xml.dll");

                 return;

         }

         catch(TypeLoadException)

         {

                 Console.WriteLine("Could not load Type: System.Xml.XmlDocument from assembly: system.xml.dll");

                 return;

         }

         catch(MissingMethodException)

         {

                 Console.WriteLine("Cannot find default constructor of " + type);

         }

         catch(MemberAccessException)

         {

                 Console.WriteLine("Could not create new XmlDocument instance");

         }

         // Get the methods from the type

         MethodInfo[] methods = type.GetMethods();

         //print the method signatures and parameters

         for(int i=0; i < methods.Length; i++)

         {

                 Console.WriteLine ("{0}", methods[i]);

                 ParameterInfo[] parameters = methods[i].GetParameters();

                 for(int j=0; j < parameters.Length; j++)

                 {  

                         Console.WriteLine (" Parameter: {0} {1}", parameters[j].ParameterType, parameters[j].Name);

                 }

         }//for (int i...)

  }

}

 
Java Code

import java.lang.reflect.*;

import org.w3c.dom.*;

import javax.xml.parsers.*;

 

class ReflectionTest

{

  public static void main(String[] args)

  {

         Class c=null;

         Document d;

         try

         {

                 c =  DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().getClass();

                 d =  (Document) c.newInstance();

                 System.out.println(d + " was created at runtime from its Class object");

         }

         catch(ParserConfigurationException pce)

         {

                 System.out.println("No document builder exists that can satisfy the requested configuration");

         }

         catch(InstantiationException ie)

         {

                 System.out.println("Could not create new Document instance");

         }

         catch(IllegalAccessException iae)

         {

                 System.out.println("Cannot access default constructor of " + c);

         }

         // Get the methods from the class

         Method[] methods = c.getMethods();

         //print the method signatures and parameters

         for (int i = 0; i < methods.length; i++)

         {

                 System.out.println( methods[i]);

                 Class[] parameters = methods[i].getParameterTypes();

                 for (int j = 0; j < parameters.length; j++)

                 {        

                         System.out.println("Parameters: " +  parameters[j].getName());

                 }

         }

  }

}

A la vue du code précédent, vous aurez remarqué que la granularité de la reflection est plus grande en C# qu'en Java. Quelque fois, vous avez besoin d'obtenir les méta-données d'une classe spécifique encapsulée sous la forme d'un objet. Cet objet est du type java.lang.Class en Java et System.Type en C#. Ainsi, pour récupérer une instance de cette classe, vous utilisez getClass() en Java et GetType() en C#.

C# Code
Type t = typeof(ArrayList);
 
Java Code
Class c = java.util.Arraylist.class; /* Must append ".class" to fullname of class */
 

9.     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 la compilation et readonly à l'exécution. const a toujours été demandé par la communauté de développeurs Java mais n'a jamais vraiment eu d'écho de la part de Sun. Gageons que la prochaine version du JDK intègrera ce mot-clé au langage car, à l'heure actuelle, le développeur est contraint de jongler avec le clonage d'objets et le masquage par interface afin d'éviter de briser l'encapsulation.

Contrairement à C++, il n'est possible ni en Java ni en C# de spécifier une classe immuable (équivalent du const MyClass* p).

C# Code

using System;

 

public class ConstantTest

{

         /* Compile time constants */

  const int i1 = 10;   //implicitly a static variable

         // code below won't compile because of 'static' keyword

  // public static const int i2 = 20;

  /* run time constants */

public static readonly uint l1 =  (uint) DateTime.Now.Ticks;

         /* object reference as constant */

  readonly Object o = new Object();

  /* uninitialized readonly variable */

  readonly float f;

  ConstantTest()

  {

         // unitialized readonly variable must be initialized in constructor

         f = 17.21f;

  }

}

 
Java Code

import java.util.*;

 

public class ConstantTest

{

  /* Compile time constants */

  final int i1 = 10;   //instance variable

  static final int i2 = 20; //class variable

  /* run time constants */

  public static final long l1 = new Date().getTime();

  /* object reference as constant */

  final Vector v = new Vector();

  /* uninitialized final */

  final float f;

  ConstantTest()

  {

         // unitialized final variable must be initialized in constructor

         f = 17.21f;

  }

}

NOTE : Le langage Java supporte aussi les paramètres de méthodes précédés du mot-clé final. Cela permet aux classes internes déclarées dans la méthode d'accéder aux paramètres en question. Cette fonctionnalité n'existe pas en C# .

10.                       Les types primitifs

Pour chaque type primitif en Java, il existe un correspondant en C# avec le même nom, excepté byte. Le byte  en Java est signé et est ainsi proche du type sbyte  de C# (et non byte). De plus, C# possède des versions non signées pour la plupart des types : ulong, uint, ushort et byte. La seule différence majeure provient du type decimal qui n'effectue aucun arrondi au prix d'un surplus de place et d'une rapidité de traitement moindre.

Ci-dessous, plusieurs manières d'implémenter des valeurs réelles en C#.

C# Code

decimal dec = 100.44m; //m is the suffix used to specify decimal numbers

double  dbl = 1.44e2d; //e is used to specify exponential notation while d is the suffix used for doubles

 

11.                       La déclaration de tableaux

Java possède deux façons de déclararer un tableau : la première, créé pour la compatibilité avec C et C++ et la deuxième plus simple et plus claire. C# utilise, quant à lui, la deuxième façon.

C# Code

int[] iArray = new int[100]; //valid, iArray is an object of type int[]

float fArray[] = new float[100]; //ERROR: Won't compile 

 
Java Code

int[] iArray = new int[100]; //valid, iArray is an object of type int[]

float fArray[] = new float[100]; //valid, but isn't clear that fArray is an object of type float[]

 

12.                       Chaînage et appel du constructeur père

C# et Java appelle implicitement les constructeurs père en cas de création d'un objet. Les deux langages proposent un moyen d'appeler explicitement le constructeur père en lui passant des paramètres spécifiques. De plus, C# et Java assurent que l'appel des constructeurs se fait dans un ordre bien précis assurant ainsi l'impossibilité d'accéder à des variables non encore initialisées. Enfin les deux langages intègrent la surcharge de constructeur et l'appel inter-constructeur afin de réutiliser du code existant. Ce procédé est aussi appelé "chaînage de constructeurs".

C# Code

using System;

 

class MyException: Exception

{

  private int Id;

  public MyException(string message): this(message, null, 100){ }

  public MyException(string message, Exception innerException):

         this(message, innerException, 100){ }

  public MyException(string message, Exception innerException, int id):

         base(message, innerException)

  {

         this.Id = id;  

  }

}

  
Java Code

class MyException extends Exception

{

private int Id;

         public MyException(String message)

  {

                 this(message, null, 100);

  }

  public MyException(String message, Exception innerException)

  {

         this(message, innerException, 100);

}

  public MyException( String message,Exception innerException, int id)

  {

         super(message, innerException);

         this.Id = id;    

  }

}

 

q       Comme un air de "Déja Vu"

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 et ne peut posséder de méthodes statiques.

C# possède l'équivalent des classes statiques Java imbriquées mais n'intègre rien d'équivalent aux classes internes de Java. Le code suivant nous montre un exemple de classe imbriquée en C# et Java.

C# Code

public class Car

{

  private Engine engine;

  private class Engine

  {

         string make;

  }

}

 
Java Code

public class Car

{

  private Engine engine;

  private static class Engine

  {

         String make;

  }

}

NOTE : En Java, une classe imbriquée peut être déclarée dans n'importe quels types de bloc, ce qui n'est pas le cas de C#. La possibilité de créer des classes imbriquées à l'intérieur de méthodes peut sembler inutile mais combinée aux classes anonymes, cela peut conduire à concevoir de puissantes applications basées sur les Design Pattern.

2.     Les threads et les membres volatiles

Les Threads en Java sont créés par dérivation de la classe java.lang.Thread et en re-définissant la méthode run() ou par implémentation directe de l'interface the java.lang.Runnable. Vous avez le choix entre l'héritage (historique) et la délégation (plus sûre).

En C#, la création de Threads se fait à l'aide de l'opération new System.Threading.Thread en passant en paramètre un délégué (System.Threading.ThreadStart) qui n'est autre qu'un pointeur vers la méthode destinée à être exécutée. Contrairement à Java qui impose comme point d'exécution d'un Tread la fonction run(), il est possible en C# de spécifier n'importe quelle fonction

En Java, toutes les classes héritent des méthodes wait(), notify() and notifyAll() situées dans java.lang.Object. Les méthodes équivalentes en C# sont Wait(), Pulse() and PulseAll() se trouvant dans la classe System.Threading.Monitor.

L'example ci-dessous nous déroule le scénario dans lequel des threads sont lancés dans un ordre bien défini et doivent être traités dans le même ordre. Comme l'exécution des threads est par nature non-déterministe, ceux qui arriveront après les autres devront attendre leur tour à l'aide de la méthode wait().

C# Code

using System;

using System.Threading;

using System.Collections;

 

public class WorkerThread

{

  private int idNumber;

  private static int num_threads_made = 1;

  private ThreadSample owner;

 

  public WorkerThread(ThreadSample owner)

  {

         idNumber = num_threads_made;   

         num_threads_made++;       

         this.owner = owner;

  }/* WorkerThread() */

   

  //sleeps for a random amount of time to simulate working on a task

  public void PerformTask()

  {

         Random r = new Random((int) DateTime.Now.Ticks);

         int timeout = (int) r.Next() % 1000;

         if(timeout < 0)

                 timeout *= -1;

         //Console.WriteLine(idNumber + ":A");

    try

         {       

                 Thread.Sleep(timeout);     

         }

         catch (ThreadInterruptedException e)

         {

                 Console.WriteLine("Thread #" + idNumber + " interrupted");

         }

    //Console.WriteLine(idNumber + ":B");

         owner.workCompleted(this);

  }/* performTask() */

  public int getIDNumber() {return idNumber;}

} // WorkerThread

 

public class ThreadSample

{

  private static Mutex m = new Mutex();

  private ArrayList threadOrderList = new ArrayList();

  private int NextInLine()

  {

         return (int) threadOrderList[0];

  }

  private void RemoveNextInLine()

  {

         threadOrderList.RemoveAt(0);

         //all threads have shown up

         if(threadOrderList.Count == 0)

                 Environment.Exit(0);

  }

  public void workCompleted(WorkerThread worker)

  {

         try

         {

                 lock(this)

                 {

                         while(worker.getIDNumber() != NextInLine())

                         {

                                try

                                {

                            //wait for some other thread to finish working

                                        Console.WriteLine ("Thread #" + worker.getIDNumber() +

" is waiting for Thread #" +

                                                               NextInLine() + " to show up.");

                                        Monitor.Wait(this, Timeout.Infinite);

                                }

                                catch (ThreadInterruptedException e) {}

                         }//while

                         Console.WriteLine("Thread #" + worker.getIDNumber() + " is home free");

                         //remove this ID number from the list of threads yet to be seen

                         RemoveNextInLine();

                         //tell the other threads to resume

                         Monitor.PulseAll(this);

                 }

         }

         catch(SynchronizationLockException){Console.WriteLine("SynchronizationLockException occurred");}

  }

  public static void Main(String[] args)

  {

         ThreadSample ts = new ThreadSample();

         /* Launch 25 threads */

         for(int i=1; i <= 25; i++)

         {

                 WorkerThread wt = new WorkerThread(ts);

                 ts.threadOrderList.Add(i);

                 Thread t = new Thread(new ThreadStart(wt.PerformTask));

                 t.Start();   

         }

         Thread.Sleep(3600000); //wait for it all to end

  }/* main(String[]) */

}//ThreadSample

 
Java Code

import java.util.*;

 

class WorkerThread extends Thread

{

private Integer idNumber;

         private static int num_threads_made = 1;

         private ThreadSample owner;

 

         public WorkerThread(ThreadSample owner)

         {

super("Thread #" + num_threads_made);  

                 idNumber = new Integer(num_threads_made);  

                 num_threads_made++;       

                 this.owner = owner;

                 start(); //calls run and starts the thread.

         }/* WorkerThread() */

         //sleeps for a random amount of time to simulate working on a task

         public void run()

{

Random r = new Random(System.currentTimeMillis());

             int timeout = r.nextInt()  % 1000;

if(timeout < 0)

                 timeout *= -1 ;

                 try{       

                Thread.sleep(timeout);     

        } catch (InterruptedException e){

                 System.out.println("Thread #" + idNumber + " interrupted");

        }

                 owner.workCompleted(this);

         }/* run() */

              

         public Integer getIDNumber() {return idNumber;}

} // WorkerThread

 

public class ThreadSample{

private Vector threadOrderList = new Vector();

         private Integer nextInLine(){

                 return (Integer) threadOrderList.firstElement();

         }

         private void removeNextInLine(){

threadOrderList.removeElementAt(0);

             //all threads have shown up

             if(threadOrderList.isEmpty())

             System.exit(0);

    }

    public synchronized void workCompleted(WorkerThread worker){

             while(worker.getIDNumber().equals(nextInLine())==false)

{

            try {

                         //wait for some other thread to finish working

                         System.out.println (Thread.currentThread().getName() + " is waiting for Thread #" +

                                                       nextInLine() + " to show up.");

                         wait();

                 } catch (InterruptedException e) {}

                 }//while

                 System.out.println("Thread #" + worker.getIDNumber() + " is home free");

                 //remove this ID number from the list of threads yet to be seen

                 removeNextInLine();

                 //tell the other threads to resume

                 notifyAll();

         }

         public static void main(String[] args) throws InterruptedException

{

                 ThreadSample ts = new ThreadSample();

                 /* Launch 25 threads */

                 for(int i=1; i <= 25; i++)

{

                 new WorkerThread(ts);

                 ts.threadOrderList.add(new Integer(i));

                 }

                 Thread.sleep(3600000); //wait for it all to end

         }/* main(String[]) */

}//ThreadSample

 

Le mot-clé volatile permet en Java d'interdire aux compilateurs de réaliser certaines optimisations de code telles que le déplacement des variables du tas vers la pile. Ce genre d'optimisation, dans la plupart des cas n'a aucun effet nocif sur le déroulement de 99% de vos applications, mais dans certaines circonstances, cela peut mener à bien des casses-têtes difficles à résoudre.  

Il existe des différences fondamentales dans la sémantique de l'ordre volatile en C# et Java qui sont illustrées dans l'exemple ci-dessous tiré de l'article suivant : The "Double-Checked Locking is Broken" Declaration

C# Code

/* Used to lazily instantiate a singleton class */

/*             WORKS AS EXPECTED                */

class Foo

{

  private volatile Helper helper = null;

  public Helper getHelper()

  {

         if (helper == null)

         {

                 lock(this)

                 {

                         if (helper == null)

                                helper = new Helper();

                 }

         }

         return helper;

  }

}

 
Java Code

/* Used to lazily instantiate a singleton class */

/* BROKEN UNDER CURRENT SEMANTICS FOR VOLATILE */

class Foo

{

  private volatile Helper helper = null;

  public Helper getHelper()

  {

         if (helper == null)

         {

                 synchronized(this)

                 {

if (helper == null)

                                helper = new Helper();

                 }

         }

         return helper;

  }

}

Bien que le code précédent paraît identique excepté l'utilisation de synchronized en Java et du lock en C#, il n'est pas garantie que la version Java fonctionne sur toutes les machines virtuelles. A l'heure actuelle, le modèle mémoire de Java n'interdit pas le ré-agencement dans l'ordre des affectations de variables volatile ou pas,  et il ainsi possible qu'un objet soit créé avant que la référence helper ne pointe vers le nouvel objet. Dans ce cas, deux objets sont créées et une référence vers un objet incomplet peut être retourné. En C#, la sémantique de l'ordre volatile prend en compte ce genre de problème et l'ordre des opérations de lecture et écriture ne peut être modifiée avant une affectation de variable volatile. De plus, en C#, annoter une variable volatile interdit au compilateur JIT de placer des variables dans des registres et assure qu'elles seront stockées en mémoire (tas).

Pour plus d'informations sur ce sujet bien connu des développeurs Java, n'hésitez pas à parcourir l'article de Javaworld sur le thème "Double Checked locking mecanism"  : Double-checked locking: Clever, but broken.

3.     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. D'un autre coté, la surcharge de l'opérateur [] pour renvoyer une copie d'un tableau membre est une alternative intéressante pour garantir l'encapsulation.

La surcharge d'opérateurs tend à être utile lorsqu'elle est utilisée de manière intuitive en accord avec le type des classes manipulées. Ainsi, il est judicieux de s'en servir pour les collections ([]), le calcul matriciel (+ et -), les calculs de nombres complexes ou la comparaison de types spécifiques induisant la modification de l'égalité sémantique (chaînes, classes monnaie, ...).

Le code ci-dessous vous illustre différentes utilisations de la surcharge d'opérateurs en C#.

NOTE: Contrairement à C++, C# n'autorise pas la surcharge des opérateurs suivants ; new, ( ), ||, &&, =.

C# Code

using System;

 

class OverloadedNumber

{

  private int value;

  public OverloadedNumber(int value)

  {

         this.value = value;

  }

  public override string ToString()

  {

         return value.ToString();

  }

  public static OverloadedNumber operator -(OverloadedNumber number)

  {

         return new OverloadedNumber(-number.value);

  }

  public static OverloadedNumber operator +(OverloadedNumber number1, OverloadedNumber number2)

  {

         return new OverloadedNumber(number1.value + number2.value);

  }

  public static OverloadedNumber operator ++(OverloadedNumber number)

  {

         return new OverloadedNumber(number.value + 1);

  }

}

 

public class OperatorOverloadingTest

{

  public static void Main(string[] args)

  {

         OverloadedNumber number1 = new OverloadedNumber(12);

         OverloadedNumber number2 = new OverloadedNumber(125);

         Console.WriteLine("Increment: {0}", ++number1);

         Console.WriteLine("Addition: {0}", number1 + number2);

  }

} // OperatorOverloadingTest

 

4.     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 ci-dessous). Les "Fall-through" ont été explicitement désactivés en C# car ils sont source d'erreurs (bugs difficiles à identifier).

C# Code

switch(foo)

{   

case "A":

Console.WriteLine("A seen");

break;

case "B":

case "C":

Console.WriteLine("B or C seen");

break;

      /* ERROR: Won't compile due to fall-through at case "D" */

case "D":

Console.WriteLine("D seen");

case "E":

Console.WriteLine("E seen");

break;

}

  

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 Java, les Assemblies contiennent du code MSIL (byte-code), un ensemble de méta-données et des ressources requises par les classes. N'hésitez pas à consulter l'article sur la structure des Assemblies dans ce même site pour plus d'informations.

De plus, plusieurs traitements tels que la gestion de la sécurité, le déploiement ou le versionning sont réalisés au niveau de l'Assembly. En Java, ces configurations s'effectuent au niveau du fichier ZIP ou JAR ou directement sur l'ensemble des classes non compactées. Alors que les compilateurs C# génèrent des Assemblies sous la forme de fichiers .DLL ou .EXE, les exécutables Java sont des archives (JAR ou ZIP).

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 chainées, Dictionnaires, ...). L'avantage de ces Frameworks est d'éviter au développeur la tâche consistant à coder tous les algorithmes de parcours, tri et création de collections.

Dans C#, les collections sont contenues dans le namespace System.Collections. Ce namespace contient l'ensemble des interfaces et classes représentant des types de données abstraits tels que IList, IEnumerable, IDictionary, ICollection et CollectionBase. Ces classes permettent de manipuler des structures de données indépendamment de leur implémentation. De plus, le namespace System.Collections propose des implémentations concrètes du type ArrayList, Queue, SortedList et Stack. L'avantage de C# par rapport à Java concernant les collections réside dans la manière dont est gérée la synchronisation. Ainsi, en C# il suffit d'utiliser un wrapper synchronisé alors qu'en Java certaines classes sont synchronisées (ArrayList, ...) et d'autres ne le sont pas (Vector, Hashtable, ..).

Les collections en Java contiennent des classes, interfaces et classes abstraites dans le package java.util. S'il existe énormément de similitudes avec C#, nous pouvons considérer que le package Java demeure légèrement plus complet que son homologue en proposant des caractéristiques supplémentaires. Ainsi, vous trouverez en Java des classes "ensemblistes" non ordonnées représentées sous l'appellation "set" avec les TreeSet, HashSet, ...

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.

C# Code

using System;

using System.Net.Sockets;

 

class GotoSample

{

  public static void Main(string[] args)

  {

         int num_tries = 0;

         retry:

                 try

                 {            

                         num_tries++;   

                         Console.WriteLine("Attempting to connect to network. Number of tries =" + num_tries);

                         //Attempt to connect to a network times out

                         //or some some other network connection error that

                         //can be recovered from

                         throw new SocketException();

                 }

                 catch(SocketException)

                 {

                         if(num_tries < 5)

                                goto retry;                

                 }      

  }/* Main(string[]) */

}//GotoSample

 

8.     Les méthode virtuelles (et finales)

Un des concepts les plus important de la programmation orientée objet est le sacro-saint Polymorphisme. Le Polymorphisme vous aide à interagir avec les membres d'une hiérarchie de classes en utilisant le type le plus générique. Concrètement, cela signifie que vous devez posséder des méthodes dans la classe de base qui seront ensuite redéfinies dans les classes filles. Par exemple, prenons le cas de la classe MoyenDeLocomotion contenant la fonction démarrer(), elle-même redéfinie ou implémentée dans la sous-classe Voiture. Le client utilisant le type générique MoyenDeLocomotion appellera donc la méthode démarrer() sans se préoccuper du type concret manipulé. La méthode démarrer() dans cet exemple est appelée méthode virtuelle.

En Java, toutes les méthodes sont par défaut virtuelles alors qu'en C#, comme en C++, le développeur doit explicitement l'indiquer. C'est pourquoi, C# propose un mot-clé n'ayant pas d'équivalent en Java : virtual. Ici encore, cette caractéristique a toujours été source de moult discussions entre pour et contre. Néanmoins, il faut noter que la nature virtuelle des méthodes constitue un frein aux optimisations de la machine virtuelle devant prévoir l'extensibilité de la classe en question. Une autre raison dans le choix de C# pourrait s'expliquer par le fait que les méthodes virtuelles peuvent être accidentellement redéfinies par le développeur dans une sous-classe. Dans ce cas, l'appel à la méthode de la classe mère sera masqué par la méthode fille et pourrait engendrer des résultats inexplicables.

Enfin, Java et C# proposent respectivement les mots-clé final et sealed permettant d'interdire toute redéfinition d'une méthode ou dérivation d'une classe.

Les exemples ci-dessous vous illustrent l'utilisation des méthodes virtuelles en C# et Java.

C# Code

using System;

 

public class Parent

{

  public void DoStuff(string str)

  {

         Console.WriteLine("In Parent.DoStuff: " + str);

  }

}

 

public class Child: Parent

{

  public void DoStuff(int n)

  {

         Console.WriteLine("In Child.DoStuff: " + n);   

  }

  public void DoStuff(string str)

  {

         Console.WriteLine("In Child.DoStuff: " + str); 

  }

}

 

public class VirtualTest

{

  public static void Main(string[] args)

  {

         Child ch = new Child();

         ch.DoStuff(100);

         ch.DoStuff("Test");

         ((Parent) ch).DoStuff("Second Test");

  }

}//VirtualTest

 
OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Parent.DoStuff: Second Test
 
Java Code

class Parent

{

  public void DoStuff(String str)

  {

         System.out.println("In Parent.DoStuff: " + str);   

  }

}

 

class Child extends Parent

{

public void DoStuff(int n)

  {

         System.out.println("In Child.DoStuff: " + n);  

  }

  public void DoStuff(String str)

  {

         System.out.println("In Child.DoStuff: " + str);

  }

}

 

public class VirtualTest

{

  public static void main(String[] args)

  {

         Child ch = new Child();

         ch.DoStuff(100);

         ch.DoStuff("Test");

         ((Parent) ch).DoStuff("Second Test");

  }

}//VirtualTest

 
OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Child.DoStuff: Second Test
 

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.

C# Code

using System;

 

public class Parent

{

  public virtual 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 override void DoStuff(string str)

  {

         Console.WriteLine("In Child.DoStuff: " + str); 

  }

}

 

public class VirtualTest

{

  public static void Main(string[] args)

  {

         Child ch = new Child();

         ch.DoStuff(100);

         ch.DoStuff("Test");

         ((Parent) ch).DoStuff("Second Test");

  }

}//VirtualTest

 
OUTPUT:
In Child.DoStuff: 100
In Child.DoStuff: Test
In Child.DoStuff: Second Test
 

L'exemple précédent peut encore être modifié pour reproduire le résultat initial en modifiant la signature de la méthode DoStuff(string) dans la classe fille ainsi :

public new void DoStuff(string str)

indique que même si la méthode DoStuff  de la classe mère est virtuelle, la classe fille peut implémenter "sa" propre version de la méthode sans redéfinir la précédente (elle peut être appelée ainsi : parent.DoStuff())

9.     Fichiers Entrée/Sortie

Les deux langages supportent les classes d'entrées/sorties. Les exemples ci-dessous réalisent une copie du contenu d'un fichier appelé "input.txt" vers un autre fichier "output.txt".

C# Code

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();

  }

}//FileIOTest

 
Java Code

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();

         }

}//FileIOTest

 

10.                       La Sérialisation

La persistance d'objets, connue aussi sous le terme Serialisation est la caractéristique consistant à lire et à écrire l'état d'un objet (la valeur de ses attributs) dans un flux quelconque (fichier texte, socket réseau, ...). La Sérialisation est utile lorsqu'il s'agit de sauvegarder l'état d'un graphe d'objets entre plusieurs invocations de méthodes. Vous êtes en mesure de reconstruire entièrement l'état de votre application suite à un crash ou tout autre arrêt inopiné. De plus, la Sérialisation vous permet de faire transiter des objets à travers le réseau dans le but de transférer des objets entre plusieurs applications. Ce mécanisme est fortement utilisé dans les architectures distribuées avec RMI,CORBA ou encore .NET Remoting.

Les objets sérialisables en C# sont marqués par l'attribut de Runtime [Serializable]. L'attribut [Serializable]  indique que les membres de la classe ne peuvent pas être sérialisés. Ces membres sont en règle générale des variables temporaires ou des champs n'ayant aucune signification lorsqu'elles sont persistantes (Thread, ...). C# fournit deux formats de sérialisation : XML et un format binaire propriétaire.
Vous avez la possibilité d'implémenter votre propre format de sérialisation par implémentation de l'interface ISerializable.

En Java, les objets sérialisables sont ceux qui implémentent l'interface Serializable  alors que les champs non sérialisables sont marqués avec le mot-clé transient. Par défaut, Java supporte la sérialisation d'objets sous un format binaire propriétaire mais 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
 
C# Code

using System;

using System.IO;

using System.Reflection;

using System.Runtime.Serialization;

using System.Runtime.Serialization.Formatters.Binary;

using 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();

         SerializeTest fromdisk = (SerializeTest)bread.Deserialize(input);

         input.Close();

         /* x will be 0 because it won't be read from disk since 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 will be 0 because it won't be read from disk since 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 Code

import java.io.*;

 

class SerializeTest implements Serializable{

         transient int x;

         private int y;

         public SerializeTest(int a, int b){

           x = a;

                 y = b;

         }

         public String toString(){

return "{x=" + x + ", y=" + y + "}";

         }

         public static void main(String[] args) throws Exception{

                 SerializeTest st = new SerializeTest(66, 61);

                 System.out.println("Before Write := " + st);

                 System.out.println("\n Writing SerializeTest object to disk");

                 FileOutputStream out  = new FileOutputStream("serialized.txt");

                 ObjectOutputStream so = new ObjectOutputStream(out);   

                 so.writeObject(st);

                 so.flush();

                 System.out.println("\n Reading SerializeTest object from disk\n");

                 FileInputStream in     = new FileInputStream("serialized.txt");

                 ObjectInputStream si   = new ObjectInputStream(in);

                 SerializeTest fromdisk = (SerializeTest)si.readObject();

                 /* x will be 0 because it won't be read from disk since transient */

                 System.out.println("After Read := " + fromdisk);

         }

}

 

11.                       Génération de la documentation à partir du code source

Java et C# proposent tous les deux un mécanisme de génération de documentation à partir des sources. Il suffit de respecter une certaine forme d'écriture des commentaires, lesquels seront traités (parsing) par un outil afin de produire une documentation sous la forme de fichiers texte. La 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 :

o        La description de la méthode

o        Les exceptions levées par la méthode.

o        Les paramètres acceptés

o        Les paramètres de retour.

o        Les méthodes associées et attributs membre.

o        Une indication sur la caractère obsolète de la méthode (deprecated).

o        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 :

o        Les API héritées

o        La liste des classe dérivées

o        Un classement hiérarchique, alphabétique de vos classes

o        Un index contenant la liste des méthodes et variables utilisées

 

Java Code

/**

 * Calculates the square of a number.

 * @param num the number to calculate.

 * @return the square of the number.

 * @exception NumberTooBigException this occurs if the square of the number

 * is too big to be stored in an int.

 */

public static int square(int num) throws NumberTooBigException{}

 

C# utilise XML comme format pour la génération de documentation. Les fichiers contiennent les méta-données spécifiées par l'utilisateur ainsi que des informations supplémentaires générées automatiquement. Il existe très peu de différences entre les types de méta-données C# et Java à quelques exceptions près (@author, @version, ou @deprecated, ...). L'avantage indéniable de la génération de documentation en XML réside dans l'indépendance du format de stockage par rapport à la présentation. Ainsi, il suffit d'associer une feuille de style XSLT au document pour pouvoir générer du HTML, des fichiers texte ou encore du PDF. Microsoft fournit par défaut une feuille de style (HTML).

C# Code

///<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){}

 

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.

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#, seule l'option du compilateur /r est utilisée.

14.                       Les évènements

Le modèle de programmation évènementielle consiste à mettre en place un mécanisme permettant de notifier des objets abonnés à un évènement particulier. Ce modèle est souvent associé au design pattern observer/observable et s'utilise en général dans la conception d'interfaces graphiques (IHM). Java et C# proposent tous les deux des mécanismes supportant les évènements mais avec une implémentation totalement différente. Ainsi, en Java, java.util.EventObject est la classe mère de tous les évènements et possède un ensemble de méthodes permettant d'identifier la source de l'évènement. Ensuite, un abonné appelé Listener, identifié à l'aide d'interfaces existantes, devra implémenter une méthode dite de callback. Vous remarquerez que Java traite la gestion d'évènements avec un typage fort. Vous trouverez donc dans les APIs MouseListener, ActionListener, KeyListener, .. contenant toutes des méthodes de callback en relation avec la sémantique de l'évènement (keyPressed, actionPerformed, ...)

C# utilise les délégués pour fournir un mécanisme explicite permettant de gérer l'abonnement/notification. Les délégués sont concrètement des pointeurs de fonction et un évènement est généralement un type dérivé de  System.EventArgs. Ainsi, lorsqu'un évènement apparaît, il suffit de passer au gestionnaire abonné la source et l'évènement en question de la manière suivante : new YourEventArgs(inits). L'émetteur possède une méthode protected précédée en général de "On" (e.g OnClick, OnClose, OnInit, etc...) invoquée lorsqu'un évènement spécifique apparaît. Cette méthode redirige ensuite l'appel au délégué spécifique en passant l'objet EventArgs préalablement initialisé. Les méthodes "OnXX" sont protected pour permettre à une classe dérivée d'y faire appel. L'abonné est une méthode (contrairement à Java où c'est une classe) acceptant les mêmes arguments et paramètres de retour que le délégué. En règle générale, les délégués possèdent deux arguments : Object pour l'objet source et EventArgs pour le contenu de l'évènement, et void comme paramètre de retour.

En C#, le mot-clé event spécifie un type particulier de délégué : ceux chargés de traiter des évènements. Pendant la phase de compilation, le compilateur surcharge les opérateurs += et -= afin de simplifier l'opération d'enregistrement ou de suppression d'un callback. En Java, il vous faut passer par des méthodes prévues à cet effet : addActionListener() et RemoveActionListener().

L'exemple ci-dessous vous illustre une classe générant 20 nombres aléatoires et produisant un évènement à chaque fois qu'un nombre pair est généré.

C# Code

using System;

 

class EvenNumberEvent: EventArgs

{

  /* HACK: fields are typically private, but making this internal so it

   * can be accessed from other classes. In practice should use properties.

   */

  internal int number;

  public EvenNumberEvent(int number):base()

  {

         this.number = number;

  }

}

 

class Publisher

{

  public delegate void EvenNumberSeenHandler(object sender, EventArgs e);

  public event EvenNumberSeenHandler EvenNumHandler;

  protected void OnEvenNumberSeen(int num)

  {

         if(EvenNumHandler!= null)

                 EvenNumHandler(this, new EvenNumberEvent(num));

  }

  //generates 20 random numbers between 1 and 20 then causes and

  //event to occur if the current number is even.

  public void RunNumbers()

  {

         Random r = new Random((int) DateTime.Now.Ticks);

         for(int i=0; i < 20; i++)

         {    

                 int current = (int) r.Next(20);

                 Console.WriteLine("Current number is:" + current);

                 //check if number is even and if so initiate callback call

                 if((current % 2) == 0)

                         OnEvenNumberSeen(current);

         }//for

  }

}//Publisher

 

public class EventTest

{

  //callback function that will be called when even number is seen

  public static void EventHandler(object sender, EventArgs e)

  {

         Console.WriteLine("\t\tEven Number Seen:" + ((EvenNumberEvent)e).number);

  }

  public static void Main(string[] args)

  {

         Publisher pub = new Publisher();

         //register the callback/subscriber

         pub.EvenNumHandler += new Publisher.EvenNumberSeenHandler(EventHandler);

         pub.RunNumbers();

         //unregister the callback/subscriber

         pub.EvenNumHandler -= new Publisher.EvenNumberSeenHandler(EventHandler);

  }

}

 
Java Code

import java.util.*;

 

class EvenNumberEvent extends EventObject

{

public int number;

  public EvenNumberEvent(Object source, int number)

  {

         super(source);

         this.number = number;

  }

}

 

interface EvenNumberSeenListener

{

  void evenNumberSeen(EvenNumberEvent ene);

}

 

class Publisher

{

  Vector subscribers = new Vector();

  private void OnEvenNumberSeen(int num)

  {

         for(int i=0, size = subscribers.size(); i < size; i++)

                 ((EvenNumberSeenListener)subscribers.get(i)).evenNumberSeen(new EvenNumberEvent(this, num));

  }

  public void addEvenNumberEventListener(EvenNumberSeenListener ensl)

  {

         subscribers.add(ensl);

  }

  public void removeEvenNumberEventListener(EvenNumberSeenListener ensl)

  {

         subscribers.remove(ensl);

  }

  //generates 20 random numbers between 1 and 20 then causes and

  //event to occur if the current number is even.

  public void RunNumbers()

  {

         Random r = new Random(System.currentTimeMillis());

         for(int i=0; i < 20; i++)

         {    

                 int current = (int) r.nextInt() % 20;

                 System.out.println("Current number is:" + current);

                 //check if number is even and if so initiate callback call

                 if((current % 2) == 0)

                         OnEvenNumberSeen(current);

         }//for

  }

}//Publisher

 

public class EventTest implements EvenNumberSeenListener

{

//callback function that will be called when even number is seen

  public void evenNumberSeen(EvenNumberEvent e)

  {

System.out.println("\t\tEven Number Seen:" + ((EvenNumberEvent)e).number);

      }

  public static void main(String[] args)

  {

         EventTest et = new EventTest();

         Publisher pub = new Publisher();

         //register the callback/subscriber

         pub.addEvenNumberEventListener(et);

         pub.RunNumbers();

         //unregister the callback/subscriber

         pub.removeEvenNumberEventListener(et);

  }

}

15.                       Interopérabilité inter-language

L'interopérabilité inter-langages est la capacité à intégrer plusieurs applications écrites dans différents langages. Il existe plusieurs manières de ré-utiliser du code C/C++ en Java. Tout d'abord, JNI (Java Native Interface) a été créé dans ce but. Les classes natives (non Java) appelées doivent respecter un certain modèle de développement utilisant des types bien spécifiques propres à l'API JNI. Pour créer une telle application il vous faut respecter les étapes suivantes :

1.                 Créer un programme Java contenant une méthode identifiée par le mot-clé native

2.                 Charger la bibliothèque correspondante au code C/C++ à l'aide de la méthode loadLibrary()

3.                 Compiler la classe contenant la déclaration des méthodes natives

4.                 Utiliser le générateur de stubs javah avec l'option -jni afin de générer les ".h" nécessaires aux invocations

5.                 Coder l'implémentation de la méthode native (généralement du C ou du C++).

6.                 Compiler les fichiers ".h" et les fichiers source dans une bibliothèque partagée (.dll,.so, ...)

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'intéragir avec un système distribué via un ORB (Object Request Broker) tel que Corba ou Rmi. Le middleware est responsable d'assurer la partie communication et marshalling des paramètres entre un objet Java et un objet C++. Sans chercher aussi loin, les WebServices avec Soap sont aussi une solution à ce style de problème. Bien entendu, ces techniques supposent de mettre en place une mécanique assez lourde et parfois inutile lorsque les deux applications n'ont aucune vocation à être distribuées.

Avec une interopérabilité binaire, les objets sont en mesure d'interagir entre eux de manière plus intégré. Ainsi, il est possible d'hériter de l'interface ou de la classe d'un objet écrit dans un autre langage ou de partager les même types. Les compilateurs et autres débogueurs se basent uniquement sur le byte-code généré qui est le même pour tous les langages. C'est l'approche de .NET.

La CLR se charge de l'environnement d'exécution et la CLS assure l'homogénéité du code binaire exécuté (MSIL) à travers un ensemble de types unifiés (CTS). Il existe à l'heure actuelle plus d'une dizaine de compilateurs proposant la génération de code binaire .NET.

Un autre aspect de l'interopérabilité inter-langage supporté par C# est l'interaction avec les objets COM. Le Framework propose des mécanismes permettant d'appeler simplement un objet COM à partir de code C#. Il suffit d'utiliser des outils fournis par le Framework et permettant de générer des classes .NET Proxy prenant en charge le passage entre les deux mondes (tlbimp et tlbexp).

Les programmes C# peuvent aussi appeler une fonction située dans n'importe quelle DLL en utilisant le mot-clé  extern associé à l'attribut DllImport. L'avantage de cette technique est que la méthode de la DLL en question n'a pas besoin d'être nécessairement écrite pour C# et l'inconvénient réside dans le caractère non portable de cette technique puisque l'invocation est fortement liée au format binaire des DLL Win32 spécifiques à Microsoft. Aussi, c'est un autre moyen simple de ré-utiliser du code "non managé" sans passer par des wrappers.

Indéniablement, C# propose plus d'alternatives que Java concernant l'intégration et l'interopérabilité avec les applications développées dans d'autres langages. D'un autre coté, Java insiste sur le caractère portable de son architecture et rejette totalement toute instruction liée à une plate-forme spécifique. Mais, il apparaît de plus en plus que le cloisonnement de la plate-forme Java autour d'un seul langage est une stratégie de moins en moins payante car la communauté de développeurs ne semble pas encore prête à accepter le monopole d'un et seul langage. Techniquement, il est tout à fait envisageable d'intégrer la compilation de code C#, VB ou Eiffel en byte-code Java, mais Sun le veut-il vraiment ?

q       Maintenant, des choses totalement différentes

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.

Il existe une autre manière plus simple de libérer explicitement des objets en utilisant l'instruction using tel que décrit dans l'exemple suivant.

Si une classe est "disposable", il est préférable de faire usage de la méthode Dispose() une et une seule fois. Ainsi, de multiples appels successifs à Dispose() n'auront aucun effet. Vous pouvez implémenter un mécanisme basé sur des flags permettant de contrôler le fait que la classe ait été déjà libérée. L'exemple ci-dessous nous illustre un programme dans lequel une classe référence un fichier ouvert jusqu'à ce que le méthode Dispose() soit appelée.

C# Code

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)

         { /* prevents multiple threads from disposing simultaneously */

           /* disposing variable is used to indicate if this method was called from a

                  * Dispose() call or during finalization. Since finalization order is not

                  * deterministic, the StreamWriter may be finalized before this object in

                  * which case, calling Close() on it would be inappropriate so we try to

                  * avoid that.

                  */

                 if(disposing)

                 {    

                         Console.WriteLine("Finalizing " + name);     

                         sw.Close(); /* close file since object is done with */

                         GC.SuppressFinalize(this);

                         disposed = true;

                 }

         }

}

  public string ShowName()

  {

         if(disposed)           

                 throw new ObjectDisposedException("MyClass");

         numShowNameCalls++;

         sw.Write("ShowName() Call #" + numShowNameCalls.ToString() + "\n");        

         return "I am " + name;

  }

  public static void Main(string[] args)

  {

         using (MyClass mc = new MyClass("A MyClass Object"))

{

     for(int i = 0; i < 10; i++)

                 {

                         Console.WriteLine(mc.ShowName());

                 } //for

         }/* runtime calls Dispose on MyClass object once "using" code block is exited, even if exception thrown  */

  }//Main

}

Ce mécanisme est très proche des destructeurs de C++ et permet de ne pas se soucier de la gestion des allocations mémoire.
La libération déterministe d'objet est une avancée significative dans une prise en charge plus fine des Ramasse miettes.

NOTE : Attention, l'appel à la méthode Dispose() ne demande pas directement au Ramasse miettes de libérer l'objet mais accélère ce processus en outre-passant l'étape de finalisation. .

2.     Les Délégués

Les Délégués sont un mécanisme permettant d'implémenter des fonctions de callback. Ils sont très proche des pointeurs de fonction en C ou C++ et peuvent s'avérer très utiles dans certaines conditions. Par exemple pour déléguer les opérations de tri de collections ou de transformation de listes d'objets. Une autre utilisation de cette technique est la déclaration d'évènements sur le modèle émetteur/récepteur

L'exemple ci-dessous illustre le mécanisme de création et d'utilisation des délégués.

C# Code

using System;

 

//delegate base

public class HasDelegates

{

  // delegate declaration, similar to a function pointer declaration

  public delegate bool CallbackFunction(string a, int b);

  //method that uses the delegate

  public bool execCallback(CallbackFunction doCallback, string x, int y)

  {  

         Console.WriteLine("Executing Callback function...");

         if (doCallback == null)

                 throw ArgumentException("Callback can't be null!");

         return doCallback(x, y);                   

  }

}

 

public class FunctionDelegates

{

  public static bool FunctionFoo(string a, int b)

  {

         Console.WriteLine("Foo: {0} {1}", a, b);

         return true;

  }

}

 

public class DelegateTest

{

  public static void Main(string[] args)

  {

         HasDelegates MyDel = new HasDelegates();

         //create delegate

         HasDelegates.CallbackFunction myCallback =

                 new HasDelegates.CallbackFunction(FunctionDelegates.FunctionFoo);

         //pass delegate to delegate function

         MyDel.execCallback(myCallback, "Twenty", 20);

  }

} // DelegateTest

L'exemple suivant illustre l'utilisation d'un délégué statique :

C# Code

using System;

 

//delegate base

public class HasDelegates

{

  // delegate declaration, similar to a function pointer declaration

  public delegate bool CallbackFunction(string a, int b);

  //method that uses the delegate

  public bool execCallback(CallbackFunction doCallback, string x, int y)

  {  

         Console.WriteLine("Executing Callback function...");

         return doCallback(x, y);                   

  }

}

 

public class FunctionDelegates

{

  public static readonly HasDelegates.CallbackFunction BarFuncCallback =

         new HasDelegates.CallbackFunction(FunctionBar);

  public static bool FunctionBar(string a, int b)

  {  

         Console.WriteLine("Bar: {0} {1}", b, a);

         return true;

  }

}

 

public class DelegateTest

{

  public static void Main(string[] args)

  {

         HasDelegates MyDel = new HasDelegates();

         // with static delegate, no need to know how to create delegate

         MyDel.execCallback(FunctionDelegates.BarFuncCallback, "Thirty Three", 33);

  }

} // DelegateTest

 

3.     Enumérations

Les énumérations sont utilisées pour grouper certaines constantes de même catégorie. Ainsi, la constante "Couleur" prendra l'énumération de valeurs suivantes : ("rouge", "vert","bleu", ..). Le manque de type d'énumération dans Java contraint à utiliser des valeurs entières ne garantissant pas la sécurité des types. Une autre technique pour assurer le respect des types consiste à créer une classe différente pour chaque valeur, ce qui tend à multiplier inutilement le nombre de type alors qu'en C# le compilateur prend en charge automatiquement cette tâche.

Vous trouverez ci-dessous une comparaison d'un code C# et Java utilisant la classe javax.swing.border.TitledBorder redéfinie afin d'illustrer les bénéfices des énumérations C# par rapport à Java.

NOTE : En C#, les  énumérations supporttent la méthode ToString(), ainsi elles peuvent renvoyer leur valeur sous la forme de chaîne de caractères (par exemple "Left") et pas uniquement sous la forme d'entier. Il existe aussi une méthode statique Parse() permettant de convertir une chaîne de caractères en énumération.

C# Code

/* declared somewhere visible */

public enum Justification{ Left, Right, Center, Leading, Default_Justification};

 

/**

 * Sets the title-justification of the titled border.

 */

public void setTitleJustification(Justification titleJustification)

{

//check if justification is a valid value

if (Enum.IsDefined(typeof(Justification), titleJustification)

{

                 /* JUSTIFICATION CODE GOES HERE */

}

}

 
Java Code

/**

 * Sets the title-justification of the titled border.

 */

public void setTitleJustification(int titleJustification)

{

// check if the justification is valid value

if(titleJustification != LEADING && titleJustification != CENTER && 

titleJustification != LEFT &&  titleJustification != RIGHT  &&

titleJustification != DEFAULT_JUSTIFICATION)

{

titleJustification = DEFAULT_JUSTIFICATION;

}

/* JUSTIFICATION CODE GOES HERE */

}

 

4.     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 rentre pas sous la responsabilité du ramasse miettes et sont instantanément supprimées au retour d'un appel de méthode. De plus, les allocations sur la pile sont beaucoup plus rapide que celles dans le tas. De plus, les problèmes liés à la fragmentation de la mémoire n'existent pas dans la pile.

C'est pourquoi, pour éviter d'utiliser inutilement le tas (heap), C# propose de stocker directement certains objets dans la pile. Ces objets sont appelés types de valeur (ValueType). D'ailleurs, en C#, l'ensemble des types primitifs sont des ValueType stockés sous la forme de structures (cf. Int32). Contrairement aux classes, les ValueType sont toujours passées par valeur et ne sont jamais libérées par le ramasse miettes. De plus, les tableaux de ValueType contiennent des valeurs et non des références d'objets, ce qui permet d'économiser de la mémoire et du  temps.

Pour déclarer un type de valeur, il suffit de d'utiliser le mot-clé struct au lieu de class . Pour instancier un type de valeur, le mot-clé new doit être utilisé tout comme une classe normale. Les structures et les constructeurs se comportent différemment par rapport aux classes. Dans le cas d'une classe, la création d'une instance doit toujours précéder l'utilisation de l'objet. Si le mot clé new est omis, la création n'a pas lieu et la référence est nulle.

Tous les types struct héritent implicitement de la classe object. Une déclaration de struct peut spécifier une liste d'interfaces implémentées, mais il n'est pas possible qu'elle spécifie une classe de base. C'est pourquoi les structs ne sont jamais abstraits et sont toujours implicitement finales (sealed). Comme l'héritage n'est pas géré dans les structs, la visibilité par défaut déclarée est protected ou internal protected. et les membres de fonctions ne peuvent être abstract ou virtual.

C# Code

using System;

 

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);

         /* The line below wouldn't compile if Point was a class */

         Point end = new Point();

         Console.WriteLine("End: " + end);

  }

} // Point

 

5.     Boxing

Le boxing (ou emboîtage) est une des fonctionnalités les plus intéressantes de C#. Le boxing 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, ...). La différence entre Java et C# se situe plus au niveau de l'implémentation que de la sémantique même du mécanisme. L'exemple ci-dessous nous illustre le fonctionnement du boxing de manière implicite et explicite.

C# Code

using System;

using System.Collections;

 

struct Point

{

  //member fields

  private int x;

  private 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);

  }

}//Point

 

class Test

{

  public static void PrintString(object o)

  {

         Console.WriteLine(o);

  }

  public static void Main(string[] args)

  {

         Point p = new Point(10, 15);

         ArrayList list = new ArrayList();

         int z = 100;

         PrintString(p); //p boxed to object when passed to PrintString

         PrintString(z); //z boxed to object when passed to PrintString

         // integers and float boxed when stored in collection

         // therefore no need for Java-like wrapper classes

         list.Add(1);

         list.Add(13.12);

         list.Add(z);

         for(int i =0; i < list.Count; i++)

                 PrintString(list[i]);   

  }

}

6.     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.

C# Code

MyClass mc = o as MyClass;

 

if(mc !=  null)      //check if cast successful

mc.doStuff();

NOTE : L'opérateur as s'utilise aussi pour convertir des types de valeur.

7.     L'instruction "for-each"

La boucle foreach est une instruction très populaire dans le monde des langages de scripts tels que Perl, PHP, Tcl/Tk. C'est un moyen efficace et peu verbeux d'effectuer une itération sur une collection d'objets implémentant l'interface System.Collections.IEnumerable.

C# Code

string[] greek_alphabet = {"alpha", "beta", "gamma", "delta", "epsilon"};

 

foreach(string str in greek_alphabet)

Console.WriteLine(str + " is a letter of the greek alphabet");

8.     Les propriétés

Les propriétés sont un moyen d'accéder aux propriétés membre d'une classe en respectant les règles d'encapsulation. Dans le monde Java, les accesseurs (getteurs et setteurs) sont utilisés à ces fins. L'avantage des propriétés en C# est de permettre à l'utilisateur d'accéder aux attributs d'un objet de la même manière que s'il effectuait directement l'opération object.attribut alors qu'en réalité il appelle une méthode de manière totalement transparente.

Il est aussi possible de créer des propriétés en lecture seule (read-only), écriture seule ou lecture/écriture si les accesseurs associés sont implémentés ou pas.

L'exemple ci-dessous nous illustre les différents types de propriétés :

C# Code

using System;

 

public class User

{

  public User(string name)

  {

         this.name = name;  

  }  

  private string name;

  //read-only property for name member variable

  public string Name

  {

         get

         {

                 return name;

         }  

  }

  private static int minimum_age = 13;  

  //read-write property for class member, minimum_age

  public static int MinimumAge

  {

         get

         {

                 return minimum_age;

         }

         set

         {

                 if(value > 0 && value < 100)

                         minimum_age = value;

                 else

                         Console.WriteLine("{0} is an invalid age, so minimum age remains at {1}", value, minimum_age);

         }

  }

  public static void Main(string[] args)

  {

         User newuser = new User("Bob Hope");

         User.MinimumAge = -5; /* prints error to screen since value invalid */

         User.MinimumAge = 18;

         //newuser.Name = "Kevin Nash"; Causes compiler error since Name property is read-only

         Console.WriteLine("Minimum Age: " + User.MinimumAge);      

         Console.WriteLine("Name: {0}", newuser.Name);

  }

} // User

 

L'inconvénient des propriétés C# est qu'elles peuvent mener à des situations pour le moins bizarre lorsqu'une exception se produit. Imaginez une contrainte sur une propriété définissant le fait qu'une valeur ne doit pas dépasser un certain seuil. En cas de débordement, une exception va être levée à l'intérieur de l'accesseur et le client devra traiter cette erreur alors qu'il réalise simplement une affectation. C'est ce qui est représenté dans l'exemple ci-dessous avec la classe Clock :

C# Code

try

{

myClock.Hours   = 28;  /* setter throws exception because 28 is an invalid hour value */

myClock.Minutes = 15;

myClock.Seconds = 39;

}

catch(InvalidTimeValueException itve)

{

/* figure out which field was invalid and report error */

}

Pour éviter ce genre de situation, il est préférable dans la mesure du possible de traiter les exceptions dans le corps des accesseurs et si ce n'est pas possible, indiquez clairement dans la documentation que cette méthode est susceptible de lever une exception que l'utilisateur devra catcher.

9.     Attributes

Les Attributs de Runtime fournissent un moyen élégant d'insérer des annotations (i.e méta-données) dans un module, une classe, une méthode, un paramètre ou une variable membre. Les attributs sont très populaires dans un certain nombre de langages de programmation tels que le noyau Linux (si si) 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. 

[MethodImpl(MethodImplOptions.Synchronized)]: Similaire au mot-clé synchronized de Java.

[Serializable]: Similaire à l'implémentation de l'interface java.io.Serializable de Java.

C# Code
   //declaration of bit field enumeration 
   [Flags]
   enum ProgrammingLanguages{ 
   C     = 1, 
   Lisp  = 2, 
   Basic = 4, 
   All  = C | Lisp | Basic
   }
 
  aProgrammer.KnownLanguages  =  ProgrammingLanguages.Lisp; //set known languages  ="Lisp"
  aProgrammer.KnownLanguages |=  ProgrammingLanguages.C; //set known languages  ="Lisp C"
  aProgrammer.KnownLanguages &= ~ProgrammingLanguages.Lisp; //set known languages  ="C"
 
  if((aProgrammer.KnownLanguages & ProgrammingLanguages.C) > 0){ //if programmer knows C
    //.. do something 
  }
 

[WebMethod]: utilisé en combinaison avec ASP.NET permet de spécifier qu'une méthode est un web service.

Il est possible d'accéder aux attributs d'un module, d'une classe ou d'une méthode par l'intermédiaire des APIs de reflection. C'est une fonctionnalité très utile pour vérifier à l'exécution si une classe supporte un type d'attribut spécifique ou pour extraire des valeurs données.

Les développeurs peuvent créer leur propres attributs de Runtime en sous-classant System.Attribute. L'exemple suivant nous indique comment implémenter son propre attribut fournissant des informations sur l'auteur des sources et comment récupérer les valeurs correspondantes à l'exécution.

C# Code

using System;

using System.Reflection;

[AttributeUsage(AttributeTargets.Class)]

public class AuthorInfoAttribute: System.Attribute

{

  string author;

  string email;

  string version;

  public AuthorInfoAttribute(string author, string email)

  {

         this.author = author;

         this.email  = email;

  }

  public string Version

  {

         get

         {

                 return version;

         }

         set

         {

                 version = value;

         }

  }

  public string Email

  {

         get

         {

                 return email;

         }

  }

  public string Author

  {

         get

         {

                 return author;

         }

  }

}

 [AuthorInfo("Dare Obasanjo", "kpako@yahoo.com", Version="1.0")]

class HelloWorld

{

 

}

class AttributeTest

{

  public static void Main(string[] args)

  {

         /* Get Type object of HelloWorld class */     

         Type t = typeof(HelloWorld);

         Console.WriteLine("Author Information for " + t);

         Console.WriteLine("=================================");

         foreach(AuthorInfoAttribute att in t.GetCustomAttributes(typeof(AuthorInfoAttribute), false))

         {

                 Console.WriteLine("Author: " + att.Author);

                 Console.WriteLine("Email: " + att.Email);

                 Console.WriteLine("Version: " + att.Version);  

         }//foreach

  }//Main

}

 

10.                       Indexeurs

Les indexeurs ont une syntaxe spécifique permettant de surcharger l'opérateur [] d'une classe. Ils sont utiles lorsqu'une classe est un conteneur pour d'autres types d'objets. Les indexeurs supportent divers types comme index : entiers, chaines de caractère, ... Il est aussi possible de créer des indexeurs supportant les tableaux multi-dimensionels. Enfin, les indexeurs peuvent être surchargés. En Java, les indexeurs sont implémentés à travers des accesseurs. Ainsi, en Java on écrirait : myList.getElement(3) et en C# : myList[3]

C# Code

using System;

using System.Collections;

 

public class IndexerTest: IEnumerable, IEnumerator

{

  private Hashtable list;

  public IndexerTest ()

  {

         index = -1;

         list = new Hashtable();    

  }

  //indexer that indexes by number

  public object this[int column]

  {

         get

         {

                 return list[column];

         }

         set

         {

                 list[column] = value;

         }

  }

  /* indexer that indexes by name */

  public object this[string name]

  {

         get

         {

                 return this[ConvertToInt(name)];

         }

         set

         {

                 this[ConvertToInt(name)] = value;

         }

  }

  /* Convert strings to integer equivalents */

  private int ConvertToInt(string value)

  {

         string loVal = value.ToLower();

         switch(loVal)

         {

                 case "zero": return 0;

                 case "one": return 1;

                 case "two": return 2;

                 case "three":  return 3;

                 case "four": return 4;

                 case "five": return 5;

                 default:

                         return 0;

         }

         return 0;

  }

  /**

   * Needed to implement IEnumerable interface.

   */

  public IEnumerator GetEnumerator(){ return (IEnumerator) this; }

  /**

   * Needed for IEnumerator.

   */

  private int index;

  /**

   * Needed for IEnumerator.

   */

  public bool MoveNext()

  {

         index++;

         if(index >= list.Count)

                 return false;

         else

                 return true;

  }

  /**

   * Needed for IEnumerator.

   */

  public void Reset()

  {

         index = -1;

  }

  /**

   * Needed for IEnumerator.

   */

  public object Current

  {

         get

         {

                 return list[index];

         }

  }

  public static void Main(string[] args)

  {

         IndexerTest it = new IndexerTest();

         it[0] = "A";

         it[1] = "B";

         it[2] = "C";

         it[3] = "D";

         it[4] = "E";

         Console.WriteLine("Integer Indexing: it[0] = " + it[0]);   

         Console.WriteLine("String  Indexing: it[\"Three\"] = " + it["Three"]);

         Console.WriteLine("Printing entire contents of object via enumerating through indexer :");

         foreach( string str in it)

         {

                 Console.WriteLine(str);

         }

  }

} // IndexerTest

 

11.                       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.

C# Code

#define DEBUG /* #define must be first token in file */

using System;

 

class PreprocessorTest

{

  public static void Main(string[] args)

  {

                 #if DEBUG

                 Console.WriteLine("DEBUG Mode := On");

                 #else

                 Console.WriteLine("DEBUG Mode := Off");

                 #endif

  }

}

 

12.                       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.

C# Code

using Terminal = System.Console;

 

class Test

{

  public static void Main(string[] args)

  {

         Terminal.WriteLine("Terminal.WriteLine is equivalent to System.Console.Writeline");

  }

}

 

13.                       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 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.

14.                       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 "sticker" ou figer ces variables le temps du traitement par le bloc unsafe. Cette opération est réalisée à l'aide du mot-clé "fixed"

C# Code

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 + " ");

  }

}

 

15.                       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 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é.

Java Code

class PassByRefTest

{

  public static void changeMe(String s)

  {

         s = "Changed";

  }

  public static void swap(int x, int y)

  {

         int z = x;

         x = y;

         y = z;

  }

  public static void main(String[] args)

  {

         int a = 5, b = 10;

         String s = "Unchanged";

         swap(a, b);

         changeMe(s);

         System.out.println("a := " + a + ", b := " + b + ", s = " + s);

  }

}

 
OUTPUT
a := 5, b := 10, s = Unchanged
 
C# Code

using System;

 

class PassByRefTest

{

  public static void ChangeMe(out string s)

  {

         s = "Changed";

  }

  public static void Swap(ref int x, ref int y)

  {

         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 := " + b + ", s = " + s);

  }

}

 
OUTPUT
a := 10, b := 5, s = Changed
 

16.                       Les listes variables de paramètres

En C et C++, il est possible de spécifier qu'une fonction prend un nombre variable de paramètres, cette fonctionnalité est fortement utilisée dans le cadre des fonctions printf() et scanf(). En C#, le fonctionnement est le même excepté qu'il faut spécifier le mot-clé param pour indiquer que le nombre de paramètres est variable.

C# Code

using System;

 

class ParamsTest

{

  public static void PrintInts(string title, params int[] args)

  {

         Console.WriteLine(title + ":");

         foreach(int num in args)

                 Console.WriteLine(num);

  }

  public static void Main(string[] args)

  {

         PrintInts("First Ten Numbers in Fibonacci Sequence", 0, 1, 1, 2, 3, 5, 8, 13, 21, 34);

  }

}

 

17.                       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 @.

C# Code

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);

  }

}

 

18.                       Détection de débordement

C# fournit une option pour explicitement détecter ou ignorer les débordements dans le cas de conversion vers un type plus faible. Le débordement lève une exception du type System.OverFlowException et comme la détection met en place des mécanismes pouvant affecter les performances, il est possible de l'activer ou de la désactiver en spécifiant les mots-clés checked et unchecked au début de blocs.

C# Code

using System;

 

class CheckedTest

{

  public static void Main()

  {

         int num = 5000;

         /* OVERFLOW I */

         byte a  = (byte) num; /* overflow detected only if /checked compiler option on */

         /* OVERFLOW II */

         checked

         {

                 byte b = (byte) num; /* overflow ALWAYS detected */    

         }

         /* OVERFLOW III */

         unchecked

         {

                 byte c = (byte) num; /* overflow NEVER detected */     

         }

  }//Main

}

 

19.                       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

C# Code

using System;

 

interface IVehicle

{

  //identify vehicle by model, make, year

  void IdentifySelf();   

}

interface IRobot

{

  //identify robot by name

  void IdentifySelf();

}

class TransformingRobot : IRobot, IVehicle

{

  string model;

  string make;

  short  year;

  string name;

  TransformingRobot(String name, String model, String make, short year)

  {

         this.name  = name;

         this.model = model;

         this.make  = make;

         this.year  = year;

  }

  void IRobot.IdentifySelf()

  {

         Console.WriteLine("My name is " + this.name);

  }

  void IVehicle.IdentifySelf()

  {

         Console.WriteLine("Model:" + this.model + " Make:" + this.make + " Year:" + this.year);

  }

  public static void Main()

  {

         TransformingRobot tr = new TransformingRobot("SedanBot", "Toyota", "Corolla", 2001);

         // tr.IdentifySelf(); ERROR

         IVehicle v = (IVehicle) tr;

         IRobot r   = (IRobot) tr;

         v.IdentifySelf();

         r.IdentifySelf();  

  }

}

 
OUTPUT
Model:Toyota Make:Corolla Year:2001
My name is SedanBot
 

q       Concepts avancés

1.     Portabilité multi-plateforme (Write Once, Run Anywhere)

Une notion importante qui a contribuée à la réussite du langage Java est sa capacité à s'exécuter sur n'importe quelle plate-forme sans avoir à ré-écrire le code. Sun officiellement supporte Linux, Windows, Solaris et d'autres éditeurs tiers assurent le portage de la machine virtuelle sur OS/2, AIX et MacOS.

A l'heure actuelle, la plate-forme .NET n'est vraiment utilisable que sur les systèmes Windows. Plusieurs efforts de portage ont été entrepris à travers le monde. Citons dans ce domaine,  les projets Mono (http:///www.go-mono.org), Halcyon (conversion .NET vers Java) ou encore Microsoft avec le portage de .NET sur FreeBSD (nom de code : rotor).

2.     Les extensions

Les extensions Java sont un moyen d'enrichir les APIs internes du JDK dans le but de fournir une implémentation spécifique de certains packages système. Cela signifie que l'extension est intégrée au sein des classes java.lang, java.util, java.net, etc,  et peut-être utilisée par l'ensemble des applications utilisant Java.

Pour faire un parallèle avec C#, c'est comme si l'extension proposée s'intégrait dans le namespace System contenu dans l'assembly System.dll.

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.

Java Code
 

public class MyRMIServer extends UnicastRemoteObject

    implements SomeInterface {

   

         public MyRMIServer() throws RemoteException{ super();}

         public String obtainName(IStockTicker ticker){

String stock_ticker = ticker.getTicker();

 

                 if(stock_ticker.equalsIgnoreCase("MSFT"))

                 return "Microsoft Corporation";

                 else  if(stock_ticker.equalsIgnoreCase("SUNW"))

                 return "Sun Microsystems";

                 else

                 return "Unknown Stock Ticker";

         }/* obtainName(IStockTicker) */

}

 

La méthode distante obtainName()  dans la classe ci-dessus accepte des types qui implémentent l'interface StockTicker. Le client doit juste se contenter de passer un objet du type de l'interface attendue. Par exemple, le type CAC40Stock qui n'existe pas sur le serveur peut-être téléchargé à partir d'une source équelconque et s'utilise comme s'il avait toujours existé sur le serveur, et cela sans arrêter ou relancer l'application.

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.

4.     Les interfaces contenant des champs

En Java, il est possible de déclarer des constantes dans les interfaces qui seront disponibles aux classes dérivées. Cette technique est souvent utilisée dans Java pour pallier à l'absence de mécanisme intégré de gestion d'énumérations.

En C#, cette caractéristique n'existe pas et ne constitue pas réellement un problème dans la mesure où ce langage fournit déjà un mécanisme d'énumération (enums)

5.     Les classes anonymes (Inner classes)

Une classe anonyme est une classe instanciée à l'initialisation du type et ne possédant pas de nom. Les classes anonymes sont souvent utilisées lorsqu'il est nécessaire d'avoir une et une seule instance d'un type donné existant dans l'application. En Java, leur utilisation est souvent cantonnée à la gestion des évènements, les classes callback sont la plupart du temps anonyme.

L'exemple qui suit illustre l'implémentation du Design Pattern State (automate à états finis) en utilisant des classes anonymes

Java Code

/* An instance of this class represents the current state of a ClientView GUI. */

public abstract class ClientState{

         // This instance of the class is used to signify that the user is not logged in.

         // The only thing a user can do in this state is login and exit. 

         public static ClientState NOT_LOGGED_IN = new ClientState() {

         public void setMenuState(ClientView cv) {

                 cv.setMenuEnabledState(false); /* disable all menus */

                 cv.menuLogin.setEnabled(true);

                 cv.menuExit.setEnabled(true);

                 //can't type

                 cv.textArea.setEnabled(false);

}

        public String toString(){

        return "ClientState: NOT_LOGGED_IN";

}

};

         // This instance of the class is used to signify that the user is logged in

         // but has not yet created a document to work with. The user cannot type or save

         // anything in this mode.

         public static ClientState NO_OPEN_DOCUMENT = new ClientState() {

        public void setMenuState(ClientView cv) {

                 cv.setMenuEnabledState(false); /* disable all menus */

                 cv.menuLogin.setEnabled(true);

                 cv.menuExit.setEnabled(true);

      cv.menuOpenFile.setEnabled(true);

                 cv.menuNewFile.setEnabled(true);

                 //can't type

                 cv.textArea.setEnabled(false);

        }

        public String toString(){

                 return "ClientState: NO_OPEN_DOCUMENT";

         }

         };

      // This instance of the class is used to signify that the user is editting a file.

         // In this mode the user can use any functionality he/she sees fit.

         public static ClientState EDITTING_DOCUMENT = new ClientState() {

        public void setMenuState(ClientView cv) {

                 cv.setMenuEnabledState(true);   /* enable all menus */           

                 cv.textArea.setEnabled(true);

        }

 

        public String toString(){

                 return "ClientState:EDITTING_DOCUMENT";

         }

         };

         // Default constructor private to stop people from directly creating instances

         // of the class.       

         private ClientState() {;}

         // This disables various elements of the ClientView's menu dependent on which

         // ClientState object this is.     

         public abstract void setMenuState(ClientView cv);

} // ClientState

 

 

Below is an example of the code that would utilize the above ClientState class. 

 

bool loginUser(String username, String passwd) {

      //check if already logged in

      if(myGUI.state == ClientState.NOT_LOGGED_IN) 

         return true;

      //enable parts of the GUI if the user authenticates

      if(userAuthenticated(username, passwd)){

                 myGUI.state = ClientState.NO_OPEN_DOCUMENT;

                 myGUI.state.setMenuState(myView);

                 return true;

         }

      return false;

}/* loginUser(String, String) */

 

6.     Le versionning des Assemblies

Le version des Assemblies est une des fonctionnalités les plus intéressantes de C# et de .NET en général. Ainsi, vous avez la possibilité d'exécuter dans un même domaine d'application plusieurs versions différentes d'une même classe. Cela est possible du fait que les programmes C# référencent statiquement des bibliothèques de classes à l'aide de leur nom mais aussi de leur version. Vous ne risquez donc pas d'avoir des problèmes de cohérence entre différentes versions de DLL s'exécutant sur la même machine. Les développeurs Java connaissent bien les âfres du déploiement d'applications Web (JSP et servlets) référençant de multiples versions de parseur XML (xerces) ou de libraries ant.jar. Cela est dû au fait que les ClassLoaders Java fonctionnent sur le principe de premier chargé premier servi. Même si le modèle Java du chargement dynamique de classes est, il faut l'avouer,  plus riche que celui de C#, il n'en demeure pas moins que cette fonctionnalité manque cruellement dans le JDK.    

q       Conclusion

La plupart des développeurs, spécialement ceux possédant un background C ou C++ apprécieront à leur juste valeur les caractéristiques de surcharge d'opérateurs, pointeurs, délégués et autre libération de mémoire déterministe faisant de C# un langage véritablement riche. Aussi, les développeurs Java désirant se former à ce nouveau langage seront agréablement surpris par le fait que certaines caractéristiques manquantes à Java sont supportées par C#, à savoir le Boxing/Unboxing, les types de valeurs, le mot-clé const, les délégués ou autre héritage explicite d'interface. D'un autre coté, le manque de support d'exception typées, l'absence de classes anonymes, la portabilité multi-plateforme ou le fait que la classe ne soit pas la plus petite unité de déploiement (c'est l'assembly) font que le choix entre C# et Java n'est plus aussi clair.

Ainsi, la vrai question est plutôt : lequel de ces deux langages pourra évoluer avec le temps pour s'adapter à de nouvelles situations lorsqu'elles apparaîtront ? Sun a joué un rôle remarquable avec Java en modifiant la syntaxe lorsque le besoin se faisait sentir (modèle d'évènements 1.0, swing, assert, ...). D'un autre coté, certaines lacunes du langage commence à peser sérieusement et aucune solution n'est réellement proposée :

§         L'absence de mécanisme de versionning de classes : possibilité d'avoir plusieurs versions différentes de classes chargées en même temps en mémoire.

§         Les mots-clé Const, Delegate, ReadOnly ...

§         Le manque de mécanisme intégré permettant d'étendre les fonctionnalités de base d'une classe au niveau des sources  (cf Attributs de runtime)

§         Les performances de Swing, ...

Il faut bien l'avouer, C# avec les attributs de Runtime et ces multiples fonctionnalités s'avère techniquement mieux armé pour le combat. Mais, l'histoire nous a démontré à maintes reprises que les langages les plus riches n'étaient pas forcément ceux qui réussissaient le mieux ...

q       Ressources

  1. Eckel, Bruce. Thinking In Java. Prentice Hall, 2000.
  2. Gunnerson, Eric. A Programmer's Introduction To C#. Apress, 2001.
  3. Sun Microsystems. The Java™ Tutorial.<http://java.sun.com/docs/books/tutorial/>
  4. Microsoft Corporation. .NET Framework SDK Documentation. < http://msdn.microsoft.com/library/default.asp?url=/library/en-us/nfstart/html/sdkstart.asp>

q       Remerciements

Je remercie sincèrement Dare Obasanjo de nous avoir permis de modifier et d'adapter son article en Français.
Merci aussi à toutes les personnes ayant passé des heures entières à relire et corriger ces centaines de lignes. (Sami Jaber)

© 2002 mailto:kpako@yahoo.com