1. Généralités sur C#

C# est un langage de programmation de Microsoft qui a été conçu spécialement pour le .Net Framework. Le .Net Framework de Microsoft est un environnement d'exécution et une bibliothèque de classes qui simplifie énormément le développement d'applications modernes basées sur des composants.

1.1. Identifiants et mots-clés

Les identifiants sont des noms que les programmeurs choisissent pour leur classes, méthodes, variables, etc.
Un identifiant doit : Les identifiants C# sont sensibles à la casse.
Voici une liste des mots-clés de C# :
abstract as base bool break
byte case catch char checked
class (1) const (1) continue decimal default
delegate do double else enum
event explicit extern false finally
fixed float for foreach get
goto (1) if implicit in int
interface internal is lock long
namespace new (1) null object (1) operatror
out override params private protected
public readonly ref return sbyte
sealed set short sizeof stackalloc
static string struct (1) switch this
throw true try typeof uint
ulong unchecked unsafe ushort using
value virtual void while

1.2. Eléments fondamentaux

Un programme C# se comprend mieux en termes de trois éléments de base:

1.3. Types valeur et référence

Tous les types C# entrent dans les catégories suivantes : Ces deux principales catégories se différencient par la façon dont elles sont stockées en mémoire.

1.3.1. Types valeur

Les types valeur contiennent directement une donnée. La caractéristique clé d'un type valeur est la copie faite d'une valeur assignée à une autre valeur.
Code Résultat Remarques
using System;

namespace Exemples
{
    class TestTypesValeur
    {
        static void Main(string[] args)
        {
            int x = 33;
            int y = x; // assigne x à  y, y est maintenant une copie de x
            x++; // incrémente x à  34
            Console.WriteLine(y); // affiche 33
            Console.ReadKey();
        }
    }
}
33
Le type int contient un entier qui est copié par valeur.

1.3.2. Types référence

Un type référence se définit avec deux entités distinctes :
Code Résultat Remarques
using System;
using System.Text;

namespace Exemples
{
    class TestTypesReference1
    {
        static void Main(string[] args)
        {
            StringBuilder x = new StringBuilder("Bonjour");
            StringBuilder y = x;
            x.Append(" tout le monde !");
            Console.WriteLine(y); // affiche Bonjour tout le monde !
            Console.ReadKey();
        }
    }
}
Bonjour tout le monde !
Le type StringBuilder est un type référence. Lorsque nous déclarons la variable StringBuilder, nous faisons en fait deux choses différentes qui peuvent être séparées en deux lignes :
StringBuilder x;
x = new StringBuilder("Bonjour");
La première ligne crée une nouvelle variable qui peut contenir une référence à un objet StringBuilder. La seconde ligne assigne un nouvel objet StringBuilder à la variable.
Lorsque nous assignons x à y, nous faisons en sorte que y pointe sur le même objet que x. Une référence contient l'adresse d'un objet (une adresse est un emplacement mémoire stocké comme un nombre de 4 octets). Nous créons ainsi une copie de x, mais nous copions ce nombre de 4 octets plutôt que l'objet StringBuilder lui-même.
using System;
using System.Text;

namespace Exemples
{
    class TestTypesReference2
    {
        static void Main(string[] args)
        {
            StringBuilder x = new StringBuilder("Bonjour");
            StringBuilder y = x;
            x = null;
            y.Append(" tout le monde !");
            Console.WriteLine(y); // affiche Bonjour tout le monde !
            Console.ReadKey();
        }
    }
}
Bonjour tout le monde !
Une référence peut ne pointer vers aucun objet en assignant la référence à null. Ici, nous assignons null à x, mais nous pouvons toujours accéder au même objet StringBuilder que nous avons créé via y.

1.3.3. Types valeur et référence côte à côte

Pour comprendre la différence entre les types valeur et les types référence, comparez-les !
En C#, vous pouvez définir vos propres types valeur (en déclarant une structure avec le mot-clé struct) et vos propres types référence (en déclarant une classe avec le mot-clé class).
Pour créer une instance de type valeur ou de type référence, le constructeur pour le type peut être appelé en utilisant le mot-clé new.
Code Résultat Remarques
using System;

namespace Exemples
{
    // Déclaration type référence
    class PointReference
    {
        public int x, y;
    }
    // Déclaration type valeur
    struct PointValeur
    {
        public int x, y;
    }
    // Test
    class TypesValeurEtReferenceCoteACote1
    {
        static void Main(string[] args)
        {
            PointReference a; // type référence
            a = new PointReference();
            PointValeur b; // type valeur
            b = new PointValeur();
            a.x = 7;
            b.x = 7;
            Console.ReadKey();
        }
    }
}
  A la fin de la méthode, les variables locales a et b son hors de portée, mais la nouvelle instance d'un pointReference reste en mémoire jusqu'à ce que le ramasse-miettes détermine qu'elle n'est plus référencée.
using System;

namespace Exemples
{
    // Déclaration type référence
    class PointReference
    {
        public int x, y;
    }
    // Déclaration type valeur
    struct PointValeur
    {
        public int x, y;
    }
    // Test
    class TypesValeurEtReferenceCoteACote2
    {
        static void Main(string[] args)
        {
            PointReference a; // type référence
            a = new PointReference();
            PointValeur b; // type valeur
            b = new PointValeur();
            a.x = 33;
            b.x = 33;
            PointReference c = a;
            PointValeur d = b;
            c.x = 91;
            d.x = 91;
            Console.WriteLine(a.x); // affiche 91
            Console.WriteLine(b.x); // affiche 33
            Console.ReadKey();
        }
    }
}
91
33
Une assignation à un type référence copie la référence d'un objet tandis qu'une assignation à un type valeur copie la valeur d'un objet.
Un objet sur le tas peut être pointé sur de multiples variables.
Un objet sur la pile ou en ligne (inline) ne peut être accédé que via la variable avec laquelle il a été créé.

1.3.4. Boxing et unboxing de types valeur

Une valeur peut être transtypée à la classe objet (object : la classe de base ultime pour tous les types valeur et référence) ou a une interface qui l'implémente. Ce processus est appelé boxing. L'opération inverse s'appelle unboxing.
Code Résultat Remarques
using System;

namespace Exemples
{
    class BoxingEtUnboxingDeTypesValeur1
    {
        static void Main(string[] args)
        {
            int x = 33;
            Console.WriteLine(x); // affiche 33
            object o = x; // boxing de int
            Console.WriteLine(o); // affiche 33
            int y = (int)o; // unboxing de int
            Console.WriteLine(y); // affiche 33
            try
            {
                string z = (string)o; // unboxing de int dans string impossible
            }
            catch (Exception e)
            {
                Console.WriteLine(e.GetType()); // System.InvalidCastException
            }
            Console.ReadKey();
        }
    }
}
33
33
33
System.InvalidCastException
Ici, nous faisons du boxing et de l'unboxing avec un type valeur int.
Lorsqu'un type valeur est mis en boite (boxed), un nouveau type référence est créé pour contenir une copie de type valeur. L'unboxing copie la valeur du type référence à un type valeur. L'unboxing requiert un transtypage explicite. Une vérification est faite afin de s'assurer que le type valeur vers lequel vous souhaitez le convertir correspond bien au type contenu dans le type référence.
Une erreur InvalidCastException survient si la vérification échoue.
using System;
using System.Collections;

namespace Exemples
{
    class BoxingEtUnboxingDeTypesValeur2
    {
        static void Main(string[] args)
        {
            Queue q = new Queue();
            q.Enqueue(33); // boxing de int
            q.Enqueue(" CH "); // boxing de " CH "
            q.Enqueue(91); // boxing de int
            Console.Write((int)q.Dequeue()); // unboxing de int
            Console.Write((string)q.Dequeue()); // unboxing de string
            Console.WriteLine((int)q.Dequeue()); // unboxing de int
            Console.ReadKey();
        }
    }
}
33 CH 91
L'utilisation des classes de collection est un bon exemple de technique de boxing et d'unboxing. Ici, nous utilisons la classe Queue avec des types valeurs.

1.4. Types prédéfinis

Tous les alias des types prédéfinis de C# se trouvent dans l'espace de noms System. Par exemple, il n'y a qu'une différence syntaxique entre ces deux déclarations :
int i = 33;
System.Int32 i = 33;

1.4.1. Types entier

