Tour du langage C
Nous allons faire un tour du langage C# sans forcément tout couvrir car le langage est très riche.
Warning
Comme le langage C# évolue chaque année et apporte des amélioration et simplifications régulièrement, il se peut que les exemples de code vues ici soient différents de ce que vous trouvez dans la littérature.
Premiers concepts
- Création d'un projet appelé hello-cs avec le CLI:
dotnet new console -n "hello_cs"
- Configuration du projet avec le fichier csproj
- Développement: Lancement de l'application avec
dotnet run
ou depuis VSCode avec F5 ou Ctrl + F5 - Concepts à couvrir: Types de base, fonctions, conditions, boucles, tableaux, etc.
Quelques caractéristiques
- C# est statiquement typé: une variable ne peut pas changer de type (contraire de dynamiquement typé)
- C# supporte la programmation orienté objet et fonctionnelle
- Différences avec les conventions de codage java:
- Les noms des méthodes, propriétés publiques et constantes publiques commencent avec une majuscule:
Console.WriteLine("Hello World!");
- Un attribut privé est préfixé avec un
_
- Les méthodes asynchrones sont suffixées avec le terme
async
- Les interfaces commencent avec la lettre I (
IEnumerable
)
- Les noms des méthodes, propriétés publiques et constantes publiques commencent avec une majuscule:
- Autres conventions communes:
- Une méthode ou fonction commence par un verbe “WriteLine” et non “LineWrite”
- Le nom d’une classe commence par une majuscule
- C# supporte la null safety: permet d'avertir ou d'interdire la manipulation de valeurs nulles sans précaution préalable de la part du codeur.
void RunTour(){
}
// Premier tour du langage
Console.WriteLine("Hello, World!");
int intValue = 10;
bool b = false;
string s = "hello";
Console.WriteLine(intValue);
Console.WriteLine(s);
// Ne pas utliser la concaténation
Console.WriteLine("La valeur de s est : " + s + " et la valeur de est " + intValue);
// Car il y a mieux à la place avec $"" -> interpolation de string
Console.WriteLine($"La valeur de s est : {s} et la valeur de l'entier est {intValue}");
// intValue = "Hello"; // erreur car statiquement typé et intValue est un int
// typage implicite: message sera de type string
var message = $"La valeur de s est : {s} et la valeur de l'entier est {intValue}";
Console.WriteLine(message);
Random r = new Random();
int randomVal = r.Next(10, 20);
if (randomVal > 15)
{
Console.WriteLine("gagné");
}
else
{
Console.WriteLine("perdu");
}
for (int i = 3; i < 8; i++)
{
Console.WriteLine($"La valeur de i est : {i}");
}
// Mettre un identifiant au pluriel pour les tableaux
int[] items = { 1, 2, 4 };
for (int i = 0; i < items.Length; i++)
{
Console.WriteLine($"La valeur de l'élement d'indice {i} du tableau est : {items[i]}");
}
foreach (var item in items)
{
Console.WriteLine($"La valeur de l'élement courant est : {item}");
}
Programmation orienté objet
- Le programme est vu comme un ensemble d'objets qui interagissent entre eux.
- Chaque object est défini par une classe qui elle même hériter d'autres classes ou interfaces.
- Une classes peut définir des propriétés et des méthodes, qu'on appelle des membres.
- Propriété : une vue sur une donnée via ses accesseurs en lecture et / ou écriture (qu'on appelle getters et setter respectivement).
- Méthode : fonction qui est définie au sein de la classe
- Une classe enfant peut hériter d'une seule classe parente en C#:
- Dans ce cas, la classe enfant contiendra implicitement tous les membres de la classe mère
- La classe enfant peut définir des membres supplémentaires qui lui seront propres
- La classe enfant peut redéfinir des membres de classe parent. On appelle cela une surcharge ou override en Anglais.
- Notion de Propriété, méthode ou classe statique:
- Propriété d'instance : chaque instance a ses propres proriétés d'instances
- Propriété statique : elle sera partagée entre toutes les instances (comme une variable globale pour la classe)
- Méthode d'instance : sera exécutée dans le contexte de l'instance qui l'a appelée (accessible via
this
) - Méthode statique : méthode qui a comme contexte que les propriété et méthodes statiques de sa classe
- Classe statique : une classe qui ne peut pas être instanciée et ne contiendra donc que des propriété et méthodes statiques
- Méthode, propriété et classe abstraite:
- Méthode abstraite: méthode qui n'a pas d'implémentation
- Propriété abstraite: propriété dont les accesseurs ne sont pas définis
- Classe abstraite: une classe qui a au moins une propriété ou méthode abstraite
- Les membres abstraits sont destinés à être définis par une sous classe non abstraite.
// ComputerPiece.cs
namespace DemoOpp
{
class ComputerPiece
{
// prorpiété en lecture et écriture
public int WattUsage { get; set; }
// prorpiété en lecture seule
public int Price { get; }
public ComputerPiece(int price)
{
Console.WriteLine("ComputerPiece constructor");
this.Price = price;
}
}
class PowerGenerator : ComputerPiece, ILightable
{
public PowerGenerator(int price) : base(price)
{
}
public void TurnLightsOff()
{
throw new NotImplementedException();
}
public void TurnLightsOn()
{
throw new NotImplementedException();
}
}
class UsbPort : ComputerPiece, ILightable
{
public UsbPort(int price) : base(price)
{
}
public void TurnLightsOff()
{
throw new NotImplementedException();
}
public void TurnLightsOn()
{
throw new NotImplementedException();
}
}
class CPU : ComputerPiece
{
public CPU(int price) : base(price)
{
}
public int ClockSpeed { get; set; }
public int CoreCount { get; set; }
}
class RAM : ComputerPiece
{
public RAM(int price) : base(price)
{
}
public int Capacity { get; set; }
}
}
// ILightable.cs
namespace DemoOpp
{
public interface ILightable
{
public void TurnLightsOn();
public void TurnLightsOff();
}
}
// IOverclockable.cs
namespace DemoOpp
{
public interface IOverclockable
{
public int MaxPossibleClockSpeed { get; }
public void OverClock(int frequency);
}
}
// OopProgram.cs
using DemoOpp;
CPU cpu1 = new CPU(200);
cpu1.WattUsage = 100;
Console.WriteLine(cpu1.WattUsage);
CPU cpu2 = new(500);
cpu2.WattUsage = 100;
Console.WriteLine(cpu2.WattUsage);
CPU cpu3 = new(200)
{
ClockSpeed = 100,
CoreCount = 8,
WattUsage = 10
};
Console.WriteLine(cpu3);
RAM ram1 = new(200)
{
Capacity = 8000,
WattUsage = 10
};
Console.WriteLine(ram1);
ComputerBox computerBox = new(10, 10, 5);
Console.WriteLine(computerBox);
Constructeur vs bloc d'initialisation
- Le constructeur est une méthode spéciale qui est appelée lors de l'instanciation d'une classe
- Le bloc d'initialisation est un bloc de code qui est exécuté avant le constructeur
- Il y a deux styles d'initialisation des propriétés d'une classe: l'initialisation via le constructeur et l'initialisation via le bloc d'initialisation
- Chaque style a des avantages et inconvénients en terme de lisibilité et verbosité
- Même si on peut mixer les deux styles d'initialisation, il est recommandé de choisir un style et de s'y tenir pour la cohérence du code
Deux styles d'initialisation
HeroWithConstructor heroWithConstructor = new(1, "garan", Role.Fighter);
HeroWithoutConstructor heroWithoutConstructor = new() { Id = 2, Name = "saraka", Role = Role.Spport };
public enum Role
{
Fighter, Mage, Spport, Shooter
}
public class HeroWithConstructor
{
public int Id { get; set; }
public string Name { get; set; }
public Role Role { get; set; }
public HeroWithConstructor(int id, string name, Role role)
{
this.Id = id;
this.Name = name;
this.Role = role;
}
}
public class HeroWithoutConstructor
{
public required int Id { get; set; }
public required string Name { get; set; }
public required Role Role { get; set; }
}
record
Les record sont des classes avec des fonctionnalités supplémentaires qui permettent de simplifier la création de classes simples.
- Les records sont des classes immuables
- Les records sont des classes finales (non héritables)
- Les records ont des propriétés en lecture seule
- Les records ont des méthodes
Equals
,GetHashCode
,ToString
etWith
générées automatiquement
Person p1 = new("John", "Doe");
Person p2 = new("John", "Doe");
Console.WriteLine(p1.LastName);
// p1.LastName = "Hello"; -> erreur
Console.WriteLine(p1 == p2); // true
Console.WriteLine(p1); // Person { FirstName = John, LastName = Doe }
public record Person(string FirstName, string LastName);
Programmation fonctionnelle
- Les fonctions sont des éléments de première classe : Les fonctions sont comme des variables
- Utilisation intensive de fonctions pures: fonction sans effet de bord, toujours le même résultat pour les mêmes entrées
- exemples de fonctions par pure:
WriteLine
(car elle change la console)
- exemples de fonctions par pure:
- Immutabilité
- On ne peut pas changer la valeur d'une variable une fois initialisée
- On ne peut pas changer les propriétés d'un objet une fois instancié
- On ne peut pas ajouter ou supprimer des éléments d'une collection
- On le code est développé sous forme d'une chaîne de traitements (comme dans une usine)
Relation entre la POO et la programmation fonctionnelle
- La POO est la prog. fonctionnelle ne sont pas mutuellement exclusifs
- On peut développer en POO avec un style fonctionnelle:
- Les méthodes ne font pas de mutation de chmaps de l'objet
- Les propriétés sont uniquement en read-only
- Les
records
simplifient la création de ce genre de classes
Autres fonctionnalités pratiques
- Méthodes d'extension: permettent de rajouter une méthode à une classe sans passer par l'héritage
- Null-safety
- Cette options est activée avec la propriété
<Nullable>enable</Nnullable>
au niveau du projet. - Pour signaler des erreurs au lieu d'avertissements, utiliser la propriété
<WarningsAsErrors>Nullable</WarningsAsErrors>
au niveau du projet.
- Cette options est activée avec la propriété
Debug vs release
- Mode debug => à utiliser pour le dev via
dotnet run
- On peut débugger (point d'arrêt, inspecter des variables et la stack frame ou stack trace)
- Programme plus lourd en espace et Moins rapide
dotnet run
permet de générer et binaire debug et de l’exécuter juste après
- Mode release: => à utiliser pour la mise en prod
- On peut pas débugger
- Programme moins spatieux et plus rapide
dotnet publish -c Release …
Mise en production d'un projet C#
- Pour générer le binaire selon l’OS:
dotnet publish -c Release -r <RID> --self-contained (false ou true)
-c Release
: optimiser et ne pas inclure les infos de débogage-r <RID>
: rid est à remplacer par l’id du système cible. Exemples:win-x64
,linux-x64
,osx-x64
, etc.--self-contained
: permet d’embarquer ou non le runtime dotnet dans le livrable. Pour une livraison autonome préférer le--self-contained
àtrue
au lieu defalse
- Votre livrable se trouvera dans le dossier:
bin\Release\netx.x\<rid>\publish
- Tous les fichiers sont à installer dans les postes client à part le fichier
...deps.json
et le fichier... .pdb
- Ensuite, il faudra soit fournir les binaires dans une archive, soit fournir un installeur
- Visual Studio permet de générer des installeurs plus facilement