2.1. C# et la programmation
orientée objet ( POO )



Plan général:   ...........retour au plan général

Rappels des fondements
de  la POO

 
Introduction : modification des niveaux de visibilités


1. Les classes C# : des nouveaux types

    1.1 Déclaration d'une classe
    1.2 Une classe est un type en C#
    1.3 Toutes les classes ont le même ancêtre - héritage
    1.4 Encapsulation des classes
    1.5 Exemple de classe imbriquée dans une autre classe
    1.6 Exemple de classe inclue dans un même espace de noms
    1.7 Méthodes abstraites
    1.8 Classe abstraite,  Interface


2. Les objets : des références ou des valeurs

    2.1 Modèle de la référence 

    2.2 Les constructeurs d'objets référence ou valeurs
    2.3 Utilisation du constructeur d'objet par défaut
    2.4 Utilisation d'un constructeur d'objet personnalisé
    2.5 Le mot clef this- cas de la référence seulement


3. Variables et méthodes

    3.1 Variables dans une classe en général
    3.2 Variables et méthodes d'instance
    3.3 Variables et méthodes de classe - static
    3.4 Bilan et exemple d'utilisation




Nous proposons des comparaisons entre les syntaxes de C# et Delphi et/ou Java, lorsque les définitions sont semblables.

Modification de visibilité 

Rappelons les classiques modificateurs de visibilité des variables et des méthodes dans les langages orientés objets, dont C# dispose :

Modification de visibilité (modularité public-privé)
par défaut (aucun mot clef) les variables et les méthodes d'une classe non précédées d'un mot clef sont private et ne sont visibles que dans la classe seulement.
public les variables et les méthodes d'une classe  précédées du mot clef public sont visibles par toutes les classes de tous les modules.
private les variables et les méthodes d'une classe  précédées du mot clef private ne sont visibles que dans la classe seulement.
protected les variables et les méthodes d'une classe  précédées du mot clef protected sont visibles par toutes les classes inclues dans le module, et par les classes dérivées de cette classe.
internal
les variables et les méthodes d'une classe  précédées du mot clef protected sont visibles par toutes les classes incluses dans le même assembly.

Les attributs d'accessibilité public, private, protected sont identiques à ceux de Delphi et Java, pour les classes nous donnons ci-dessous des informations sur leur utilisation.

L'attribut internal joue à peu près le rôle (au niveau de l'assembly) des classes Java déclarées sans mot clef dans le même package , ou des classes Delphi déclarées dans la même unit (classes amies). Toutefois pour des raisons de sécurité C# ne possède pas la notion de classe amie.

Tableau des limitations des niveaux de visibilité fourni par microsoft :




1. Les classes : des nouveaux types
 

Rappelons  un point fondamental déjà indiqué : tout programme C# contient une ou plusieurs classes précédées ou non d'une déclaration d'utilisation d'autres classes contenues dans des bibliothèques (clause using) ou dans un package complet composé de nombreuses classes. La notion de module en C# est représentée par l'espace de noms (clause namespace) semblable au package Java,  en C# vous pouvez omettre de spécifier un namespace, par défaut les classes déclarées le sont automatiquement dans un espace 'sans nom' (généralement qualifié de global) et tout identificateur de classe déclaré dans cet espace global sans nom est disponible pour être utilisé dans un espace de noms nommé. Contrairement à Java, en C# les classes non qualifiées par un modificateur de visibilité (déclarées sans rien devant) sont public.

La notion de classe interne de C# (qui n'existe pas en Delphi) est moins riche à ce jour qu'en Java (pas de classe membre statique, pas de classe locale et pas de classe anonyme), elle corespond à la notion de classe membre de Java.
Attention
Par défaut dans une classe tous les membres sans qualificateur de visibilité (classes internes inclues)
 sont private.


Remarque

La notion de classe sealed en C#  correspond strictement à la notion de classe final de Java : ce sont des classes non héritables.
  • Les interfaces ressemblent aux classes abstraites sur un seul point : elles contiennent des membres expliquant certains comportements sans les implémenter.

  •  
  • Les classes abstraites et les interfaces se différencient principalement par le fait qu'une classe peut implémenter un nombre quelconque d'interfaces, alors qu'une classe abstraite ne peut hériter que d'une seule classe abstraite ou non.


Vocabulaire et concepts :

  • Une interface est un contrat, elle peut contenir des propriétés, des méthodes et des événements mais ne doit contenir aucun champ ou attribut
  • Une interface ne peut pas contenir des méthodes déjà implémentées.
  • Une interface doit contenir des méthodes non implémentées.
  • Une interface est héritable.
  • On peut contsruire une hiérarchie d'interfaces.
  • Pour pouvoir construire un objet à partir d'une interface, il faut définir une classe non abstraite implémentant toutes les méthodes de l'interface.


Une classe peut implémenter plusieurs interfaces. Dans ce cas nous avons une excellente alternative à l'héritage multiple.

Lorsque l'on crée une interface, on fournit un ensemble de définitions et de comportements qui ne devraient plus être modifiés. Cette attitude de constance dans les définitions, protège les applications écrites pour utiliser cette interface.

Les variables de types interface respectent les mêmes règles de transtypage que les variables de types classe. 

Les objets de type classe clA peuvent être transtypés et reférencés par des variables d'interface IntfA dans la mesure où la classe clA implémente l’interface IntfA. (cf. polymorphisme d'objet)

Si vous voulez utiliser la notion d'interface pour fournir un polymorphisme à une famille de classes, elles doivent toutes implémenter cette interface, comme dans l'exemple ci-dessous.

Exemple :

l'interface Véhicule  définissant 3 méthodes (abstraites) Démarrer, RépartirPassagers de répartition des passagers à bord du véhicule (fonction de la forme, du nombre de places, du personnel chargé de s'occuper de faire fonctionner le véhicule...), et PériodicitéMaintenance renvoyant la périodicité de la maintenance obligatoire du véhicule (fonction du nombre de km ou miles parcourus, du nombre d'heures d'activités,...)

Soit l'interface Véhicule définissant ces 3 méthodes :

interface IVehicule{
       void Demarrer( );
       void RépartirPassager( );
       void PériodicitéMaintenance( );
   }


Soient les deux classes Véhicule terrestre et Véhicule marin, qui implémentent partiellemnt chacune l'interface Véhicule , ainsi que trois classes voiture, voilier et croiseur héritant de ces deux classes :

Dans cette vision de la hiérarchie on a supposé que les classes abstraites Véhicule terrestre et Véhicule marin savent comment  répartir leur éventuels passagers et quand effectuer une maintenance du véhicule.

Les classes voiture, voilier et croiseur , n'ont plus qu'à implémenter chacune son propre comportement de démarrage.


Une interface C# peut être qualifiée par un des 4 modificateur public, protected, internal, private.

Contrairement à Java, une classe abstraite C# qui implémente une interface doit obligatoirement déclarer toutes les méthodes de l'interface, celles qui ne sont pas implémentées dans la classe abstraite doivent être déclarées abstract. C'est le cas dans l'exemple ci-dessous pour la méthode abstraite Démarrer , nous proposons deux écritures possibles pour cette hiérarchie de classe :

C#
méthode abstraite sans corps
C#
méthode virtuelle à corps vide
interface IVehicule{
       void Demarrer( );
       void RépartirPassager( );
       void PériodicitéMaintenance( );
   }

    abstract class Terrestre  : IVehicule {
       public abstract void Demarrer( );
       public void virtual RépartirPassager( ){...}
       public void virtual PériodicitéMaintenance( ){...}
   }

    class Voiture : Terrestre {
       override public void Demarrer( ){...}
   }

    abstract class Marin : IVehicule {
       public abstract void Demarrer( );
       public void virtual RépartirPassager( ){...}
       public void virtual PériodicitéMaintenance( ){...}
   }

    class Voilier : Marin {
       override public void Demarrer( ){...}
   }
    class Croiseur : Marin {
       override public void Demarrer( ){...}
   }

interface IVehicule{
       void Demarrer( );
       void RépartirPassager( );
       void PériodicitéMaintenance( );
   }

    abstract class Terrestre  : IVehicule {
       public virtual void Demarrer( ){}
       public void virtual RépartirPassager( ){...}
       public void virtual PériodicitéMaintenance( ){...}
   }

    class Voiture : Terrestre {
       override public void Demarrer( ){...}
   }

    abstract class Marin : IVehicule {
       public virtual void Demarrer( ) {}
       public void virtual RépartirPassager( ){...}
       public void virtual PériodicitéMaintenance( ){...}
   }

    class Voilier : Marin {
       override public void Demarrer( ){...}
   }
    class Croiseur : Marin {
       override public void Demarrer( ){...}
   }


Les méthodes RépartirPassagers, PériodicitéMaintenance et Demarrer sont implantées virtual , soit comme des méthodes à liaison dynamique, afin de laisser la possibilité pour des classes enfants de redéfinir ces méthodes.

Soit à titre de comparaison, les deux mêmes écritures en Java de ces classes  :

Java
méthode abstraite sans corps
Java
méthode virtuelle à corps vide



Objets type valeur

Les classes encapsulant les types élémentaires dans .NET Framework sont des classes de type valeur du genre structures. Dans le CLS  une classe de type valeur est telle que les allocations d'objets de cette classe se font directement dans la pile et non dans le tas, il n'y a donc pas de référence pour un objet de type valeur et lorsqu'un objet de type valeur est passé comme paramètre il est passé par valeur.

Dans .NET Framework les classes-structures de type valeur sont déclarées comme structures et ne sont pas dérivables


Objets type référence

Le principe d'allocation et de représentation des objets type référence en C# est identique à celui de Delphi il s'agit de la référence, qui est une encapsulation de la notion de pointeur. Dans .NET Framework les classes de type référence sont déclarées comme des classes classiques et sont dérivables.

Afin d'éclairer le lecteur prenons par exemple un objet x instancié à partir d'une classe de type référence et un objet y instancié à partir d'un classe de type valeur contenant les mêmes membres que la classe par référence. Ci-dessous le schéma d'allocation de chacun des deux catégories d'objets :

C# Explication
class Un
 {
     int a;
    public
Un (int b ) 

    { a = b;
    }
   public Un ( ) 
    { a = 100;
    }
   public Un (float b ) 
    { a = (int)b;
    }
   public Un (int x , float y ) : this(y)
    { a += 100;
    }
 }
La classe Un possède 3 constructeurs servant à initialiser chacun d'une manière différente le seul attribut int a.

Soit le dernier constructeur :
public
Un (int x , float y ) : this(y)

{ a += 100; }

Ce constructeur appelle tout d'abord le constructeur Un (float y ) par l'intermédiaire de this(y), puis il exécute le corps de méthode soit : a += 100;

Ce qui revient à calculer :
a = (int)y + 100 ;
 




struct 
AppliInstance
{
   int
x ;
   int y ;
}



Un programme C# à 2 classes illustrant l'exemple précédent (classe référence):
Programme C# exécutable
class AppliInstance
{  public int x = -58 ;
   public int y = 20 ;
}
class Utilise
{ public static void Main( ) {
     AppliInstance  obj1 = new AppliInstance( );
     AppliInstance  obj2 = new AppliInstance( );
     AppliInstance  obj3 = new AppliInstance( );
     System.Console.WriteLine( "obj1.x = " + obj1.x );
  }
}