Ce tableau donne les types entier et leur caractéristiques :
Type C# Type System Taille Signé
sbyte System.SByte 1 octet Oui
short System.Int16 2 octets Oui
int System.Int32 4 octets Oui
long System.Int64 8 octets Oui
byte System.Byte 1 octet Non
ushort System.UInt16 2 octets Non
uint System.UInt32 4 octets Non
ulong System.UInt64 8 octets Non
Il y a quelques régles qui s'appliquent aux entiers :
Code Résultat Remarques
using System;

namespace Exemples
{
    class TypesEntier1
    {
        static void Main(string[] args)
        {
            int x = 33;
            // utilisation du préfixe 0x pour hexadécimal
            ulong y = 0x21; // 2 * 16 + 1 = 33
            Console.WriteLine((ulong)x - y); // affiche 0
            Console.ReadKey();
        }
    }
}
0
Les entiers littéraux peuvent utiliser aussi bien une notation décimal qu'hexadécimale.
using System;

namespace Exemples
{
    class TypesEntier2
    {
        static void Main(string[] args)
        {
            Console.WriteLine((0xFF).GetType()); // 1 octet => System.Int32
            Console.WriteLine((0xFFFF).GetType()); // 2 octets => System.Int32
            Console.WriteLine((0xFFFFFF).GetType()); // 3 octets => System.Int32
            Console.WriteLine((0xFFFFFFFF).GetType()); // 4 octets => System.UInt32
            Console.WriteLine((0xFFFFFFFFFF).GetType()); // 5 octets => System.Int64
            Console.WriteLine((0xFFFFFFFFFFFF).GetType()); // 6 octets => System.Int64
            Console.WriteLine((0xFFFFFFFFFFFFFF).GetType()); // 7 octets => System.Int64
            Console.WriteLine((0xFFFFFFFFFFFFFFFF).GetType()); // 8 octets => System.UInt64
            Console.ReadKey();
        }
    }
}
System.Int32
System.Int32
System.Int32
System.UInt32
System.Int64
System.Int64
System.Int64
System.UInt64
Lorsqu'un type entier littéral est valide pour différents types entiers, le type par défaut est choisi dans cet ordre : int, uint, long et ulong.
using System;

namespace Exemples
{
    class TypesEntier3
    {
        static void Main(string[] args)
        {
            Console.WriteLine(((ulong)0xFF).GetType()); // 8 octets => System.UInt64
            Console.WriteLine((0xFFUL).GetType()); // 8 octets => System.UInt64
            Console.WriteLine((255UL).GetType()); // 8 octets => System.UInt64
            Console.WriteLine((255U).GetType()); // 8 octets => System.UInt32
            Console.WriteLine((255L).GetType()); // 8 octets => System.Int64
            Console.ReadKey();
        }
    }
}
System.UInt64
System.UInt64
System.UInt64
System.UInt32
System.Int64
Les suffixes suivants peuvent être utilisés pour spécifier explicitement le type choisi :
  • U : uint et ulong
  • L : long ou ulong
  • UL : ulong

1.4.1.1. Convertir des entiers

Une conversion implicite entre des types entier est permise lorsque le type vers lequel vous souhaitez le convertir contient toutes les valeurs possibles du type à convertir. Sinon, une conversion explicite est requise.
Code Résultat Remarques
using System;

namespace Exemples
{
    class ConvertirDesEntiers
    {
        static void Main(string[] args)
        {
            int x = 123456; // 123456 = 0x1E240
            Console.WriteLine(x); // affiche 1213456
            long y = x; // conversion implicite, pas d'information perdue
            Console.WriteLine(y); // affiche 1213456
            short z = (short)x; // conversion explicite, tronque x à 2 octets => 0xE240 = -7616
            Console.WriteLine(z); // affiche -7616
            Console.ReadKey();
        }
    }
}
123456
123456
-7616
Vous pouvez convertir implicitement un int vers un long, mais une conversion explicite est requise pour convertir un int vers un short.

1.4.2. Types nombre à virgule flottante

Type C# Type System Taille
float System.Single 4 octets
double System.Double 8 octets

Code Résultat Remarques
using System;

