La version actuelle est .NET 6 (2021).
dotnet <commande> <options>
> dotnet new <template> -o <output directory>
Type d’application | Template |
---|---|
Console | console |
Bibliothèque de classes | classlib |
ASP.NET (vide) | web |
ASP.NET (API) | webapi |
ASP.NET (MVC) | mvc |
> dotnet new gitignore
.gitignore
permet d’exclure certains fichiers/dossiers de la gestion des versions avec Git. Il le plus souvent s’agit de fichiers locaux (exemple : configuration de l’environnement de développement) ou de fichiers recréés systématiquement par le processus de génération de l’application..gitignore
adapté aux projets .NET.> dotnet add package <name>
> dotnet list package
> dotnet run
Si nécessaire, effectue la restauration des dépendances du projet (équivalent de dotnet restore
).
> dotnet watch run
Pour une application web, jnjecte un script qui met à jour le contenu affiché par le navigateur lorsque des fichiers surveillés sont modifiés.
Jeu d’instructions élémentaires comprises par une famille de processeurs.
str:
.ascii "Bonjour\n"
.global _start
_start:
movl $4, %eax
movl $1, %ebx
movl $str, %ecx
movl $8, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80
“Un algorithme est une méthode de calcul qui indique la démarche à suivre pour résoudre une série de problèmes équivalents en appliquant dans un ordre précis une suite finie de règles.” (Académie Française)
Début
Sortir une casserole
Mettre de l'eau dans la casserole
Ajouter du sel
Mettre la casserole sur le feu
Tant que l'eau ne bout pas
Attendre
Sortir les pâtes du placard
Verser les pâtes dans la casserole
Tant que les pâtes ne sont pas cuites
Attendre
Verser les pâtes dans une passoire
Egoutter les pâtes
Verser les pâtes dans un plat
Goûter
Tant que les pâtes sont trop fades
Ajouter du sel
Goûter
Si on préfère le beurre à l'huile
Ajouter du beurre
Sinon
Ajouter de l'huile
Fin
“Le programmeur est un créateur d’univers dont il est seul responsable.” (Joseph Weizenbaum).
# Le projet est créé dans le sous-répertoire HelloCSharp
dotnet new console -o HelloCSharp
Décrit la configuration du projet.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
Fichier source principal contenant le code C#.
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
# Cette commande doit être lancée dans le répertoire HelloCSharp
dotnet run
2
, 3.14
, "Hello World!"
..
"..."
.;
en C#.{ ... }
.//...
(monoligne) ou /* ... */
(multilignes)._
.abstract as base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false file finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void volatile while
totalFacturesClient
.CompteBancaire
.// Ceci est un commentaire tellement long qu'il est réparti
// sur plusieurs lignes.
/*
Encore
un
commentaire.
*/
=
.int x; // Déclaration : aucune valeur dans la variable x
x = 3; // Affectation de la valeur 3 à x
int y = 5; // Déclaration et initialisation de la variable y
y = "Coucou"; // NOK : valeur incompatible avec le type de y
{ ... }
ainsi que ses sous-blocs éventuels.int a = 1;
{
a = 2; // OK : a est déclarée dans le bloc parent
int b = 4; // La portée de b se limite au bloc actuel
}
Console.WriteLine(a); // OK : a est déclarée dans le bloc courant
Console.WriteLine(b); // NOK : b n'est pas visible ici
Extrait du vaste éventail de types prédéfinis du C# :
Mot-clé | Nom complet | Description | |
---|---|---|---|
int | System.Int32 | Entier (signé) | |
double | System.Double | Réel | |
bool | System.Boolean | Booléen | |
char | System.Char | Caractère | |
string | System.String | Chaîne de caractères |
Plusieurs valeurs constantes sont associées à certains types.
int i = int.MaxValue; // Plus grande valeur gérée par int
int j = int.MinValue; // Plus petite valeur gérée par int
double a = double.MaxValue; // Plus grande valeur gérée par double
double b = double.MinValue; // Plus petite valeur gérée par double
double c = double.Epsilon; // Plus petite valeur réelle positive
double d = double.NaN; // Pas un nombre (Not a Number)
double e = double.PositiveInfinity; // +∞
double f = double.NegativeInfinity; // −∞
double
.float f = 1.2f; // Réel sur 32 bits
double d = 1.2d; // Réel sur 64 bits
decimal m = 1.2m; // Réel sur 128 bits
Les types C# se divisent en deux catégories.
Types valeur : la variable contient directement la valeur qu’elle stocke.
Types référence : la variable contient une référence vers la valeur ($\approx$ son adresse dans la mémoire). Cette indirection permet d’optimiser certaines opérations (comparaison, copie, etc).
Tous les exemples précédents sont des types valeur, sauf string
(mais qui s’utilise comme un type valeur).
?
.int a = null; // NOK : type valeur non nullable
int? b = null; // OK, type nullable
null
ou une valeur potentiellement égale à null
.?
autorise null
.string s; // OK, valeur de s = null (variable inutilisable en l'état)
s = null; // NOK, type référence non nullable (C# 8+)
string? t = null; // OK, type nullable
En C#, le type d’une variable ne peut plus être modifié après sa déclaration (typage statique).
Une conversion implicite a lieu quand la nouvelle valeur est directement compatible avec le type actuel (exemples : entier vers entier de plus grande précision, entier vers réel).
int i = "Bonjour"; // NOK, valeur incompatible avec le type int
int n = 2147483647;
double d = n; // OK, conversion implicite
// Le type entier long peut stocker n'importe quel int
long l = n; // OK, conversion implicite
{type}.Parse
ou Convert.To{type}
.int x = (int)12.3; // OK : transtypage possible, x vaut 12
int y = int.Parse("123"); // OK, y vaut 123
int z = int.Parse("12.3"); // NOK : nouvelle valeur non entière
true
ou false
.bool?
true
.bool b = true;
bool? n = null; // OK : type nullable
bool p = Convert.ToBoolean(1); // p vaut true
bool q = Convert.ToBoolean(0); // r vaut false
''
ou sa valeur Unicode transtypée.\
.char c = 'a';
int cVal = c; // cVal vaut 97, la valeur Unicode de 'a'
char cBis = (char)97; // Initialisation à partir de la valeur Unicode
Extrait de la liste complète des séquences d’échappement C# :
Séquence | Valeur |
---|---|
\' | Guillemet simple |
\" | Guillemet double |
\\ | Antislash |
\n | Saut de ligne |
\t | Tabulation |
{chaîne}.Length
renvoie le nombre de caractères d’une chaîne.[]
permet d’accéder à un caractère de la chaîne à partir de son rang.string s = "abc";
char premier = s[0]; // 'a'
char dernier = s[s.Length - 1]; // 'c'
Placé devant son début, le caractère @
permet de créer une chaîne verbatim dans laquelle on peut ajouter des caractères spéciaux sans les échapper.
// Ces deux chaînes contiennent "c:\Windows\Temp"
string c1 = "c:\\Windows\\Temp";
string c2 = @"c:\Windows\Temp";
void
.Opérateur | Rôle |
---|---|
+ | Addition |
- | Soustraction |
* | Multiplication |
/ | Division |
% | Modulo |
Ne pas confondre ==
et =
(affectation) !
Opérateur | Rôle |
---|---|
== | Egal à |
!= | Différent de |
< | Inférieur |
<= | Inférieur ou égal |
> | Supérieur |
>= | Supérieur ou égal |
Opérateur | Rôle |
---|---|
&& | ET logique |
|| | OU logique |
! | NON logique |
Elle est gérée comme en mathématiques à l’aide de parenthèses ()
.
let e = 3 + 2 * 4; // e contient la valeur 11
int f = (3 + 2) * 4; // f contient la valeur 20
int q = 5 / 2; // q vaut 2
int r = 5 % 2; // r vaut 1
double d = 5 / 2.0; // d vaut 2,5
double e = 5 / 2d; // e vaut 2,5
double f = (double)5 / 2; // f vaut 2,5
int z = 0;
int g = 5 / z; // NOK, division par zéro à l'exécution
++
et --
permettent resp. d’augmenter et de diminuer de 1 la valeur d’une variable.variable = variable ± 1
.int a = 3;
a++; // a vaut 4
int b = a++; // a vaut 5, b vaut 4 !
int c = ++a; // a et c valent 6
variable {opérateur}= valeur
, ils équivalent à variable = variable {operateur} valeur
int i = 10;
i += 5; // Equivaut à i = i + 5, donc i vaut 15
i /= 3; // i vaut 5
int x = int.MaxValue; // Plus grand int possible
x++;
Console.WriteLine(x == int.MinValue); // true !
L’arithmétique sur les types à virgule flottante peut entrainer des imprécisions et des erreurs d’arrondi.
Console.WriteLine(5.32 + 2.23); // 7,550000000000001
Console.WriteLine(5.32m + 2.23m); // 7,55
Elle s’effectue à l’aide de l’opérateur +
lorsqu’au moins l’une des deux opérandes est une chaîne.
string s = "I " + "feel " + "good!";
Console.WriteLine(s);
int nbFamilles = 5;
string ecole = "ENSC";
string t = "Il y a " + nbFamilles + " familles à l'" + ecole;
Console.WriteLine(t); // "Il y a 5 familles à l'ENSC"
$
permet de créer une chaîne interpolée dans laquelle on peut ajouter des expressions entre accolades {}
.int nbFamilles = 5;
string ecole = "ENSC";
string u = $"Il y a {nbFamilles} familles à l'{ecole}";
Console.WriteLine(u); "Il y a 5 familles à l'ENSC"
int a = 3;
Console.WriteLine(a == 3); // true
Console.WriteLine(a != 3); // false
// ET logique
Console.WriteLine(true && true); // true
Console.WriteLine(true && false); // false
Console.WriteLine(false && true); // false
Console.WriteLine(false && false); // false
// OU logique
Console.WriteLine(true || true); // true
Console.WriteLine(true || false); // true
Console.WriteLine(false || true); // true
Console.WriteLine(false || false); // false
// NON logique
Console.WriteLine(!true); // false
Console.WriteLine(!false); // true
Dans une condition composée avec &&
, la seconde sous-condition n’est évaluée que si la première vaut true
(évaluation abrégée).
int a = 3;
string s = "Hi";
Console.WriteLine((a == 3) && (s == "Hello")); // false
Console.WriteLine((a == 3) || (s == "Hello")); // true
// La sous-condition (s == "Hi") n'est pas évaluée
// puisque (a != 3) vaut false
Console.WriteLine((a != 3) && (s == "Hi")); // false
if
permet de soumettre l’exécution d’une partie du programme à une condition, qui doit être vérifiée.if (condition) {
// Instructions exécutées si la condition est vérifiée
}
if (condition)
// Seule instruction exécutée si la condition est vérifiée
if
, le mot-clé else
permet d’exprimer une alternative.if
est vérifiée, alors les instructions du bloc de code associé seront exécutées, sinon ce seront celles du bloc associé au else
.if/else
permet de créer un branchement logique dans le code.if (condition) {
// Instructions exécutées si la condition est vérifiée
}
else {
// Instructions exécutées si la condition n'est pas vérifiée
}
int nombre = 1;
if (nombre > 0)
{
Console.WriteLine($"{nombre} est positif");
}
else
{ // nombre <= 0
if (nombre < 0)
{
Console.WriteLine($"{nombre} est négatif");
}
else
{ // nombre == 0
Console.WriteLine($"{nombre} est nul");
}
}
if/else
constitue une seule instruction : les accolades peuvent donc être omises.elif
en C# !int nombre = 1;
if (nombre > 0)
Console.WriteLine($"{nombre} est positif");
else if (nombre < 0)
Console.WriteLine($"{nombre} est négatif");
else
Console.WriteLine($"{nombre} est nul");
L’instruction ?:
renvoie la valeur d’une expression parmi deux, selon la valeur d’une condition booléenne : (condition) ? valeur_si_vraie : valeur_si_fausse
Appelée instruction conditionnelle ternaire, elle constitue une alternative plus concise au if/else
dans certains scénarions.
double temperature = 38.5;
string etat = (temperature > 38) ? "malade" : "bien portant";
Console.WriteLine(etat); // "malade"
L’instruction switch
permet d’exécuter un bloc parmi plusieurs selon la valeur d’une expression.
string meteo = "orage";
switch (meteo)
{
case "soleil":
Console.WriteLine("Sortez en t-shirt");
break;
case "vent":
Console.WriteLine("Sortez en coupe-vent");
break;
default:
Console.WriteLine("Restez au chaud à la maison !");
break;
}
while
permet de répéter des instructions tant qu’une condition est vérifiée.while
est évaluée :while
(appelé corps de la boucle) est exécuté, puis la condition est de nouveau évaluée.while (condition) {
// Instructions exécutées tant que la condition est vérifiée
}
while
dont la condition ne peut pas devenir fausse ne s’arrête jamais.int nombre = 1;
while (nombre <= 5)
{
Console.WriteLine(nombre);
// nombre n'est jamais modifiée => condition toujours vraie
}
for (initialisation; condition; étape) {
// Instruction executées tant que la condition est vérifiée
}
for
est fréquemment utilisée avec une variable (appelée compteur) qui permet d’identifier et de numéroter les tours de boucle.i
, j
ou k
.int i;
for (i = 1; i <= 5; i++)
{
Console.WriteLine(i);
}
Console.WriteLine(i); // 6
Afin de limiter sa portée au corps de la boucle, on peut déclarer la variable compteur dans l’initialisation d’une boucle for
.
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i + 1);
}
Console.WriteLine(i); // NOK : i est inaccessible ici
for
intègre la gestion du compteur, mais implique que le nombre de tours de boucle soit connu à l’avance.while
. En revanche, attention aux boucles infinies !Variante de la boucle while
dans laquelle le corps de la boucle est toujours exécuté au moins une fois.
do
{
// Instructions exécutées tant que la condition est vérifiée
}
while (condition);
Début
Sortir une casserole
Mettre de l'eau dans la casserole
Ajouter du sel
Mettre la casserole sur le feu
Tant que l'eau ne bout pas
Attendre
Sortir les pâtes du placard
Verser les pâtes dans la casserole
Tant que les pâtes ne sont pas cuites
Attendre
Verser les pâtes dans une passoire
Egoutter les pâtes
Verser les pâtes dans un plat
Goûter
Tant que les pâtes sont trop fades
Ajouter du sel
Goûter
Si on préfère le beurre à l'huile
Ajouter du beurre
Sinon
Ajouter de l'huile
Fin
Début
Faire bouillir de l'eau
Cuire les pâtes dans l'eau
Egoutter les pâtes
Assaisonner les pâtes
Fin
La recette est décomposée en sous-étapes :
// Définition du sous-programme DireBonjour
void DireBonjour()
{
Console.WriteLine("Bonjour !");
}
Console.WriteLine("Début du programme");
DireBonjour(); // Appel du sous-programme DireBonjour
Console.WriteLine("Fin du programme");
Sauf cas particulier (voir plus loin), l’utilisation peut précéder la définition dans le code.
Console.WriteLine("Début du programme");
DireBonjour(); // "Bonjour !"
Console.WriteLine("Fin du programme");
void DireBonjour()
{
Console.WriteLine("Bonjour !");
}
return
provoque la fin de l’exécution d’un sous-programme et définit l’expression associée comme sa valeur de retour.string DireBonjour2()
{
return "Bonjour !";
}
Console.WriteLine("Début du programme");
string message = DireBonjour2();
Console.WriteLine(message); // "Bonjour !"
Console.WriteLine("Fin du programme");
// Procédure
void DireBonjour()
{
Console.WriteLine("Bonjour !");
}
// Fonction
string DireBonjour2()
{
return "Bonjour !";
}
// NOK : aucune valeur de retour (pas de return)
int CalculerUnTruc()
{
Console.WriteLine("Je calcule...");
}
// NOK : tous les chemins de code ne retournent pas une valeur
int CalculerUnTruc2()
{
if (2 > 3)
return 1;
}
// NOK : conversion implicite impossible d'une chaîne vers un entier
int CalculerUnTruc3()
{
return "1";
}
Ne provoque pas d’erreur, mais risque de perte d’information.
Console.WriteLine("Début du programme");
DireBonjour2(); // N'affiche rien !
Console.WriteLine("Fin du programme");
string DireBonjour3()
{
string message = "Bonjour !";
return message;
}
Console.WriteLine(DireBonjour3()); // "Bonjour !"
Console.WriteLine(message); // NOK : message n'existe pas ici
Il est possible de déclarer des variables portant des noms identiques mais ayant des portées différentes.
string DireBonjour4()
{
// Déclaration d'une variable locale message
string message = "Bonjour";
return message + " !";
}
// Déclaration d'une variable message dans le programme principal
string message = DireBonjour4();
Console.WriteLine(message); // ?
Visibles dans tous les sous-programmes si elles sont déclarées avant.
string message = "Bonjour";
string DireBonjour5()
{
return message + " !";
}
Console.WriteLine(DireBonjour5()); // ?
string message = "Bonjour";
string DireBonjour6()
{
// La variable locale message masque la variable globale du même nom
string message = "Hello";
return message + " !";
}
Console.WriteLine(message); // ?
Console.WriteLine(DireBonjour6()); // ?
string DireBonjour7(string prenom)
{
return $"Bonjour, {prenom} !";
}
Console.WriteLine(DireBonjour7("Alex")); // "Bonjour, Alex !"
// Ici l'argument est "Alex"
Console.WriteLine(DireBonjour7("Alex")); // "Bonjour, Alex !"
// Ici l'argument est "Marco"
Console.WriteLine(DireBonjour7("Marco")); // "Bonjour, Marco !"
void Presenter(string prenom, int age)
{
Console.WriteLine($"Tu es {prenom} et tu as {age} ans");
}
Presenter("Garance", 14); // "Tu es Garance et tu as 14 ans"
Presenter("Gaëlle"); // NOK : pas de valeur pour le paramètre age
Presenter(10, "Prosper"); // NOK : types des arguments incompatibles avec ceux des paramètres
Presenter(age: 10, prenom: "Prosper"); // "Tu es Prosper et tu as 10 ans"
Comme d’autres langages, C# permet de définir des valeurs par défaut pour les paramètres non définis lors d’un appel.
void Presenter2(string prenom = "inconnu", int age = 0)
{
Console.WriteLine($"Tu es {prenom} et tu as {age} ans");
}
Presenter2("Garance", 14); // "Tu es Garance et tu as 14 ans"
Presenter2("Gaëlle"); // "Tu es Gaëlle et tu as 0 ans"
Presenter2(age: 10); // "Tu es inconnu et tu as 10 ans"
ref
et out
.int
est un type valeur : les valeurs sont directement stockées dans les variables.
int nombre1;
nombre1 = 5;
int nombre2 = 3;
nombre2 = nombre1;
nombre1 = 10;
Console.WriteLine(nombre1); // 10
Console.WriteLine(nombre2); // ?
int[]
est un type référence : les variables stockent des références ($\approx$ adresses mémoire) vers les valeurs.
int[] tab1;
tab1 = new int[] { 1, 2, 3 };
int[] tab2 = { 4, 5, 6 };
tab2 = tab1;
tab1[0] = 0;
Console.WriteLine(string.Join(" ", tab1)); // 0 2 3
Console.WriteLine(string.Join(" ", tab2)); // ?
void Augmenter(int unNombre)
{
Console.WriteLine($"Avant l'augmentation, unNombre = {unNombre}");
unNombre++;
Console.WriteLine($"Après l'augmentation, unNombre = {unNombre}");
}
int nombre = 5;
Console.WriteLine($"Avant l'appel, nombre = {nombre}"); // 5
Augmenter(nombre);
Console.WriteLine($"Après l'appel, nombre = {nombre}"); // ?
void AugmenterTab(int[] unTab)
{
Console.WriteLine($"Avant l'augmentation, unTab = {string.Join(" ", unTab)}");
for (int i = 0; i < unTab.Length; i++)
unTab[i]++;
Console.WriteLine($"Après l'augmentation, unTab = {string.Join(" ", unTab)}");
}
int[] tab = { 1, 2, 3 };
Console.WriteLine($"Avant l'appel, tab = {string.Join(" ", tab)}"); // 1 2 3
AugmenterTab(tab);
Console.WriteLine($"Après l'appel, tab = {string.Join(" ", tab)}"); // ?
ref
indique que ce paramètre est passé par référence.ref
doit également être utilisé lors de l’appel du sous-programme.out
est similaire à ref
mais n’oblige pas à initialiser la valeur de l’argument avant l’appel.void Permuter(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int nombre1 = 5;
int nombre2 = 3;
Permuter(ref nombre1, ref nombre2);
Console.WriteLine($"{nombre1} {nombre2}"); // ?
void ResetTab(int[] unTab)
{
unTab = new int[] { 0, 0, 0 };
}
void ResetTabRef(ref int[] unTab)
{
unTab = new int[] { 0, 0, 0 };
}
int[] tab = { 1, 2, 3 };
ResetTab(tab);
Console.WriteLine(string.Join(" ", tab)); // ?
ResetTabRef(ref tab);
Console.WriteLine(string.Join(" ", tab)); // ?
void Init(out int a)
{
a = 10;
}
void Init2(out int a)
{
// NOK : le paramètre doit être modifié
}
int nombre;
Init(out nombre);
Console.WriteLine(nombre); // ?
void InitTab(out int[] unTab)
{
unTab = new int[] { 1, 2, 3 };
}
int[] tab;
InitTab(out tab);
Console.WriteLine(string.Join(" ", tab)); // ?