Skip to content

Instantly share code, notes, and snippets.

@KevinRoussel
Last active January 10, 2024 12:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save KevinRoussel/2ec3899db2cb8931f92b0efec7d6d34e to your computer and use it in GitHub Desktop.
Save KevinRoussel/2ec3899db2cb8931f92b0efec7d6d34e to your computer and use it in GitHub Desktop.
Master C#
// 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