namespace Exemples
{
    class TypesNombreAVirguleFlottante
    {
        static void Main(string[] args)
        {
            float g = 9.81f;
            // affiche System.Single : 9,81
            Console.WriteLine(g.GetType() +  " : " + g.ToString());
            double N = 6.02E23;
            // affiche System.Double : 6,02E+23
            Console.WriteLine(N.GetType() + " : " + N.ToString());
            Console.ReadKey();
        }
    }
}
System.Single : 9,81
System.Double : 6,02E+23
Les nombres littéraux à virgule flottante peuvent utiliser une notation décimale ou exponentielle.
Un flottant littéral requiert le suffixe f ou F.
On peut choisir le suffixe d ou D pour un double littéral.

1.4.2.1. Convertir des nombres à virgule flottante

Une conversion implicite d'un flottant vers un double ne provoque aucune perte d'information et est permise, mais pas l'inverse.
Une conversion implicite d'un int, d'un uint et d'un long vers un float, et d'un long vers un double, est permise.
Code Résultat Remarques
using System;

namespace Exemples
{
    class ConvertirDesNombreAVirguleFlottante1
    {
        static void Main(string[] args)
        {
            int a = 33;
            int b = 91;
            // y = 33 * 33,91 + 91 = 1210,03
            float y = a * 33.91f + b;
            // affiche 1210,03
            Console.WriteLine(y.ToString());
            Console.ReadKey();
        }
    }
}
1210,03
Si cet exemple utilisait des valeurs plus grandes, la précision serait perdue.
using System;

namespace Exemples
{
    class ConvertirDesNombreAVirguleFlottante2
    {
        static void Main(string[] args)
        {
            float grosso = 33.91f;
            // affiche 33,91
            Console.WriteLine(grosso.ToString());
            int famille = (int)grosso;
            // affiche 33
            Console.WriteLine(famille.ToString());
            Console.ReadKey();
        }
    }
}
33,91
33
Toutes les autres conversions entre des types entier et des types flottant doivent être explicites.

1.4.3. Type décimal

Le type decimal contient 28 chiffres et la position de la virgule sur ces chiffres. A l'inverse du type flottant, il offre une plus grande précision mais une plage de valeurs moins élevée. Il est particulièrement utile pour des calculs financiers, pour lesquels la combinaison de sa haute précision et de sa capacité à stocker des nombres en base 10 sans erreur d'arrondi est très appréciable.
Code Résultat Remarques
using System;

namespace Exemples
{
    class TypeDecimal
    {
        static void Main(string[] args)
        {
            float f = 13391.3391f;
            // affiche 13391,34
            Console.WriteLine(f.ToString());
            decimal d = 13391.3391m;
            // affiche 13391,3391
            Console.WriteLine(d.ToString());
            Console.ReadKey();
        }
    }
}
13391,34
13391,3391
Dans cet exemple, on voit la capacité du type décimal à stocker des nombres en base 10 sans erreur.

1.4.3.1. Convertir des décimaux

Code Résultat Remarques
using System;

namespace Exemples
{
    class ConvertirDesDecimaux
    {
        static void Main(string[] args)
        {
            int i = 133913391;
            // affiche 133913391
            Console.WriteLine(i.ToString());
            decimal d = i;
            // affiche 133913391
            Console.WriteLine(d.ToString());
            float f = (float)d;
            // affiche 1,339134E08
            Console.WriteLine(f.ToString());
            Console.ReadKey();
        }
    }
}
133913391
133913391
1,339134E08
Une conversion implicite de tous types entier vers un type décimal est permise parce qu'un décimal peut représenter toutes valeurs de type entier.
Une conversion d'un type décimal vers un type flottant ou vice versa nécessite une conversion explicite.

1.4.4. Type char

Le type char représente un caractère Unicode.
Type C# Type System Taille
char System.Char 2 octets
Un char littéral peut être :
Code Résultat Remarques
using System;

