Last active
January 10, 2024 12:52
-
-
Save KevinRoussel/2ec3899db2cb8931f92b0efec7d6d34e to your computer and use it in GitHub Desktop.
Master C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Les usings au cas où des classes sont rangés dans un namespace différent | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
// Là où l'on va ranger notre classe, ceux qui voudront l'utiliser pourrons faire : using MasterCSharp_x_Unity; | |
namespace MasterCSharp_x_Unity | |
{ | |
// Création d'une classe, on définit l'ensemble des caractéristiques de l'objet que l'on conçoit. | |
// Convention de nommage : PascalCase pour le nom des classes | |
// Plusieurs visibilité possible : public, private, internal. Habituellement c'est public. | |
// Niveau organisation essayez de respecter cet ordre : field, property, event, constructor, method | |
public class Résumé | |
{ | |
#region Fields | |
// Création d'un champs (field) dans la classe. | |
// Respectez la convention de nommage : le nom est préfixé d'un _ et le reste est en camelCase | |
// Gardez cette règle : les champs sont quasi systématiquement privé (ou protected en cas d'héritage). | |
// JAMAIS en public ni internal | |
int _monSuperChamps; | |
string _monSuperNom; | |
bool _isSuperChamps; // Tips, traditionnellement les bool on démarre leur nom par _is... | |
#endregion | |
#region Properties | |
// Propriété (property), globalement ça génère à notre place les deux méthodes Get... et Set... | |
// On peut très bien avoir juste un get, ou juste un set. On décide ce que l'on met en place | |
// Ici le get et le set sont public (on peut aussi le mettre private, internal, protected) | |
public int MonSuperChamps | |
{ | |
get { return _monSuperChamps; } | |
set { _monSuperChamps = value; } | |
} | |
// Deux changements ici : | |
// Premier point : | |
// Ayez l'habitude également avec cette écriture, dans le cas où l'on a une seule ligne | |
// C'est l'écritude dite "lambda" ou "inline", pas besoin de return, on renseigne direct le champs ou le calcul et voila | |
// On peut aussi voir une lambda qui fait juste une affectation (ex: le set ici) | |
// Deuxième point : | |
// La propriété est public, MAIS on a réécrit la visibilité du set. | |
// C'est super classique, on veut souvent proposer aux classes d'accéder à une info | |
// SANS pour autant qu'ils puissent la modifier | |
public string MonSuperNom | |
{ | |
get => _monSuperNom; | |
private set => _monSuperNom = value; | |
} | |
// Les propriétés nous permet de pouvoir faire des vérifications avant que l'on modifie effectivement le champs | |
// Pensez à systématiquement vérifier cette fameuse value, surtout si le set est public | |
// Potentiellement le stagiaire est passé par là et cherche à vous envoyer des informations naze, protégez-vous | |
public bool IsSuperChamps | |
{ | |
get { return _isSuperChamps; } | |
set | |
{ | |
if (MonSuperNom == "Kévin") // Ah dommage je vais t'ignorer | |
{ | |
return; | |
} | |
_isSuperChamps = value; | |
} | |
} | |
// Les propriétés ne font que renvoyer un champs. | |
// Il faut donc créer un champs + une propriété ... Trop dur | |
// Quand on a une propriété extrêmement simple (get et set ultra basique) on peut laisser le compilateur générer | |
// à notre place le champs qu'il va exploiter. Et ce champs on s'en cogne, on va systématiquement passer par la propriété | |
public float MaSuperPropriétéAutoGénéré { get; set; } | |
// Et bien sûr on peut changer la visibilité du set pour le rendre dispo que dans notre classe | |
public float MonAutreSuperPropriété { get; private set; } | |
// On peut également créer un getter de cette façon, il est implicite que l'on ne peut que get cette chose. | |
// Souvent c'est pour faire un getter qui fait un mini calcul ou une petite ligne de code pour améliorer | |
// le confort de la personne qui va utiliser la propriété. | |
// Ici par exemple IsDead n'a pas besoin d'un champs bool pour travailler, on peut directement deviner en | |
// consultant la valeur des hp en cours/ | |
int _currentHP; | |
public bool IsDead => _currentHP <= 0; | |
// Utilisation des propriétés | |
// Ici je fais juste deux petites méthodes qui simule un Get.../Set... afin de voir la lourdeur de l'écriture à l'utilisation | |
void Set_MonAutrePropriété(float value) => MonAutreSuperPropriété = value; | |
float Get_MonAutrePropriété() => MonAutreSuperPropriété; | |
void UseProperty() | |
{ | |
// Là où l'utilisation des propriétés est pratique c'est que l'on a du sucre syntaxique qui nous permet | |
// de considérer la propriété comme si c'était une variable, derrière le compilateur remplacera par l'utilisation de la | |
// méthode Get... ou Set... | |
Console.WriteLine(MonAutreSuperPropriété); // Ici on utilise le Get... | |
MonAutreSuperPropriété = 3.5f; // Ici on utilise le Set... | |
// Cette écriture fonctionne dans n'importe quelle classe du moment qu'on a la visibilité sur cette dernière | |
// Pas besoin donc de truc comme ça | |
Console.WriteLine(Get_MonAutrePropriété()); // Écriture un peu lourde | |
Set_MonAutrePropriété(3.5f); // Écriture un peu lourde | |
} | |
#endregion | |
#region Methodes | |
// Une méthode est une fonctionnalité crée pour répondre à un besoin dans l'objet | |
// Vous l'écrivez exactement comme une fonction classique, on a juste la possibilité comme d'hab de mettre une visibilité | |
// Les visibilités dispo : public, private, internal, protected | |
void MaSuperFunction(int param1, float param2) | |
{ | |
} | |
// La méthode peut également retourner une information en sortie, classique | |
bool IsSuperFonction(int param1, float param2) | |
{ | |
return true; | |
} | |
// Pareil les méthodes qui font une seule ligne peuvent être écrite en lamda | |
void SayHelloWorld() => Console.Write("Hello World !"); | |
#endregion | |
#region Dans une méthode ... | |
// Ici j'ai commencé une méthode, on va pouvoir rentrer dedans afin de voir tout ce que l'on peut | |
// utiliser dans une méthode : variable, condition, boucle etc .... | |
void Coucou() | |
{ | |
#region Variables | |
// ################################################## | |
// Variables | |
// ################################################## | |
int maVariable; // Création d'une variable | |
int maVariable2 = 12; // Création + affectation d'une variable | |
// Lorsque l'on fait une affectation direct, | |
// on peut mettre "var" pour que le compilateur devine de lui même le type. | |
var maVariable3 = 12; | |
// Attention ptite variation, on fait la distinction entre les double et les floats | |
// Quand on écrit un float, le nombre fini toujours par 'f' | |
// On peut ranger un float dans un double, l'inverse non. | |
// Étant donné que le double est plus "gros" que le float, il peut ne pas rentrer dans le float par défaut. | |
// Donc une opération de cast est necessaire (avec des problèmes potentiels) | |
float maVariable4 = 3.5f; // La ça compile | |
float maVariable5 = 3.5; // Ici ça ne compile pas, ranger un double dans un float ça pose problème | |
float maVariable6 = (float)3.5; // Ici ça passe car on force le type du double, mais ça peut poser problème s'il n'y a pas d'équivalence du double au float | |
double maVariable7 = 3.5; // Ici ça compile, un double dans un double | |
double maVariable8 = 3.5f; // ça marche également, un float dans un double | |
// ################################################## | |
// Scope | |
// ################################################## | |
// Attention, 2 accolades indiquent la création d'un scope (portée en fr) | |
// Ce principe fonctionne avec n'importe quel accolade: if, while, fonction, classe etc | |
// Une variable créé dans un scope n'est disponible QUE dans ce dernier et ceux qu'il englobe | |
// Cas 1 : | |
string hw = "Hello World"; | |
if (true) | |
{ | |
Console.Write(hw); // Ici ça marche | |
} | |
Console.Write(hw); // Ici ça marche | |
// Cas 2 : | |
if (true) | |
{ | |
string hw2 = "Hello World"; | |
Console.Write(hw2); // Ici ça marche | |
} | |
Console.Write(hw2); // !!! Ici ça marche pas, hw2 n'est pas disponible !!! | |
// ################################################## | |
// Tableaux | |
// ################################################## | |
// Il existe un type de variable/champs qui permet de stocker une collection d'un même type | |
// C'est le concept du tableau | |
// On doit indiquer la taille du tableau à l'avance (un nombre en dur, une variable ou un calcul) | |
int[] loto = new int[4]; | |
// On accède aux index du tableau par le numéro de la case. | |
// Les index commencent TOUJOURS par 0, dans un tableau de 4 cases, on va donc de la case 0 à 3. | |
loto[0] = 12; | |
loto[1] = 23; | |
loto[2] = 45; | |
loto[3] = 56; | |
// Si on se vautre et que l'on accède à une case qui n'existe pas, on a un joli poème à l'execution | |
// ♫ Une OutOfRangeException ♫ | |
loto[100] = 12; // ERREUR | |
// Tips, pour remplir un tableau avec des valeurs intiales, on peut le faire directement lors du new | |
// Et d'ailleurs dans ce cas là, pas besoin de renseigner de taille, il va se baser sur le nombre d'éléments que tu lui donne. | |
loto = new int[] { 12, 23, 45, 56 }; | |
// Également quand le type du tableau est évident, on n'est pas obligé d'écrire le type lors du new | |
// L'initalisateur est composé uniquement de int, il est donc évident qu'il s'agit d'un int[] | |
loto = new[] { 12, 23, 45, 56 }; | |
// ################################################## | |
// Tableaux à plusieurs dimensions | |
// ################################################## | |
// En C ou C++ quand on veut faire un tableau à 2 dimensions on fait un tableau de tableau | |
int[][] map1 = new int[10][]; | |
for (int i = 0; i < map1.Length; i++) | |
{ | |
map1[i] = new int[10]; | |
} | |
// Pas super pratique car on doit parcourir chaque case pour préparer la deuxième dimension de ce dernier | |
// Pour accéder à une case spécifique on fait donc | |
map1[3][5] = 12; | |
// Pour connaitre la longueur d'un tableau donné, on utilise la propriété Length | |
var l = map1.Length; // Longueur de la première dimension | |
var l2 = map1[0].Length; // Longueur de la deuxième dimension | |
// Mais faut faire gaffe, ici la deuxième dimension peut avoir une taille différente dans le tableau | |
var m = new int[][] | |
{ | |
new [] {12, 23, 34}, | |
new [] {12}, | |
new [] {12, 23}, | |
}; | |
// Ici la deuxième dimension a 3 de Length sur la première case, 1 sur la 2e, 2 sur la 3e. | |
// Des fois c'est ce que l'on veut, des fois non. | |
// En C# on a inventé une syntaxe spécifique pour les tableaux à plusieurs dimensions: | |
int[,] map2 = new int[10, 10]; | |
// Ici le tableau est directement disponible et déjà prêt utilisation | |
// Pour accéder à une case spécifique on fait donc | |
map2[3, 5] = 12; | |
// Un tableau avec cette syntaxe est forcément consistent en terme de taille de tableau | |
// Pour obtenir la length d'une dimension : | |
map2.GetLength(0); // Taille de la première dimension | |
map2.GetLength(1); // Taille de la deuxième dimension | |
#endregion | |
#region Strings | |
// Il existe plusieurs manière d'écrire une string | |
// La version classique on met le texte entre guillement | |
string name = "Bidule"; | |
string message = "Hello World"; | |
// On peut concaténer des chaine pour faire un message qui insère différentes strings | |
message = "Hello " + name + " je suis content de te voir"; | |
// ... Mais c'est un peu laborieux de devoir toujours jouer avec les + pour faire ça. | |
// On a inventé une nouvelle syntaxe plus facile à lire, il faut préfixer notre string d'un symbole $ | |
// Lors d'une insertion d'une string ou d'un calcul, il nous suffit de mettre { pour entrer en "mode code" et donc mettre | |
// le texte que tu veux ajouter, on ferme l'accolade pour repasser en mode string classique | |
message = $"Hello {name} je suis content de te voir"; | |
// On peut avoir plusieurs insertion bien sûr | |
message = $"Hello {name}, t'as vu {name.ToLower()} je me souviens de ton nom {name.ToUpper()} !"; | |
// Hello Bidule, t'as vu bidule je me souviens de ton nom BIDULE ! | |
// On peut écrire une string sur plusieurs lignes | |
message = "Hello \n" + | |
"World"; | |
// Mais bon faut penser à mettre des \n afin de marquer la fin de ligne | |
// En utilisant @ juste avant les guillements on annonce au compilateur que le texte va faire plusieurs lignes | |
// Attention, dans cette situation il intègre l'ensemble des caractères que vous écrivez, y compris les \n et les tabulations. | |
message = @" | |
Hello | |
World | |
Je suis un test | |
Sur plusieurs lignes !"; | |
#endregion | |
#region Conditions | |
// Bon là c'est ultra classique et totalement équivalent au C, C++ etc | |
// ################################################## | |
// If | |
// ################################################## | |
if (true) | |
{ | |
Console.Write("Hello World"); | |
} | |
// ################################################## | |
// Bloc complet : if/else if/else | |
// ################################################## | |
int age = 12; | |
if (age < 18) | |
{ | |
Console.Write("Tu es mineur"); | |
} | |
else if (age < 0 || age > 150) | |
{ | |
Console.Write("Mytho va"); | |
} | |
else | |
{ | |
Console.Write("Tu es majeur"); | |
} | |
// ################################################## | |
// Switch | |
// ################################################## | |
// Parfois faire pleins de if sur une même variable peut être relou, on a inventé la syntaxe switch pour ça | |
switch (age) | |
{ | |
case 0: | |
Console.Write("Oh le nouveau né"); | |
break; | |
case 18: | |
Console.Write("ça y est la majorité"); | |
break; | |
case 67: | |
Console.Write("T'inquiète tu seras ptet en retraite un jour"); | |
break; | |
default: | |
Console.Write("J'ai pas de message personnalisé mais c'est cool"); | |
break; | |
} | |
// ################################################## | |
// Ternaire | |
// ################################################## | |
// On peut rencontrer un type de dev que l'on appelle "le kéké" (c'est ptet toi) | |
// Il veut tout faire sur une seule ligne parce que "c'est stylé". | |
// Quand la ligne que tu écris est ÉVIDENTE oui ça peut faire sens, mais au delà évite | |
Console.Write(age < 18 ? "Mineur" : "Majeur"); | |
// Une ternaire fonctionne en 3 partie : | |
// [la condition] ? la valeur si c'est vrai : la valeur si c'est faux | |
// Jle rappelle à utiliser avec parcimonie | |
Console.Write(age < 18 ? ((age == 0) ? "Nouveau né" : "Enfant") : (age < 150 ? "Adulte" : "RIP")); // Évite | |
#endregion Conditions | |
#region Boucles | |
// Pareil ici, les boucles c'est un grand classique, même syntaxe qu'en C / C++ | |
// ################################################## | |
// While | |
// ################################################## | |
// On évalue la condition, si elle est vrai on entre dans la boucle, si elle est fausse on saute | |
// A la fin de chaque boucle, on remonte, on réévalue la condition ... | |
// ... si elle est encore vrai on ré-entre, si elle devient fausse on saute | |
while (age < 18) | |
{ | |
Console.Write("Enfant"); | |
} | |
Console.Write("Adulte"); | |
// ################################################## | |
// Do / While | |
// ################################################## | |
// Même chose que le while avec la seule différence, que l'on fait le bloc et ENSUITE on évalue la condition. | |
// De cette manière, on est sûr de faire au moins une fois le bloc de code concerné | |
int random = 12; | |
int input = 0; | |
do | |
{ | |
Console.Write("Devine le nombre"); | |
input = int.Parse(Console.ReadLine()); | |
} while (random != input); | |
Console.Write("Gagné"); | |
// ################################################## | |
// For | |
// ################################################## | |
// On a tellement l'habitude d'utiliser les boucles pour faire une opération un nombre précis de fois | |
// que l'on a fini par créer une syntaxe pour ça, le for | |
// L'idée est de rassembler en une seule ligne tout ce qui concerne la gestion de la boucle : | |
// L'initialisation du compteur, la condition de sortie et l'opération d'incrémentation du compteur | |
for (int i = 0; i < 10; i++) | |
{ | |
Console.Write($"Message numéro {i}"); | |
} | |
// ################################################## | |
// Foreach | |
// ################################################## | |
// On peut faire un foreach avec toutes les classes qui implémente l'interface IEnumerable | |
// Globalement : les tableaux, liste, dictionnaires, stack, queue etc | |
// Avec le foreach, on va créer une petite variable qui va avoir pour valeur successivement l'ensemble des elements dans la collection que l'on est en train d'itérer | |
int[] monTableau = new[] { 12, 23, 34 }; | |
foreach (var el in monTableau) | |
{ | |
Console.WriteLine(el); // On va écrire successivement 12, 23, 34 | |
} | |
// Attention à la petite subtilité, IEnumerable indique que l'on doit pouvoir parcourir l'ensemble des valeurs contenu dans la collection, mais il n'impose pas d'ordre particulier. | |
// ça va dépendre de l'implémentation de la classe. Dans le cas du tableau ou de la liste, vous pouvez être à peut près sûr que vous aurez les elements dans l'ordre. Mais ce n'est pas vrai avec les dictionnaires par exemple qui a un fonctionnement complètement différent qui perd la notion "d'ordre" dans la collection. | |
// L'explication du dictionnaire est dans le document voir "les classes qui sont classes" | |
Dictionary<int, int> dic = new Dictionary<int, int>() | |
{ | |
{ 12, 12 }, | |
{ 23, 23 }, | |
{ 34, 34 }, | |
}; | |
foreach (var el in dic) | |
{ | |
Console.WriteLine(el); // on est pas sûr de l'ordre d'écriture, mais on est sûr que l'on va voir passer toutes les valeurs inséré dans le dictionnaire | |
} | |
// Parler de l'interface IEnumerable c'est mettre le doigt dans un mécanisme du C# qui est très avancé | |
// On peut jouer avec ce fameux itérateur pour faire quelques dingueries, voir Linq (pas sur ce document) | |
#endregion Boucles | |
#region Un peu plus avancé .... | |
// ################################################## | |
// Coalescence | |
// ################################################## | |
// WIP | |
// ################################################## | |
// Tuple | |
// ################################################## | |
// WIP | |
// ################################################## | |
// Exception | |
// ################################################## | |
// ################################################## | |
// Try / Catch / Finally | |
// ################################################## | |
// ################################################## | |
// Using | |
// ################################################## | |
#endregion | |
} | |
#endregion | |
#region Les Classes qui sont classes | |
// ################################################## | |
// Console | |
// ################################################## | |
// WIP | |
// ################################################## | |
// File | |
// ################################################## | |
// WIP | |
void FileExample() | |
{ | |
File.ReadAllText("map.txt"); | |
FileStream fileStream = File.Open("map.txt", FileMode.Open); | |
fileStream.Dispose(); | |
using (FileStream fileStream2 = File.Open("map.txt", FileMode.Open)) | |
{ | |
} | |
} | |
// ################################################## | |
// Random | |
// ################################################## | |
// WIP | |
// ################################################## | |
// List | |
// ################################################## | |
// WIP | |
// ################################################## | |
// Dictionary | |
// ################################################## | |
// WIP | |
#endregion | |
} // Fin de la classe Résumé, pour la suite on va créer d'autres petites classes pour avoir des exemples plus parlants | |
#region Encapsulation | |
// WIP | |
#endregion | |
#region Events | |
// Dans l'exemple qui va suivre je vais mettre en place 2 classes | |
// Guard qui propose un évènement | |
// LevelManager qui s'inscrit à l'event | |
class Guard | |
{ | |
// Déclaration d'un évènement, ici, on propose aux gens à inscrire des fonctions void () | |
// une fonction void () s'appelle parfois une "procédures" dans certains langage | |
public event Action OnPlayerDetected; | |
public void SeePlayer() | |
{ | |
// On va donc pouvoir déclencher l'event | |
// ATTENTION, si personne ne s'est inscrit, l'event est null | |
// Il faut donc systématiquement vérifier si c'est nul, sinon on récupère une NullRefException | |
if (OnPlayerDetected != null) OnPlayerDetected.Invoke(); | |
// Syntaxe équivalente à juste au dessus, elle est juste plus agréable | |
// Ici le ? nous sert de detection pour le null. Si l'objet est null, on saute le reste de la ligne | |
// En revanche si c'est pas null, c'est que ça vaut le coup de continuer la ligne | |
OnPlayerDetected?.Invoke(); | |
} | |
} | |
class LevelManager | |
{ | |
// De l'autre côté on va donc avoir des objets qui vont exploiter l'event. | |
// On va pouvoir s'inscrire à cet event afin d'être averti quand ce dernier est invoqué | |
void Game() | |
{ | |
Guard g = new Guard(); | |
// Inscription à un evenement, on utilise l'opérateur += | |
// On donne le nom de la fonction qu'il faut inscrire, attention, on n'appelle pas la fonction | |
// On a donc pas de parenthèse après le nom de la fonction, on ne fait que "donner" cette fonction à l'event | |
g.OnPlayerDetected += DisplayMessage; | |
g.SeePlayer(); | |
// Pensez également à désinscrire vos fonctions quand ce n'est plus pertinent (ou quand vos objets sont détruit) | |
// C'est donc juste l'opération inverse : -= | |
g.OnPlayerDetected -= DisplayMessage; | |
} | |
// Ici on a la fonction qui est donc enregistrée dans l'évent. | |
// Attention, on DOIT respecter la signature de l'evenement concerné, on récupère donc tout les paramètres | |
// de l'event, on peut les utiliser ou non c'est nous qui décidons, mais on DOIT avoir la bonne signature | |
// Dans le cas d'une Action, il s'agit d'une fonction void () | |
private void DisplayMessage() | |
{ | |
Console.WriteLine("PiPinPon"); | |
} | |
// Si l'event est un Action<int> la signature de la fonction serait donc : | |
void Function(int param) { } | |
// Si l'event est un Action<int, string, float> la signature de la fonction serait donc : | |
void Function(int param1, string param2, float param3) { } | |
} | |
#endregion | |
#region Static | |
// WIP | |
#endregion | |
#region Héritage | |
// WIP | |
#endregion | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment