🥃 C##

Indication
Cette page à pour but de découvrir le langage C#, le framework dotnetcore et quelques concepts de base de l’OOP.
C# est un langage de programation édité par Microsoft depuis 2002 et destiné à développer sur la plateforme .NET et depuis 2016 sur .NET Core. Il est utilisé notamment pour développer des applications web, console, mobiles, web services, etc.
C# est un language de programmation avec les caractéristiques suivantes:
Paradigme: Structuré, impératif, orienté objet
Typage: Statique, fort, nominatif
Influencé par C++, Java
Multiplateforme via .NET Core
Extension de fichier:
.cs
Plateforme .NET#
C# est destiné à développer sur la plateforme .NET, le coeur de cette pile technologique est le framework .NET composé:
des environnements Razor, ASP.NET, Winforms, WPF
d’une bibliothèque de classes
du CLR ou Common Language Runtime
.NET Core#
Framework libre et open source pour les OS à noyaux NT et Unix, il comprend:
CoreCLR équivalent du CLR de .NET
dotnet ligne de commande pour la création, compilation, exécution, etc.
Indication
Téléchargez le kit SDK .NET Core pour tester .NET Core sur votre ordinateur Windows, macOS ou Linux.
Hello C#!#
Tout d’abord il convient de créer un nouveau projet .NET Core grâce à la commande dotnet
$ mkdir HelloWorld
$ cd HelloWorld
$ dotnet new console
using System;
namespace helloworld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
$ dotnet run
Hello World!
Félicitations ! Votre environnement de développement est fonctionnel et vous venez de créer votre première application C# !
Syntaxe de base de C##
Pour récupérer la saisie d’un utilisateur et la placer dans une variable, on utilise l’instruction suivante :
int integer_from_user = int.Parse(Console.ReadLine());
Pour afficher une belle chaine de caractères bien formatée:
string message = String.Format("Value from integer_from_user {0}.", integer_from_user);
Console.WriteLine(message);
Types en C##
Les types les plus communs et utilisés:
Entiers |
|
Flottants |
|
Caractères |
|
Chaînes de caractères |
|
Un char
sert à représenter le code UNICODE
d’un caractère. Il est possible d’affecter à une telle variable toute valeur du code UNICODE
entourée de simples quotes.
char char_a = 'B';
Une chaîne de caractères, se déclarant avec le mot-clé string
, est une succession de caractères (aucun, un ou plusieurs). Les littéraux de ce type se délimitent par des double quotes et l’instruction Saisie
s’écrit sans Parse
:
string name = "Ben Kenobi";
Console.WriteLine(name.Length);
Traitements conditionnels en C##
Console.WriteLine("Enter a value");
int value = int.Parse(Console.ReadLine());
if (value == 0)
{
Console.WriteLine("Value is zero.");
}
else if (value >= 0)
{
Console.WriteLine("Value is more or egal to zero");
}
else
{
Console.WriteLine("Value is less than zero");
}
Console.WriteLine("Bye!");
La formulation d’une condition se fait souvent à l’aide des opérateurs de comparaison. Les opérateurs de comparaison disponibles sont :
Égalité |
|
Différence |
|
Inférieur à, respectivement strict et large |
|
Supérieur à, respectivement strict et large |
|
Boucles en C##
Il existe trois types de boucle :
while
do .. while
for
while
Avec un instruction tant que Les instructions du corps de la boucle sont délimitées par des accolades. La condition est évaluée avant chaque passage dans la boucle, à chaque fois qu’elle est vérifiée, on exécute les instructions de la boucle.
Une fois que la condition n’est plus vérifiée, l’exécution se poursuit après l’accolade fermante.
int counter = 1;
while(counter <= 5)
{
Console.Write(String.Format("{0} ", counter));
counter++;
}
Console.Write("\n");
do while
Le faire tant que est analogue à celui de la boucle tant que à quelques détails près :
la condition est évaluée après chaque passage dans la boucle,
le corps de la boucle s’exécute tant que la condition est vérifiée.
int counter = 1;
do
{
Console.Write(String.Format("{0} ", counter));
counter++;
}
while (counter <= 5);
Console.Write("\n");
Indication
Un des usages les plus courant de la boucle do ... while
est le contrôle de saisie.
int value_from_user;
do
{
Console.Write("Enter a positive or null int");
value_from_user = int.Parse(Console.ReadLine());
if (value_from_user < 0)
{
Console.WriteLine(String.Format("{0} is invalid", value_from_user));
}
}
while(value_from_user < 0);
Console.WriteLine(String.Format("Your input is {0}", value_from_user));
for
for (int counter = 1; counter <= 6; counter++)
{
Console.Write(String.Format("{0} ", counter));
}
Console.Write("\n");
Indication
On utilise une boucle for
lorsque l’on connait en entrant dans la boucle combien d’itérations devront être faites. Par exemple, n’utilisez pas une boucle for
pour contrôler une saisie !
Les tableaux en C##
Un tableau est un regroupement de variables de même type, il est identifié par un nom. Chacune des variables du tableau est numérotée, ce numéro s’appelle un indice. Chaque variable du tableau est donc caractérisée par le nom du tableau et son indice.
int[] integer_arr = new int [4];
integer_arr[0] = 1;
integer_arr[1] = 2;
Important
Il convient de préciser le type au moment de la déclaration du tableau ainsi que sa taille.
Il est possible de parcourir un tableau de façon séquentielle et en lecture seule grâce à l’instruction foreach
.
Cette instruction affecte à <variable>
pour chaque itération de la boucle un élément de <tableau>
.
On remarque au passage que le <type>
doit être celui des éléments de <tableau>
.
char[] alphabet = {'a', 'b', 'c', 'd'};
foreach (char letter in alphabet)
{
Console.WriteLine(letter);
}
Les procédures en C##
Une procédure est un ensemble d’instructions portant un nom. Pour définir une procédure on utilise la syntaxe:
class HelloWorld
{
public static void SayHello()
{
Console.WriteLine("Hello World");
}
static void Main(string[] args)
{
SayHello();
}
}
Indication
Vous pouvez définir autant de procédures que vous le voulez et vous pouvez appeler des procédures depuis des procédures.
Variables locales
Une procédure est un bloc d’instructions et est sujette aux mêmes règles que Main. Il est donc possible d’y déclarer des variables:
public static void Count()
{
int counter;
}
Important
Ces variables ne sont accessibles que dans le corps de la procédure, cela signifie qu’elles naissent au moment de leur déclaration et qu’elles sont détruites une fois la dernière instruction de la procédure éxécutée.
C’est pour cela qu’on les appelle variables locales. Une variable locale n’est visible qu’entre sa déclaration et l’accolade fermant la procédure.
Passage de paramètres
Il est possible que la valeur d’une variable locale d’une procédure ne soit connue qu’au moment de l’appel de la procédure.
public static void Count(int counter)
{
}
Indication
Il est possible de passer plusieurs valeurs en paramètre.
public static void Multiply(int facteur_gauche, int facteur_droite)
{
}
Les fonctions en C##
Une fonction est un sous-programme qui communique une valeur au sous-programme appelant. Cette valeur s’appelle valeur de retour, ou valeur retournée.
L’instruction servant à retourner une valeur est return
.
public static int ReturnOne()
{
return 1;
}
Passages de paramètre par référence
En C#, lorsque vous invoquez une fonction, toutes les valeurs des paramètres effectifs sont recopiés dans les paramètres formels. On dit dans ce cas que le passage de paramètre se fait par valeur.
Par conséquent seule la valeur de retour vous permettra de communiquer une valeur au programme appelant.
Lorsque vous passez un tableau en paramètre, la valeur qui est recopiée dans le paramètre formel est l’adresse de celui-ci (l’adresse est une valeur scalaire).
Toute modification effectuée sur les éléments d’un tableau dont l’adresse est passée en paramètre sera repercutée sur le paramètre effectif (i.e. le tableau d’origine).
Lorsque les modifications faites sur un paramètre formel dans un sous-programme sont repércutées sur le paramètre effectif, on a alors un passage de paramètre par référence.
Règles d’or
Les variables scalaires se passent en paramètre par valeur,
Les variables non scalaires se passent en paramètre par référence.
Forcer un passage par référence sur un type scalaire ?
Par l’utilisation du mot clé out
, il s’agit d’un modificateur de paramètre, la définition de la méthode d’appel doit utiliser explicitement ce mot clé.
public void ForceReferenceParameter(out int number)
{
number = 1977;
}
Who are you ?#
À faire
Réaliser l’analyse, la rédaction de l’algorithme puis le développement d’un programme équivalent à cette exécution:
Saisissez votre nom: Kenobi
Saisissez votre prénom: Ben
Saisissez votre âge: 57
Salutations Ben Kenobi, vous êtes âgé de 57 ans.
Nous allons améliorer notre programme précédent pour obtenir l’exécution suivante:
Saisissez votre nom: Kenobi
Saisissez votre prénom: Ben
Saisissez votre âge: 57
Salutations Ben Kenobi!
- Vous êtes âgé de 57 ans,
- Vous êtes née en 1962,
- Vous êtes née une année non bissextile
Note
Depuis l’ajustement du calendrier grégorien, l’année sera bissextile si l’année est divisible (et reste un nombre entier) par 4 et non divisible par 100, ou si l’année est divisible (et reste un nombre entier) par 400.
À faire
Réaliser l’analyse, la rédaction (sur papier ) de l’algorithme puis le développement d’un programme équivalent à cette exécution:
Jusqu'à combien dois-je compter : 12
1 2 3 4 5 6 7 8 9 10 11 et 12
À faire
Réaliser l’analyse, la rédaction (sur papier ) de l’algorithme puis le développement d’un programme équivalent à cette exécution:
Saisissez une phrase :
Les framboises sont perchees sur le tabouret de mon grand-pere.
Vous avez saisi :
Les framboises sont perchees sur le tabouret de mon grand-pere.
Cette phrase commence par une majuscule.
Cette phrase se termine par un point.
À faire
Réaliser l’analyse, la rédaction (sur papier ) de l’algorithme puis le développement d’un programme équivalent à cette exécution:
Saisissez dix valeurs :
1 : 4
2 : 7
3 : 34
4 : 1
5 : 88
6 : 22
7 : 74
8 : 19
9 : 3
10 : 51
Saisissez une valeur :
22
22 est la 6-eme valeur saisie.
Tests unitaires & fonctions C##
Nous allons dans cette partie créer une solution qui contiendra deux projet (notre application console et notre jeu de tests) avec Visual Studio 2017 puis Visual Studio Code. Ainsi vous aurez une rapide idée du fonctionnement de ces deux IDE.
Note
Une solution en .Net permet de lier entre eux plusieurs projets (C# mais aussi Visual Basic .Net) et permet de concevoir une application complexe à partir de plusieurs projets plus petits.
Pour résumer voici les étapes que nous allons suivre:
Création d’un solution (
.sln
)Création de notre projet
CalculatriceConsole
Création de notre projet
CalculatriceConsoleTests
Ajout d’une référence entre
CalculatriceConsoleTests
etCalculatriceConsole
Création de la solution .Net Core, pour ce faire on créé d’abord un répertoire qui servira de répertoire pour notre solution
$ mkdir CalculatriceProjet
$ cd CalculatriceProjet
$ dotnet new sln
Création du projet CalculatriceConsole
, idem d’abord la création d’un répertoire qui contiendra notre projet console.
Attention
CalculatriceConsole
est un sous répertoire de CalculatriceProjet
$ mkdir CalculatriceConsole
$ cd CalculatriceConsole
$ dotnet new console
$ cd ..
$ dotnet sln add CalculatriceConsole/CalculatriceConsole.csproj
Une fois notre projet créé, il nous faut créer le projet qui contiendra nos tests unitaires.
$ mkdir CalculatriceTest
$ cd CalculatriceTest
$ dotnet new xunit
$ dotnet add reference ../CalculatriceConsole/CalculatriceConsole.csproj
$ cd ..
$ dotnet sln add CalculatriceTest/CalculatriceTest.csproj
Nous venons de créer notre première solution incluant un projet console et un projet de tests unitaires.
Approche TDD en C#
Le but de cet exercice est de créer plusieurs fonctions qui permettront de faire les opérations mathématique suivantes:
addition
soustraction
multiplication
division
Création de la signature
Dans notre fichier Program.cs
, nous allons créer la signature de la méthode multiplication:
using System;
namespace CalculatriceConsole
{
public class Program
{
public static int Multiplication(int facteur_gauche, int facteur_droite)
{
throw new NotImplementedException();
}
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Nous n’avons que créer la signature de la méthode Multiplication, en effet celle-ci renvoie une exception NotImplementedException
qui signifie que la méthode n’est pas implémentée.
Création de notre premier tests
Attention
La syntaxe des tests unitaires est différentes entre .Net Framework et .Net Core, pensez donc à faire bien attention à quels code source vous recopiez !
Nous allons ajouter à notre fichier OperationTests.cs
de notre projet de tests (CalculatriceTests
) un premier test:
OperationTest.cs
sous .Net Framework:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using CalculatriceConsole;
namespace CalculatriceConsoleTests
{
[TestClass]
public class OperationTest
{
[TestMethod]
public void TestMultiplication()
{
Assert.AreEqual(4, Program.Multiplication(2, 2));
}
}
}
OperationTest.cs
sous .Net Core:
using System;
using Xunit;
using CalculatriceConsole;
namespace CalculatriceTest
{
public class OperationTest
{
[Fact]
public void TestMultiplication()
{
Assert.Equal(4, Program.Multiplication(2, 2));
}
}
}
Nous pouvons éxécuter notre premier test,
pour ce faire via Visual Studio 2017 en utilisant le menu
via .Net Core et visual Studio Code, dans un terminal (situé dans le répertoire de la solution):
$ cd CalculatriceTest
$ dotnet test
Que constatez vous ?
Comprenez vous cette erreur ?
Implémentation de la méthode multiplication
Nous venons de respecter les deux premières étapes du TDD, créer un test et vérifier que celui-ci est en erreur étant donné qu’il n’y pas de code source. Maintenant nous allons écrire juste assez de code pour que celui-ci passe.
using System;
namespace CalculatriceConsole
{
public class Program
{
public static int Multiplication(int facteur_gauche, int facteur_droite)
{
return facteur_gauche + facteur_droite;
}
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Non non ce code ne contient aucune erreur, ne modifiez pas la ligne du corp de la méthode Multiplication
.
Et si on relance notre test maintenant ?
Pensez vous que notre jeu de test est suffisament fiable ?
Complexifions notre jeu de test de multiplication
À faire
Et si nous ajoutions une assertion à notre méthode
TestMultiplication
pour vérifier que le produit de 7 par 3 est égale à 21 ?Que se passe t’il lors de la relance de notre test ?
Pouvez-vous corriger cette erreur ?
Que se passe t’il lors de la relance de notre test ?
Félicitations, vous venez de découvrir les tests unitaires et le TDD ! Désormais vous devrez utiliser cette technique de développement pour chaque TD !
Des tests unitaires plus complexes#
La société InGen ; spécialisée dans la manipulation de génomes et d’ADNs; souhaite créer une bibliothèque de fonctions pour manipuler des brins d’ADNs.

ADN
Pour rappel un brin d’ADN est constitué de 4 base azotée (A, T, G, C), organisé en codon de 3 bases. Ces bases fonctionnent par paires (d’où la structure en double hélice et c’est pour cela que l’on parle de brin d’ADN complémentaire). Ainsi A est complémentaire de T, et G est complémentaire de C.
Bibliothèque de fonctions
Nous voulons créer les fonctions suivantes:
Vérifier qu’une chaîne est bien un brin d’ADN valide
Obtenir le brin d’ADN complémentaire
Obtenir le nombre de codon
Voici quelques données de test pour vous aider:
GCGTCC
AGGGTG
ATCTAC
ATCTACCTCTTC
CTCTTC
CTXTEC
À faire
En adoptant une approche TDD; créer une solution IngenDNA
contenant le projet DNA
et le projet DNATest
; puis implémenter les trois fonctions.
Tests unitaires le retour#
Calcul d’aire
En adoptant une approche TDD, créer une solution contenant un projet de test et un projet de type console proposant les fonctions suivante:
Calcul de l’aire d’un carré
Calcul de l’aire d’un rectangle
Calcul de l’aire d’un triangle
Rappel de mathématique
Calcul de l’aire d’un carré: \(c \times c\)
Calcul de l’aire d’un rectangle: \(L \times l\)
Calcul de l’aire d’un triangle: \((base \times hauteur) \div 2\)
Déterminer la majorité d’un utilisateur
En adoptant une approche TDD, créer une solution contenant un projet de test et un projet de type console proposant la fonction suivante:
Déterminer si une personne est majeure selon son année de naissance passé en paramètre.
Indication
Pour obtenir l’année courante en C#:
DateTime.Now.Year //retourne un int
Recherche d’élément dans un tableau
En adoptant une approche TDD, créer une solution contenant un projet de test et un projet de type console proposant les fonctions suivantes:
Déterminer si un entier est présent dans un tableau d’entier,
Déterminer si une string est présente dans un tableau de string,
Déterminer la position d’un entier dans un tableau,
Déterminer la position d’une string dans un tableau.
Ainsi que la procédure suivante:
Multiplication de tous les entiers d’un tableau par un entier n (le tableau sera passé par adresse).
Base de la conception objet#
Notre voyage dans le temps nous propulse en plein Mésozoïque et plus particulièrement au jurassique moyen.

Au cours de notre voyage, nous avons rencontré les dinosaures suivants:
Suite à une analyse et une conception (très) poussée nous obtenons le diagramme de classe UML suivant (nous omettons volontairement pour l’instant la visibilité des attributs):
Diagramme UML de classe pour nos dinosaures#
Maintenant que nous avons ce diagramme il convient d’implémenter notre classe Dinosaur
via le langage C#.
Nous allons créer une solution nommée MesozoicSolution
qui contiendra:
Un projet console:
MesozoicConsole
,Un projet de test unitaire:
MesozoicTest
.
Important
Vous savez désormais créer une solution contenant ces deux types de projet.
Nous allons toujours adopter l’approche TDD en écrivant d’abord nos tests et ensuite en rédigant notre code qui sera validé par les tests.
Fichier DinosaurTest.cs
au sein du projet MesozoicTest
En .NETCore |
En .NET |
// Le fichier est ici tronqué et ne fait apparaître que les méthodes de tests
[Fact]
public void TestDinosaurConstructor()
{
Dinosaur louis = new Dinosaur("Louis", "Stegosaurus", 12);
Assert.Equal("Louis", louis.name);
Assert.Equal("Stegosaurus", louis.specie);
Assert.Equal(12, louis.age);
}
[Fact]
public void TestDinosaurRoar()
{
Dinosaur louis = new Dinosaur("Louis", "Stegosaurus", 12);
Assert.Equal("Grrr", louis.roar());
}
[Fact]
public void TestDinosaurSayHello()
{
Dinosaur louis = new Dinosaur("Louis", "Stegosaurus", 12);
Assert.Equal("Je suis Louis le Stegosaurus, j'ai 12 ans.", louis.sayHello());
}
|
// Le fichier est ici tronqué et ne fait apparaître que les méthodes de tests
[TestMethod]
public void TestDinosaurConstructor()
{
Dinosaur louis = new Dinosaur("Louis", "Stegosaurus", 12);
Assert.AreEqual("Louis", louis.name);
Assert.AreEqual("Stegosaurus", louis.specie);
Assert.AreEqual(12, louis.age);
}
[TestMethod]
public void TestDinosaurRoar()
{
Dinosaur louis = new Dinosaur("Louis", "Stegosaurus", 12);
Assert.AreEqual("Grrr", louis.roar());
}
[TestMethod]
public void TestDinosaurSayHello()
{
Dinosaur louis = new Dinosaur("Louis", "Stegosaurus", 12);
Assert.AreEqual("Je suis Louis le Stegosaurus, j'ai 12 ans.", louis.sayHello());
}
|
Nous allons créer le squelette de notre classe pour que nous puissions compiler notre projet de test et lancer les tests unitaires pardi!
Créer un nouveau Fichier Dinosaur.cs
au sein du projet MesozoicConsole
( sous Visual Studio 2017) et conserver le fichier Program.cs
using System;
namespace Mesozoic
{
public class Dinosaur
{
public string name;
public string specie;
public int age;
public Dinosaur(string name, string specie, int age)
{
throw new NotImplementedException();
}
public string sayHello()
{
throw new NotImplementedException();
}
public string roar()
{
throw new NotImplementedException();
}
}
}
À faire
Lancez l’exécution des tests que se passe t’il ?
Nous avons indiquer que nos méthodes ne sont pas implémentées, il convient donc de les implémenter et de vérifier que nos tests passent au vert.
Accéder directement aux attributs de notre classe peut être un problème, il convient donc d’utiliser le mot clé private
pour modifier la visibilité de nos attributs et de modifier dans un premier temps nos tests puis notre classe pour aboutir à l’implémentation de notre classe UML révisée:
Diagramme UML de classe pour nos dinosaures#
Nous pouvons implémenter des méthodes qui prennent comme paramètre la référence d’un autre objet, par exemple nous allons ajouter la méthode hug
à nos dinosaures:
Diagramme UML de classe pour nos dinosaures#
La méthode hub
prend en paramètre la référence vers un dinosaure et retourne une chaîne de caractéres.
Son exécution serait:
louis.hug(nessie); // Je suis Louis et je fais un calin à Nessie.
À faire
Quel test devriez vous développer pour tester cette nouvelle méthode ?
Implémentez cette nouvelle méthode.
Dans le
main
du fichierProgram.cs
instanciez plusieurs dinosaures et utilisez leurs différentes méthodes.
À faire
Nous nous inspirerons de l’univers d’un jeu vidéo bien connu à savoir Warcraft.

Voici quelques unités:
Ces unités possédent les méthodes suivantes:
sayHello(): string
grunt(): string
talk(): string
talkToPeasant(Peasant peasant): string
talkToPeon(Peon, peon): string
Quels sont les classes que vous devrez implémenter pour représenter ces unités ?
Réalisez le schéma UML de ces classes,
Quels sont les tests à implémenter pour valider le fonctionnement de ces classes ?
Réalisez l’implémentation des tests puis des classes.
Créez un
main
dans le fichierProgram.cs
pour manipuler des instances de ces classes.
Les collections en Csharp#
Vous avez l’habitude de manipuler des tableaux pour stocker plusieurs données au sein d’une même structure.
Toutefois des structures plus polyvalentes et performante existe. Il s’agit des listes. Nous allons dans la suite de cet exercice découvrir cette structure qualifiée de collection.
Une liste de dinosaures#
Reprenons notre Solution MesoizoicSolution
, nous souhaitons stocker dans une liste nos différents dinosaures, pour ce faire nous allons utiliser le type List<T>
.
Note
Le type List<T>
représente une liste fortement typée d’objets accessibles par index. Fournit des méthodes de recherche, de tri et de manipulation de listes. Celle-ci fait partie du namespace System.Collections.Generic
. Vous aurez donc besoin de faire un using de ce namespace en début de fichier:
using System.Collections.Generic;
Premier exemple de liste en C##
Au sein de la méthode main
ajoutons la création d’une liste:
Dinosaur louis = new Dinosaur("Louis", "Stegosaurus", 12);
Dinosaur nessie = new Dinosaur("Nessie", "Diplodocus", 11);
List<Dinosaur> dinosaurs = new List<Dinosaur>();
dinosaurs.Add(louis); //Append dinosaur reference to end of list
dinosaurs.Add(nessie);
Console.WriteLine(dinosaurs.Count);
//Iterate over our list
foreach (Dinosaur dino in dinosaurs)
{
Console.WriteLine(dino.name);
}
dinosaurs.RemoveAt(1); //Remove dinosaur at index 1
Console.WriteLine(dinosaurs.Count);
//Iterate over our list
foreach (Dinosaur dino in dinosaurs)
{
Console.WriteLine(dino.name);
}
dinosaurs.Remove(louis);
Console.WriteLine(dinosaurs.Count);
//Iterate over our list
foreach (Dinosaur dino in dinosaurs)
{
Console.WriteLine(dino.name);
}
À faire
Comprenez vous le fonctionnement des éléments
Add
,Count
,Remove
etforeach
?Modifiez le
main
pour ajouter deux dinosaures à notre listePlutôt que d’afficher le nom de chaque dinosaure de notre liste, demandez leurs de se présenter.
Une horde de dinosaures#
Très souvent nos dinosaures se déplacent en horde, il convient donc de créer une classe Horde
: qui proposent les méthodes suivantes:
Ajouter un dinosaure au troupeau,
Retirer un dinosaure du troupeau,
Demander à l’ensemble des dinosaures du troupeau de se présenter.
À faire
Écrivez le diagramme UML de la classe
Horde
.Quels sont les tests à développer pour valider le fonctionnement de notre classe ?
Implémentez la classe
Horde
.Utilisez cette nouvelle classe au sein de votre
main
.
Le mot clé static#
Reprenons notre diagramme UML pour la classe Dinosaur, l’attribut specie
est désormais déclaré en statique.
Diagramme UML de classe pour nos dinosaures#
Il faut donc modifier notre classe C# pour utiliser l’attribut specie
en tant qu’attribut de classe et non plus en tant qu’attribut d’instance.
public class Dinosaur
{
protected string name;
protected static string specie;
protected int age;
public Dinosaur(string name, int age, string specie)
{
this.name = name;
this.age = age;
Dinosaur.specie = specie;
}
// Attention encore une fois la classe est ici tronquée, il faut donc bien modifier notre fichier source en remplaçant les occurences de this.specie par Dinosaur.specie
}
Une fois cela fait, dans le main de votre programme lancez les instructions suivantes:
Dinosaur henry = new Dinosaur("Henry", 11,"Diplodocus");
Console.WriteLine(henry.SayHello());
Dinosaur louis = new Dinosaur("Louis", 12, "Stegosaurus");
Console.WriteLine(louis.SayHello());
Console.WriteLine(henry.SayHello());
Constatez vous un problème ?
Quel est son origine ?
Important
Nous verrons dans le prochain exercice comment corriger ce problème.
Vous devriez maintenant vous en être aperçu, nous avons utiliser des méthodes statiques depuis le début de ce module sans même le savoir.
Dans cette partie, il vous est demandé de créer une classe Laboratoire qui permet de créer des dinosaures à la demande.
Voici le diagramme UML pour la classe Laboratoire
Diagramme UML de classe pour notre laboratoire#
À faire
Créez le(s) test(s) unitaire(s) pour valider le fonctionnement de notre classe laboratoire;
Implémentez celle-ci (pensez au namespace, etc.);
Utilisez la classe laboratoire au sein de notre programme pour créer quelques dinosaures.
L’héritage#
Nous avons découvert la notion d’héritage. Dans un premier temps il convient de donc de définir une classe mère en regroupant les caractéristiques en commun au sein d’une même classe.
Classe abstraite & héritage#
Note
Une classe abstraite est une classe qu’on ne peut instancier. Nous l’avons vu en cours il n’existe pas d’instance de dinosaure mais des instances de Diplodocus, Stégausaure, etc.
Ainsi une classe abstraite représente une définition abstraite qui sera commune à différentes classes.
Diagramme UML de nos dinosaures#
Diagramme UML de classe pour nos dinosaures#
Il va donc falloir modifier notre classe Dinosaur pour la rendre abstraite et la dériver en quatre classes filles (Diplodocus, Stegosaurus, TyrannosaurusRex, Triceratops).
Attention
Rappelez-vous, chaque classe doit être écrite au sein de son propre fichier fichier.
Création des classes et modification de Dinosaur
#
Avant toute chose nous ne pouvons plus tester une classe abstraite, ainsi il va donc falloir implémenter pour chaque classe de dinosaure une classe de test.
Voici le diagramme UML des classes de test
Diagramme UML de classe pour nos tests unitaires#
À faire
A vous d’implémenter ces différentes classes de tests.
Modification de la classe Dinosaur
#
Notre classe Dinosaur
étant désormais abstraite, il convient d’indiquer via le mot clé abstract
:
public abstract class Dinosaur
{
// classe tronquée
}
Implémentation des classes de dinosaures#
À faire
A vous de jouer, implémenter les quatre classes définit au sein du diagramme.
Si vous avez bien écrit les tests de la classe TestDinosaurs vous devriez avoir une erreur, pourquoi ?
Attributs statique & propriété#
Un attribut statique est partagée par l’ensemble des instances d’une classe et des classes filles dont elles dérivent. Ainsi l’attribut statique specie
est commun à toutes les classes.
Pour corriger cela, nous allons donc créer une propriété au sein de la classe dinosaur:
public abstract class Dinosaur
{
protected virtual string Specie { get { return "Dinosaur"; } }
// la classe est tronquée, la propriété Specie remplace l'attribut statique specie
}
Relancer vos tests,
Que constatez vous ?
Surcharge de propriété#
Note
Il est possible et même conseillé d’utiliser la surchage en OOP, nous pouvons donc surcharger la propriété Specie
dans chacune des classes de nos dinosaures, par exemple pour le diplodocus:
public class Diplodocus : Dinosaur
{
protected override string Specie { get { return "Diplodocus"; } }
//Encore une fois la classe est tronquée
}
Surcharger la propriété
Specie
de chacune de vos classes,Relancer vos tests,
Que constatez vous ?
Surchage de méthodes#
En suivant le diagramme UML suivant, il convient de surcharger pour chaque classe de dinosaure la méthode Roar
:
Diagramme UML de classe pour nos dinosaures#
À faire
Marquer la méthode
SayHello
comme virtuel,Implémenter une surcharge pour la méthode
SayHello
de chaque classe de dinosaure,Penser à mettre à jour vos T.U.
Les exceptions#
Supposez que vous écrivez un programme qui accède à un fichier, ou qui se connecte à un serveur.
Que se passerait-il si le fichier disparaît, ou si le serveur tombe en panne ? Le comportement de votre programme serait au mieux interrompu, au pire deviendrait imprévisible. L’idéal serait de pouvoir écrire du code qui gèrerait ces situations exceptionnelles.
Supposons que l’on appele une méthode d’une classe. Cette méthode fait quelque chose de risqué, qui peut ne pas fonctionner au moment de l’exécution (ouverture d’un fichier, d’une connexion réseau, etc.).
Il convient d’indiquer au compilateur que la méthode que l’on appele est risquée ett d’écrire du code pour gérer cette erreur si elle se produit.
Note
La gestion des exceptions est un mécanisme qui permet de traiter toutes les situations inattendues qui peuvent se produire au moment de l’exécution.
Elle repose sur le fait que vous savez que la méthode est potentiellement risqué. Si tel est le cas, il faut écrire un morceau de code qui prend en compte cette éventualité.
Lorsque qu’une méthode possède un comportement risqué on dit qu’elle peut lancer une exception.
Une exception
Une exception est une alerte dont le but est d’éviter une mauvaise opération. L’intérêt de lancer une exception est que le programme soit capable de gérer l’erreur ou s’arrêter brutalement. Pour lancer une exception il suffit d’utiliser le mot clé throw
.
throw new NotImplementedException("TODO: add some code");
Intercepter une exception
Lorsque l’on souhaite exécuter du code susceptible d’être source d’erreur, il convient de l’enfermer dans un bloc try
.
Il est possible d’intercepter l’exception avec un (ou plusieurs) bloc(s) catch
.
Indication
Enfin, vous pouvez utiliser un bloc finally
pour effectuer systématiquement une opération après la tentative d’exécution.
Avertissement
Un bloc try doit impérativement être suivi d’au moins un bloc (catch ou finally).
try{
int n = 100/0;
}
catch(DivideByZeroException dbzEx){
Console.WriteLine("Error: Division by Zero");
}
catch(Exception ex){
Console.WriteLine("Error: {0}", ex.Message);
}
finally{
Console.WritLine("Always executed!");
}
Exceptions personnalisées
Indication
Il est possible de créer ses propres exceptions, pour ce faire il faut créer une classe qui dérive de la classe Exception
.
public class BadNameException : Exception
{
public BadNameException(): base("This is a bad name")
{
}
}
public class Dinosaur
{
public Dinosaur(string name)
{
if(name.Equals("Denver"))
{
throw new BadNameException();
}
this.name = name;
}
}
Exceptions prédéfinies#
Important
Il existe de nombreuses exceptions prédéfinies en C#:
DivideByZeroException : Exception levée pendant une tentative de division par zéro d’une valeur intégrale ou Decimal;
FormatException : Exception levée quand le format d’un argument n’est pas valide ou qu’une chaîne de format composite n’est pas formée correctement;
ArithmeticException : Exception levée quand des erreurs se produisent dans une opération arithmétique, de transtypage ou de conversion;
NotImplementedException : Exception levée lorsqu’une méthode ou une opération demandée n’est pas implémentée;
Exception : Représente les erreurs qui se produisent lors de l’exécution de l’application.
Intercepter une exception#
Prenons ce code source:
string user_value = "vingt";
int value = int.Parse(user_value);
Si nous l’exécutons nous obtenons l’erreur suivante:
Unhandled Exception: System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(ReadOnlySpan`1 str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
at System.Number.ParseInt32(ReadOnlySpan`1 s, NumberStyles style, NumberFormatInfo info)
at System.Int32.Parse(String s)
at exceptions.Program.Main(String[] args) in /home/sam/Documents/Projects/dotnet/base_csharp/exceptions/Program.cs:line 13
L’application « plante » lamentablement produisant un rapport d’erreur. Lors de la conversion, si le framework .NET n’arrive pas à convertir correctement la chaine de caractères en entier, il lève une exception. Cela veut dire qu’il alerte le programme qu’il rencontre un cas en erreur qui nécessite d’être géré.
Si ce cas n’est pas géré, alors l’application plante et c’est le CLR qui intercepte l’erreur et qui fait produire un rapport par le Framework.
Pourquoi une exception et pas un code d’erreur ?
L’intérêt des exceptions est qu’elles sont typées. Finis les codes d’erreurs incompréhensibles. C’est le type de l’exception, c’est-à-dire sa classe, qui va nous permettre d’identifier le problème.
Pour éviter une mort douloureuse à notre application, nous devons gérer ces cas et intercepter les exceptions.
Pour ce faire, il faut encadrer les instructions à risque avec le bloc d’instruction try catch
, par exemple :
try
{
string user_value = "vingt";
int value = int.Parse(user_value);
Console.WriteLine("This line will never be executed");
}
catch (Exception)
{
Console.WriteLine("Error during user value conversion.");
}
Nous avons « attrapé » l’exception levée par la méthode de conversion grâce au mot-clé catch.
Cette structure nous permet de surveiller l’exécution d’un bout de code, situé dans le bloc try et s’il y a une erreur, alors nous interrompons son exécution pour traiter l’erreur dans le bloc catch.
La suite du code dans le try, à savoir l’affichage de la ligne avec Console.WriteLine, ne sera jamais exécuté car lorsque la conversion échoue, il saute directement au bloc catch.
Inversement, il est possible de ne jamais passer dans le bloc catch si les instructions ne provoquent pas d’erreur. Essayer donc de remplacer vingt
par 20
. Que se passe t’il ?
Lever une exception#
Il est possible de déclencher soi-même la levée d’une exception. C’est utile si nous considérons que notre code a atteint un cas d’eeruer, qu’il soit fonctionnel ou technique.
Pour lever une exception, nous utilisons le mot-clé throw, suivi d’une instance d’une exception.
Imaginons par exemple une méthode permettant de calculer la racine carrée d’un double.
Nous pouvons obtenir un cas d’erreur lorsque nous tentons de passer un double négatif :
public static double RacineCarree(double valeur)
{
if (valeur <= 0)
throw new ArgumentOutOfRangeException("valeur", "Le paramètre doit être positif");
return Math.Sqrt(valeur);
}
Essayez maintenant d’exécuter la méthode RacineCarree
en lui passant en paramètre -42. Que se passe t’il ?
Exemple 1 - Division#
Voici le code source de la classe MultipleDivision
qui prend en paramètre de constructeur une liste d’entier et posséde une méthode division
qui permet de diviser un élément de la liste par un autre.
Diagramme UML de classe MultipleDivision#
et voici le code source de cette classe:
using System.Collections.Generic;
namespace MyMath
{
public class MultipleDivision
{
private List<int> values;
public MultipleDivision(List<int> values)
{
this.values = values;
}
public Division(int index_dividende, int index_diviseur)
{
double quotient;
quotient = this.values[index_dividende] / this.values[index_diviseur];
return quotient;
}
}
}
Puis dans notre main:
List<int> values = new List<int>{17, 12, 15, 38, 29, 157, 89, -22, 0, 5};
MultipleDivision mltd = new MultipleDivision(values);
//TODO: Ajouter saisie index_dividende, puis saisie index_diviseur
//TODO: Call mltd.division(index_dividende, index_diviseur)
//TODO: Show Result
À faire
Modifiez le
main
pour implémenter la saisie du dividende et du diviseur;Essayez votre programme avec les index 1 et 2, puis 3 et 4 et enfin 9 et 8;
Que se passe t’il ?
Pouvez vous corriger cette erreur ?
Surcharge#
Surchage de l’affichage#
Nous avons depuis le début de notre aventure utilisé la méthode Console.WriteLine
pour afficher des informations dans la console. Toutefois il s’agissait d’afficher directement des chaines de caractères ou des types scalaires. Que se passe t’il si nous passons un type non-scalaire à cette méthode ?
Exemple n°1#
Au sein du main
de votre classe Program
de votre solution MesozoicSolution
ajouter le code suivant:
Dinosaur dinosaur = new Stegosaurus("Louis", 12);
Console.WriteLine(dinosaur);
Que se passe t’il si l’on exécute notre programme ?
Comprenez-vous ce qui est affiché ?
La classe Object
#
Comme nous l’avons vu en cours, tout nos classes héritent automatiquement de la classe Object.
Ainsi ce schéma UML:
Diagramme UML de classe pour nos dinosaures#
est équivalent à celui-ci:
Diagramme UML de classe pour nos dinosaures#
Par convention on ne fait pas figurer sur un diagramme de classe l’héritage par défaut de la classe object étant donné que celui-ci est implicite.
La méthode ToString
#
Note
La classe object fournit une méthode ToString qui retourne une chaîne qui représente l’objet actuel.
Ainsi ce code:
Dinosaur dinosaur = new Stegosaurus("Louis", 12);
Console.WriteLine(dinosaur);
est équivalent à celui-ci:
Dinosaur dinosaur = new Stegosaurus("Louis", 12);
Console.WriteLine(dinosaur.ToString());
ou encore à celui-ci:
Dinosaur dinosaur = new Stegosaurus("Louis", 12);
string dinosaur_repr = dinosaur.ToString();
Console.WriteLine(dinosaur_repr);
Cette méthode est bien évidemment surchargeable pour l’adapter à nos besoins:
public class Stegosaurus
{
public override string ToString()
{
// We only call base ToString from Object class Here
return base.ToString();
}
}
Implémentation d’une surcharge#
Nous allons implémenter une surchage de la méthode ToString au sein de la classe ( abstraite ) Dinosaur de sorte à obtenir ceci:
Dinosaur dinosaur = new Stegosaurus("Louis", 12);
Console.WriteLine(dinosaur); // Mesozoic.Stegosaurus = {name: Louis, age: 12}
À faire
Créez les T.Us correspondant à notre besoin,
Implémentez la méthode et vérifiez son fonctionnement.
Surcharge d’opérateur#
Exemple n°2#
Au sein du main
de votre classe Program
de votre solution MesozoicSolution
ajouter le code suivant:
Dinosaur louis = new Stegosaurus("Louis", 12);
Dinosaur louis2 = new Stegosaurus("Louis", 12);
//Console.WriteLine(dinosaur);
Console.WriteLine(louis == louis2);
louis2 = louis;
Console.WriteLine(louis == louis);
Que se passe t’il si l’on exécute notre programme ?
Comprenez-vous pourquoi ?
La méthode Equals
#
Note
La classe object fournit une méthode Equals qui détermine si deux instances d’objets sont égales.
Ainsi ce code:
Dinosaur dinosaur = new Stegosaurus("Louis", 12);
Dinosaur dinosaur2 = new Stegosaurus("Louis", 12);
Console.WriteLine(dinosaur == dinosaur2);
est équivalent à celui-ci:
Dinosaur dinosaur = new Stegosaurus("Louis", 12);
Dinosaur dinosaur2 = new Stegosaurus("Louis", 12);
Console.WriteLine(dinosaur.Equals(dinosaur2));
HashCode#
Note
La méthode GetHashCode fait office de fonction de hachage par défaut. C’est à dire qu’elle fournit pour un objet un code numérique unique créer à partir de ses attributs.
Voici un exemple de création d’un Hash pour une classe, le hash est une agrégation des hash des différents attribut de l’objet.
public class Dinosaur
{
public override int GetHashCode()
{
int hash = 13;
hash ^= this.name.GetHashCode();
hash ^= this.age.GetHashCode();
hash ^= this.Specie.GetHashCode();
return hash;
}
}
Cette méthode doit être implémenter pour permettre la comparaison d’objet, ainsi deux objets ayant les mêmes valeurs d’attributs retourneront le même hash.
Dinosaur louis = new Stegosaurus("Louis", 12);
Dinosaur louis2 = new Stegosaurus("Louis", 12);
Dinosaur nessie = new Diplodocus("Nessie", 11);
Console.WriteLine(louis.GetHashCode()); // 2034845202
Console.WriteLine(louis2.GetHashCode()); //2034845202
Console.WriteLine(nessie.GetHashCode()); //-275418933
On peut donc comparer la valeur des hash, toutefois il est possible de faire mieux avec la surcharge d’opérateur.
Surcharge d’opérateur(s)#
Note
La surchage d’opérateur en c# permet de préciser un comportement sur la comparaison entre deux instances. Ainsi il est possible d’implémenter un comportement spécifique pour l’opérateur d’égalité (==) entre deux instances d’une même classe. Il faudra également implémenter l’inverse de l’opérateur d’égalité .. la différence (!=).
L’opérateur d’égalité compare chaque attribut des objets pour valider leurs égalités. La différence fait l’inverse.
public class Dinosaur
{
public static bool operator ==(Dinosaur dino1, Dinosaur dino2)
{
return dino1.name == dino2.name && dino1.Specie == dino2.Specie && dino1.age == dino2.age;
}
public static bool operator !=(Dinosaur dino1, Dinosaur dino2)
{
return !(dino1 == dino2);
}
}
Implémentation de la méthode Equals
#
Enfin il est possible d’implémenter la méthode Equals en ce basant sur l’opérateur d’égalité. Remarquez l’introduction du mot clé is
qui valide le type d’un objet.
public class Dinosaur {
public override bool Equals(object obj)
{
return obj is Dinosaur && this == (Dinosaur)obj;
}
}
Implémentation de la méthode equals#
Nous allons implémenter une surchage de la méthode Equals et de l’opérateur d’égalité au sein de la classe ( abstraite ) Dinosaur de sorte à obtenir ceci:
Dinosaur dinosaur = new Stegosaurus("Louis", 12);
Stegosaurus dinosaur2 = new Stegosaurus("Louis", 12);
Dinosaur dinosaur3 = new Diplodocus("Louis", 12);
Console.WriteLine(dinosaur == dinosaur2); // True
Console.WriteLine(dinosaur == dinosaur3); // False
À faire
Créez les T.Us correspondant à notre besoin,
Implémentez les méthode(s) et vérifiez leurs fonctionnements.
Paramètres de type générique#
Où en sommes nous ..#
Reprenons notre classe Laboratory
, vous avez dû la modifier afin de créer quatre méthodes qui permettent chacune de créer une instance d’une espèce de dinosaure.
Diagramme UML de classe pour notre laboratoire#
Toutefois cela implique de la redondance de code et surtout de devoir créer une nouvelle méthode à chaque ajout d’une espèce de dinosaure dans notre code.
Il serait donc plus intéressant de créer une méthode générique où l’on passe le type d’objet à créer au moment de l’appel afin d’éviter la redondance de code et surtout de rendre celui-ci générique et tolérant à l’ajout de nouvelle espèce.
Il s’agit des types génériques.
Paramètres de type générique dans une méthode#
Note
Dans une définition de méthode, le paramètre de type représente un espace réservé pour un type spécifié par le client au moment de créer une instance du type générique.
Utilisation d’un type générique#
On peut définir une méthode qui utiliser un typé générique de cette manière (T
est ici un type générique):
public T DoSomething<T>(T obj)
{
return obj;
}
Notre laboratoire#
Ainsi notre laboratoire peut s’écrire sous cette forme:
Diagramme UML de classe pour notre laboratoire#
public class Laboratory
{
public static TDinosaur CreateDinosaur<TDinosaur>(string name, int age=0)
{
//Do something magic here
}
}
Utilisation de la classe Activator#
Malheureusement il n’est pas possible de coder ceci:
public class Laboratory
{
public static TDinosaur CreateDinosaur<TDinosaur>(string name, int age=0)
{
return new TDinosaur(typeof(TDinosaur), name, age);
}
}
Nous allons donc devoir utiliser la classe Activator qui contient des méthodes permettant de créer des types d’objets localement ou à distance, ou d’obtenir des références à des objets distants existants.
Ainsi notre code deviendra équivalent à cela:
public static TDinosaur CreateDinosaur<TDinosaur>(string name, int age=0)
{
return (TDinosaur)Activator.CreateInstance(typeof(TDinosaur), name, age);
}
À faire
Implémentez les T.Us de cette nouvelle méthode CreateDinosaur,
Implémentez cette méthode,
Utilisez cette nouvelle méthode au sein de votre Main.
Erreur de conception ?#
Nous avons créer notre méthode générique pour créer des dinosaures mais que se passe t’il dans ce cas:
Laboratory.CreateDinosaur<Diplodocus>("Nessie", 11);
Laboratory.CreateDinosaur<String>("Louis", 12);
Le compilateur permet de compiler ce programme toutefois nous avons une erreur au moment de l’exécution. Nous pouvons résoudre ce problème directement au sein de notre code en ajoutant une clause where
à notre type générique.
public static TDinosaur CreateDinosaur<TDinosaur>(string name, int age=0) where TDinosaur: Dinosaur
{
return (TDinosaur)Activator.CreateInstance(typeof(TDinosaur), name, age);
}
Ainsi au moment de la compilation nous forçons le compilateur à vérifier le type générique passé à notre méthode.
À faire
Pouvez vous compiler votre programme maintenant ?
Corrigez le pour pouvoir le compiler.
En reprenant notre diagramme de classe, faite en sorte que la méthode
hub
puissent être appelé avec n’importe quel type de dinosaure via un type générique.
Input/Output en C##
Avertissement
Nos programmes jusqu’alors utilise la mémoire vive de notre machine pour stocker des données sous la forme d’instances, de listes, etc.
Nous allons découvrir comment lire et écrire dans des fichiers avec C#.
Output - Écrire dans un fichier#
Nous allons voir différentes manières d’écrire du texte dans un fichier notamment en utilisant la classe System.IO.File.
Nous aurons donc besoin d’importer le namespace System.IO
via
using System.IO;
Méthode n°1 - Écrire une string dans un fichier#
Tout d’abord nous avons besoin de définir le chemin vers notre fichier, nous pouvons utiliser un chemin relatif comme ci-dessous, ainsi le fichier sera créé à la racine du projet ou alors un chemin absolu vers un autre répertoire de notre machine.
String path = @"test.txt";
Une fois notre chemin définit au sein de la variable path
, nous pouvons créer une string qui contiendra le texte à écrire au sein de notre fichier.
string text = "Hello World!";
Enfin nous pouvons écrire notre string dans notre fichier
File.WriteAllText(path, text);
Vous pouvez vérifier le contenu de votre fichier test.txt
maintenant.
Méthode n°2 - Écrire plusieurs lignes#
Écrire une string dans un fichier c’est bien, pouvoir écrire plusieurs lignes où chaque ligne est représenté par une string c’est mieux.
Nous allons d’abord instancier une liste de string (pensez à ajouter quelques string supplémentaires)
List<string> lines = new List<string>();
lines.Add("I'm you father");
Puis nous allons écrire dans notre fichier l’ensemble des lignes via:
File.WriteAllLines(path, lines);
Consulter le contenu de votre fichier test.txt
que constatez vous ?
Méthode n°3 - Écrire plusieurs lignes, ligne à ligne#
Nous avons écrit plusieurs lignes en une seule instructions, parfois il peut être utile de les écrire lignes par lignes au sein de notre fichier.
Nous allons définir une liste de personnage représenter par leurs noms:
List<string> persos = new List();
persos.Add("Ben Kenobi");
persos.Add("Luke Skywalker");
persos.Add("Darth Vader");
persos.Add("Darth Sidious");
persos.Add("Han Solo");
Ainsi qu’un chemin vers notre fichier rebels.txt
:
string path_rebels = @"rebels.txt";
Nous souhaitons écrire dans notre fichier uniquement les personnages de l’alliance rebelles et non les seigneurs sith. Nous allons donc devoir écrire ligne par ligne notre fichier.
Pour vérifier qu’une string ne contient pas une autre string, C# nous propose la méthode Contains
, par exemple:
line.Contains("Darth") // True, line est ici une string
Nous allons utiliser un StreamWriter
pour écrire via un flux dans notre fichier. L’instruction using permet de délimiter un périmétre d’éxécution durant lequel le flux sera utilisé. Après celui-ci (le using) le flux sera automatiquement flushé et fermé.
using (StreamWriter file = new StreamWriter(path))
{
foreach(string line in lines)
{
file.WriteLine(line);
}
}
À faire
À vous de jouer, implémenter l’écriture ligne à ligne des personnages uniquement de l’alliance.
Méthode n°4: Ajouter du contenu à un fichier#
Note
Pour l’instant nous avons écrit dans un fichier en remplaçant son contenu. Toutefois il peut être utilse d’écrire à la suite d’un fichier sans en effacer son contenu.
Pour ce faire nous utilisons toujours le StreamWriter
en lui passant un paramètre à true
lors de son instanciation.
using (StreamWriter file = new StreamWriter(path, true))
{
file.WriteLine("Another line"); // Add this line at the end of the text file
}
À faire
À vous de jouer, ajouter la princesse Leia Organa et le capitaine Wedge Antilles à notre liste de combattant de l’alliance.
Erreur éventuelle
Les conditions ci-dessous peuvent générer une exception:
Le fichier existe déjà et est en lecture seule.
Le nom du chemin d’accès peut s’avérer trop long.
Le disque est peut-être plein.
Input - Lire un fichier#
Nous allons voir différentes manières d’écrire du texte dans un fichier notamment en utilisant la classe System.IO.File.
Nous aurons donc besoin d’importer le namespace System.IO
via
using System.IO;
Méthode n°1 - Lire le contenu complet d’un fichier#
On définit toujours le chemin vers notre fichier
string path = @"rebels.txt";
Puis on récupérer l’ensemble du contenu du fichier au sein d’une string
string text = File.ReadAllText(path);
À faire
Afficher le contenu du fichier maintenant.
Méthode n°2 - Lire le contenu d’un fichier ligne à ligne#
On peut également récupérer l’ensemble des lignes du fichiers sous la forme d’un tableau de string :
string[] lines = File.ReadAllLines(path);
À faire
Afficher les lignes une à une grâce à l’instruction foreach
.
Erreur éventuelle
Les conditions ci-dessous peuvent générer une exception:
Le fichier n’existe pas ou ne se trouve pas à l’emplacement spécifié. Vérifiez le chemin et l’orthographe du nom de fichier.
À faire
Et si on créer un simple éditeur de texte avec WinForms ?
.Net Core & SQLite#
Nous allons découvrir comment lier notre programme .Net Core avec une base de données. Pour ce faire nous utiliserons le SGBD SQLite.
Mise en place du projet console#
Il convient de créer un projet comme d’habitude
mdkir sqlite_csharp_demo
cd sqlite_csharp_demo
dotnet new console
Puis on ajoute le package SQLite pour C#
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet restore
Connexion à une BDD#
Il convient d’importer l’espace de nom correspondant au package SQLite
using Microsoft.Data.Sqlite;
Il convient dans un un premier temps de créer une connexion à la bdd
static SqliteConnection connect(String datasource)
{
SqliteConnectionStringBuilder connectionBuilder = new SqliteConnectionStringBuilder();
connectionBuilder.DataSource = datasource;
return new SqliteConnection(connectionBuilder.ConnectionString);
}
static void Main(string[] args)
{
SqliteConnection connection = null;
connection = connect("data.sqlite3");
connection.Open();
}
Si vous lancez votre programme via un dotnet run
, une base de données sqlite3 vide sera créé. Il est donc temps de créer une table dans cette base de données:
static void createBeerTable(SqliteConnection connection)
{
SqliteCommand command = connection.CreateCommand();
command.CommandText = "CREATE TABLE IF NOT EXISTS beers(id INTEGER NOT NULL UNIQUE PRIMARY KEY AUTOINCREMENT, name TEXT);";
command.ExecuteNonQuery();
}
Et si on insérait un enregistrement ?
static void createBeer(String beer, SqliteConnection connection)
{
SqliteCommand command = connection.CreateCommand();
command.CommandText = "INSERT INTO beers (name) VALUES (@name)";
command.Parameters.Add(new SqliteParameter("@name", beer));
command.ExecuteNonQuery();
}
Et enfin pour lire toutes les données issues de la table:
static Dictionary<int, string> getAllBeers(SqliteConnection connection)
{
Dictionary<int, string> beers = new Dictionary<int, string>();
SqliteCommand command = connection.CreateCommand();
command.CommandText = "SELECT id, name FROM beers";
using (SqliteDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
beers.Add(reader.GetInt32(0), reader.GetString(1));
}
}
return beers;
}
Pour parcourir un Dictionnaire on peut utiliser le code suivant:
foreach(KeyValuePair<int, string> row in getAllBeers(connection))
{
Console.WriteLine("id: {0} name: {1}", row.Key, row.Value);
}
À faire
Plutôt que d’utiliser un dictionnaire et si on utilisait une classe
Beer
pour récupérer nos enregistrement de la base de données,Créer une méthode qui permet d’insérer plusieurs bières en prenant en paramètre une liste de string correspondant à une liste de nom,
Créer une méthode qui permet de récupérer une bière par son nom,
Créer une méthode pour supprimer une bière par son id.