namespace Exemples
{
    class TypeChar
    {
        static void Main(string[] args)
        {
            // caratères simples
            char c3 = '3';
            char c9 = '9';
            char c1 = '1';
            // unicode
            char cC = '\u0043';
            // unsigned short hexadecimal
            char cH = '\x0048';
            // caractères d'échappement
            char CR = '\r';
            char LF = '\n';
            // construction de chaînes de caractères
            string trenteTrois = c3.ToString() + c3.ToString();
            string CH = cC.ToString() + cH.ToString();
            string quatreVingtOnze = c9.ToString() + c1.ToString();
            string CRLF = CR.ToString() + LF.ToString();
            // affiche 33
            //         CH
            //         91
            string message = trenteTrois + CRLF + CH + CRLF + quatreVingtOnze ;
            Console.WriteLine(message);
            Console.ReadKey();
        }
    }
}
33
CH
91
Dans cet exemple, on voit apparaître :
  • des caractères simples,
  • un caractère Unicode,
  • un unsigned short hexadecimal et
  • des caractères d'échappement.

1.4.4.1. Caractères d'échappement

Char Signification Valeur
\' Apostrophe 0x0027
\" Guillemet 0x0022
\\ Antislash 0x005C
\0 Null 0x0000
\a Alerte 0x0007
\b Retour arrière 0x0008
\f Avance chariot 0x000C
\n Nouvelle ligne 0x000A
\r Retour chariot 0x000D
\t Tabultation horizontale 0x0009
\v Tabultation verticale 0x000B

1.4.4.2. Convertir des char

Code Résultat Remarques
using System;

namespace Exemples
{
    class ConvertirDesChar
    {
        static void Main(string[] args)
        {
            char A = 'A';
            Console.WriteLine(A); // affiche A
            int intA = A;
            Console.WriteLine(intA); // affiche 65
            byte byteA = (byte)A;
            Console.WriteLine(byteA); // affiche 65
            Console.ReadKey();
        }
    }
}
A
65
65
La conversion implicite d'un char vers la plupart des types numériques fonctionne si ce dernier peut être traité comme un unsigned short.
Dans le cas contraire, une conversion explicite est requise.

1.4.5. Type bool

Le type bool est une valeur logique qui peut être assignée aux littéraux true ou false. Bien qu'une valeur booléenne ne requiert qu'un seul bit (0 ou 1), elle occupe un octet de mémoire, puisque ce dernier est la plus petite unité qui puisse être adressée sur la plupart des architectures processeur. Chaque élément dans un tableau de booléens occupe deux octets de mémoire.
Type C# Type System Taille
bool System.Boolean 1 octet / 2 octets

1.4.5.1. Convertir des booléens

Code Résultat Remarques
using System;

namespace Exemples
{
    class ConvertirDesBooleens
    {
        static void Main(string[] args)
        {
            bool flag = true;
            Console.WriteLine(flag); // affiche True
            int intFlag = convertBool(flag);
            Console.WriteLine(intFlag); // affiche 1
            flag = false;
            Console.WriteLine(flag); // affiche False
            intFlag = convertBool(flag);
            Console.WriteLine(intFlag); // affiche 0
            Console.ReadKey();
        }
        static int convertBool(bool abFlag)
        {
            if (abFlag)
            {
                // vrai
                return 1;
            } else
            {
                // faux
                return 0;
            }
        }
    }
}
True
1
False
0
Aucune conversion de booléens vers des types numériques ne peut être réalisée et réciproquement...

1.4.6. Type objet

La classe objet représente le type de base ultime pour les types valeur et référence (CF boxing et unboxing).
Type C# Type System Taille
object System.Object 0 octet / 8 octets et plus

1.4.7. Type string

La chaîne C# représente une séquence de caractères Unicode.
Les chaînes peuvent également être créées avec les chaînes verbatim littérales (verbatim : reproduction intégrale des propos prononcés par l'interviewé ; compte-rendu fidèle). Les chaînes verbatim littérales commencent par @.
Type C# Type System Taille
string System.String Minimum 20 octets

Code Résultat Remarques
using System;

namespace Exemples
{
    class TypeString
    {
        static void Main(string[] args)
        {
            string s1 = "\\\\aci-prod\\DIRTEC";
            string s2 = @"\\aci-prod\DIRTEC";
            Console.WriteLine(s1 == s2); // affiche True
            s1 = "Première ligne\r\nSeconde ligne";
            s2 = @"Première ligne
Seconde ligne";
            Console.WriteLine(s1 == s2); // affiche True
            Console.ReadKey();
        }
    }
}
True
True
Dans ces deux exemples, à chaque fois, les deux chaînes s1 et s2 sont identiques.