Table des matières
Le langage C#
Introduction et quelques notes pêle-mêle à propos du C#.
Constantes
private const double x = 1.0, y = 2.0, z = 3.0;
Types de données
Entiers
Type | Range | Size | Suffixe |
---|---|---|---|
sbyte | -128 → 127 | Signed 8-bit integer | |
byte | 0 → 255 | Unsigned 8-bit integer | |
char | U+0000 → U+ffff | Unicode 16-bit character | |
short | -32,768 → 32,767 | Signed 16-bit integer | |
ushort | 0 → 65,535 | Unsigned 16-bit integer | |
int | -2,147,483,648 → 2,147,483,647 | Signed 32-bit integer | |
uint | 0 → 4,294,967,295 | Unsigned 32-bit integer | U |
long | -9,223,372,036,854,775,808 → 9,223,372,036,854,775,807 | Signed 64-bit integer | L |
ulong | 0 → 18,446,744,073,709,551,615 | Unsigned 64-bit integer | UL |
Virgule flottante
Type | Approximate range | Precision | Suffixe |
---|---|---|---|
float | ±1.5e−45 to ±3.4e38 | 7 digits | F |
double | ±5.0e−324 to ±1.7e308 | 15-16 digits | D |
Décimale
Type | Approximate Range | Precision | .NET Framework type | Suffixe |
---|---|---|---|---|
decimal | ±1.0 × 10−28 to ±7.9 × 1028 | 28-29 significant digits | System.Decimal | M |
String
La signification du symbole @
devant une chaîne de caractère signifie que les caractères d'échappement (escape caracters) ne seront pas traitées. Très utile pour les chaînes du type :
"C:\\repertoire\\fichier.txt"
qui peut être remplacé par@"C:\\repertoire\\fichier.txt"
.- Ou bien pour les expressions régulières qui contiennent des caractères d'échappement :
@"^(97(8|9))?\d{9}(\d|X)$"
.
String Interpolation
On peut utiliser $
devant une chaîne pour pouvoir utiliser directement des variables.
string variable = "quelconque valeur"; string interpolation = $"On peut utiliser la valeur de la variable {variable} ici.";
Guid id = Guid.Empty; string inter = $"Commande {(!id.Equals(Guid.Empty) ? $"-Id {id}" : string.Empty)}";
Commentaires
Il y a une façon comparable à JavaDoc de documenter son code en C#. Si vous êtes dans Visual Studio, tapez trois barres obliques (///
) avant l'entête de l'élément à commenter (classe, méthode, enum, etc). Contrairement à Javadoc qui génère du HTML, le compilateur C# produit du XML, ce qui est plus flexible1).
Élément prédéfini | Utilisé pour |
---|---|
<c> | Une façon d'indiquer que le text inlus entre doit être vu comme du code |
<code> | Comme l'élément précédent (<c> ) pour le faire sur plusieurs lignes |
<example> | Permet de spécifier un exemple d'utilisation d'une méthode ou d'un membre de librairie |
<exception> | Permet de documenter une classe d'exception |
<include> | Permet de référer aux commentaires dans un autre fichier, utilisant la syntaxe XPath, qui décrit les types et les membres dans le code source. |
<list> | Utilisé pour insérer une liste dans le fichier de documentation |
<para> | Utilisé pour insérer un paragraphe dans le fichier de documentation |
<param> | Décrit un paramètre |
<paramref> | Ce tag permet de faire référence à un paramètre dans le texte |
<permission> | Permet de documenter les permission d'accès |
<remarks> | Ce tag permet de fournir des informations complémentaires sur une entité sous la forme d'une remarque. |
<returns> | Décrit la valeur retournée d'une méthode |
<see> | Ce tag permet de faire un lien vers un élément accessible dans le code |
<seealso> | Ce tag permet de faire un lien vers un élément qui sera inclus dans la section See Also. |
<summary> | Utilisé pour une description générale |
<value> | Permet de fournir des informations sur une propriété |
Ces tags sont utilisables en fonction de l'entité documentée2) :
Entité | Tags utilisables |
---|---|
class | <summary>, <remarks>, <seealso> |
struct | <summary>, <remarks>, <seealso> |
interface | <summary>, <remarks>, <seealso> |
delegate | <summary>, <remarks>, <seealso>, <param>, <returns> |
enum | <summary>, <remarks>, <seealso> |
constructor | <summary>, <remarks>, <seealso>, <param>, <permission>, <exception> |
property | <summary>, <remarks>, <seealso>, <value>, <permission>, <exception> |
method | <summary>, <remarks>, <seealso>, <param>, <returns>, <permission>, <exception> |
event | <summary>, <remarks>, <seealso> |
L'élément <list>
- de syntaxe
<list type="bullet" | "number" | "table"> <listheader> <term>term</term> <description>description</description> </listheader> <item> <term>term</term> <description>description</description> </item> </list>
Opérateurs
Voir C# Operators.
Object Initialization Expressions
Employe employe = new { Nom = "Tremblay", Prenom = "Réjean", Code = "rtrem009" };
List<Employe> listeEmploye = new List<Employe> { new Employe { Id = 1, Nom="Tremblay", Prenom ="Réjean" }, new Employe { Id = 2, Nom="Smith", Prenom ="John" }, new Employe { Id = 3, Nom="Gagnon", Prenom ="Alain" } };
Propriétés automatiques
public string Title { get; set; }
au lieu de :
private string title; public string Title { get { return title; } set { title = value; } }
Cast et parsing
Pour changer le type d'un objet :
- de casting
string s = (string)obj; // Change obj en string string s = ((Form)obj).Text; // Change obj en Form Object obj = new TextBox(); Button btn = obj as Button; // L'opérateur as retourne null si le cast ne peut pas se faire
Pour obtenir un int
à partir d'une chaîne :
- de parsing
int chiffre; string chaine; chaine = "1234"; chiffre = int.Parse(chaine);
Switch
Le commutateur switch
peut être utilisé avec des données intégrales ou des chaînes3).
- de switch avec données intégrales
using System; class SwitchTest { static void Main() { Console.WriteLine("Coffee sizes: 1=Small 2=Medium 3=Large"); Console.Write("Please enter your selection: "); string s = Console.ReadLine(); int n = int.Parse(s); int cost = 0; switch(n) { case 1: cost += 25; break; case 2: cost += 25; goto case 1; case 3: cost += 50; goto case 1; default: Console.WriteLine("Invalid selection. Please select 1, 2, or 3."); break; } if (cost != 0) { Console.WriteLine("Please insert {0} cents.", cost); } Console.WriteLine("Thank you for your business."); Console.ReadLine(); } }
- de switch avec des chaines
switch(s) { case "1": // ... case "2": // ... }
Listes génériques
Il es possible de créer des listes d'objets4).
- de liste
using System; using System.Collections.Generic; public class Example { public static void Main() { List<string> dinosaurs = new List<string>(); Console.WriteLine("\nCapacity: {0}", dinosaurs.Capacity); dinosaurs.Add("Tyrannosaurus"); dinosaurs.Add("Amargasaurus"); dinosaurs.Add("Mamenchisaurus"); dinosaurs.Add("Deinonychus"); dinosaurs.Add("Compsognathus"); Console.WriteLine(); foreach(string dinosaur in dinosaurs) { Console.WriteLine(dinosaur); } Console.WriteLine("\nCapacity: {0}", dinosaurs.Capacity); Console.WriteLine("Count: {0}", dinosaurs.Count); Console.WriteLine("\nContains(\"Deinonychus\"): {0}", dinosaurs.Contains("Deinonychus")); Console.WriteLine("\nInsert(2, \"Compsognathus\")"); dinosaurs.Insert(2, "Compsognathus"); Console.WriteLine(); foreach(string dinosaur in dinosaurs) { Console.WriteLine(dinosaur); } Console.WriteLine("\ndinosaurs[3]: {0}", dinosaurs[3]); Console.WriteLine("\nRemove(\"Compsognathus\")"); dinosaurs.Remove("Compsognathus"); Console.WriteLine(); foreach(string dinosaur in dinosaurs) { Console.WriteLine(dinosaur); } dinosaurs.TrimExcess(); Console.WriteLine("\nTrimExcess()"); Console.WriteLine("Capacity: {0}", dinosaurs.Capacity); Console.WriteLine("Count: {0}", dinosaurs.Count); dinosaurs.Clear(); Console.WriteLine("\nClear()"); Console.WriteLine("Capacity: {0}", dinosaurs.Capacity); Console.WriteLine("Count: {0}", dinosaurs.Count); } }
Initialisation de collections
Une liste:
var list = new List<int> { 1, 2, 3 };
Après on peut créer une seconde liste basée sur la première:
var list2 = new List<int>(list) { 4, 5 };
Ou même:
string[] ab = new string[] { "a", "b" }; List<string> abcd = new List<string>(ab) { "c", "d" };
Un dictionnaire:
var dict = new Dictionary<int, int> { { 0, 1 }, { 1, 2 }, }; // Equivalent to var dict = new Dictionary<int, int>(); dict.Add(0, 1); dict.Add(1, 2); var dict = new Dictionary<int, int> { [0] = 1, [1] = 2, }; // Equivalent to var dict = new Dictionary<int, int>(); dict[0] = 1; dict[1] = 2;
Pour se baser sur des dictionnaires existants:
var dict2 = new Dictionary<int, int>(dict) { [2] = 3 }; // ou var dict2 = new Dictionary<int, int>(dict) { [0] = 4 };
Manipulations de dates
Il est possible de soustraire et additionner avec les dates en utilisant TimeSpan
.
DateTime UneDate = DateTime.Parse("12 May 2007 20:15:00"); // Ajout d'une heure, 10 minutes, 30 secondes UneDate = UneDate + new TimeSpan(1, 10, 30); // UneDate = 2 May 2007 21:25:30 // Soustraire 15 jours UneDate = UneDate - new TimeSpan(15, 0, 0, 0); // UneDate = 17 Apr 2007 21:25:30
DateHelper
public class DateHelper { public static DateTime _UnixEpoch = new DateTime(1970,1,1,0,0,0); public static int ToUnixTimeStamp(int year, int month, int day) { DateTime dt = new DateTime(year, month, day, 0, 0, 0); TimeSpan ts = (dt - _UnixEpoch); return (int)ts.TotalSeconds; } public static int ToBasicDateFormat(int year, int month, int day) { return(year*10000 + month*100 + day); } public static long ToTicks(int year, int month, int day) { return new DateTime(year, month, day).Ticks; } }
public DateTime GetFirstDayOfCurrentWeek() { DateTime firstDayOfCurrentWeek = DateTime.Now.AddDays(DayOfWeek.Monday - DateTime.Now.DayOfWeek); firstDayOfCurrentWeek.AddHours(-firstDayOfCurrentWeek.Hour).AddMinutes(-firstDayOfCurrentWeek.Minute); return firstDayOfCurrentWeek; }
Tableau de int à tableau de string
public static class ExtensionMethods { public static string[] ToStringArray(this int[] intArray) { return Array.ConvertAll<int, string>(intArray, delegate(int intParameter) { return intParameter.ToString(); }); } public static int[] ToIntArray(this string[] strArray) { return Array.ConvertAll<string, int>(strArray, delegate(string intParameter) { return int.Parse(intParameter.ToString()); }); } }
enums
flags
[Flags] public enum Styles { None = 0, Flat = 1, Sunken = 2, Raised = 4 }
La valeur 0 devrait être une absence de flag. (Réf: Effective C#, Bill Wagner, p110).
Events
tetxBox.TextChanged += new EventHandler(this.TextChangedHandler);
Surcharge d'Equals et opérateurs
public override bool Equals(Object obj) { if (obj == null) { return false; } Employe t = obj as Employe ; if (t == null) { return false; } if (this.IdEmploye == t.IdEmploye) { return true; } else { return false; } } public static bool operator ==(Employe x, Employe y) { // Si les deux sont null, ou s'ils sont la meme instance, retourne true if (System.Object.ReferenceEquals(x, y)) { return true; } // Si un est null, mais pas les deux, retourne false. if (((object)x == null) || ((object)y == null)) { return false; } return x.Equals(y); } public static bool operator !=(Employe x, Employe y) { // Si les deux sont null, ou s'ils sont la meme instance, retourne false if (System.Object.ReferenceEquals(x, y)) { return false; } // Si un est null, mais pas les deux, retourne true. if (((object)x == null) || ((object)y == null)) { return true; } return !x.Equals(y); }
Source : Guidelines for Overloading Equals() and Operator == (C# Programming Guide)
Parallélisme
Threads
Main thread:
void Main() { Thread.CurrentThread.Name = "Main Thread here"; }
Créer un thread:
void Main() { Thread thread = new Thread(WriteUsingNewThread); thread.Name = "Worker Thread"; thread.Start(); } private static void WriteUsingNewThread() { for (int i = 0; i < 1000; i++) { Console.WriteLine(" Z" + i + " "); } }
Ressources partagées
Shared resources
- Le CLR assigne à chaque thread sa propre mémoire locale pour garder les variables isolées
- Une copie séparée des variables locales est créée pour chaque pile mémoire du thread
using System.Threading; private static bool isCompleted = false; void Main() { Thread thread = new Thread(HelloWorld); thread.Start(); HelloWorld(); } private static void HelloWorld() { if (!isCompleted) { Console.WriteLine("Hello World"); isCompleted = true; } }
Threads vs Processes
Thread:
- Exécuté en parallèle à même un seul processus
- Mémoire partagée avec les autres threads de la même application
Processus:
- Complètement isolés de l'un des autres
Copie d'état
void Main() { Employee employee = new Employee (); employee.Name = "my name"; employee.CompanyName = "company name"; ThreadPool.QueueUserWorkItem(new WaitCallback(DisplayEmployeeInfo), employee); } private static void DisplayEmployeeInfo(object employee) { Console.WriteLine(Thread.CurrentThread.IsThreadPoolThread); Employee emp = employee as Employee; Console.WriteLine("Person name is {0} and company name is {1}", emp.Name, emp.CompanyName); } class Employee { public string Name { get; set; } public string CompanyName { get; set; } }
Set Max Threads
À titre informatif, on peut demander combien de processeurs sont disponibles:
var processorCount = Environment.ProcessorCount;
Par exemple, sur un i7 12700K, processCount
serait 20.
On pourrait donc faire:
ThreadPool.SetMaxThreads(processorCount * 2, processorCount * 2);
Les deux arguments de SetMaxThreads
sont:
int workerThreads
: Le maximum de worker threads dans le thread pool.int completionPortThreads
: Le maximum de threads I/O asynchrones dans le thread pool.
On pourrait obtenir le minimum des valeurs, et les multiplier selon les besoins:
int workerThreads = 0; int completionThreads = 0; ThreadPool.GetMinThreads(out workerThreads, out completionThreads); ThreadPool.SetMaxThreads(workerThreads * 2, completionThreads * 2);
Exception Handling
Si on exécute Execute()
(sans le new Thread(Execute).Start()
), on va tomber dans le catch
et le message ex.Message
s'affichera dans la console vu qu'ils sont dans le même thread.
Par contre, si on exécute new Thread(Execute).Start();
(sans le Execute()
), on ne tombera pas dans le catch
vu que le throw null
(là où l'exception est générée) se trouve dans un autre thread (dans worker thread, tandis que le try/catch
est dans le main thread).
using System.Threading; void Main() { Demo(); } private static void Demo() { try { Execute(); // Première fois avec cette ligne new Thread(Execute).Start(); // Deuxième fois avec cette ligne } catch (Exception ex) { Console.WriteLine(ex.Message); } } static void Execute() { throw null; }
Il faut donc voir là où l'erreur peut être généré plus localement et le mettre dans un try/catch
:
private static void Demo() { new Thread(Execute).Start(); } static void Execute() { try { throw null; // pourrait être n'importe quelle méthode/opération qui lance des exceptions. } catch (Exception ex) { Console.WriteLine(ex.Message); } }
Tasks
Les Task
sont comme des threads mais de plus haut niveau et faciles à gérer, entre autres pour le retour de valeurs.
using System.Threading.Tasks; async void Main() { // Simple tâche Task task = new Task(SimpleMethod); task.Start(); // Tâche qui retourne une valeur Task<string> taskThatReturns = new Task<string>(MethodThatReturns); taskThatReturns.Start(); taskThatReturns.Wait(); Console.WriteLine("Returning MethodThatReturns' return is '{0}'", taskThatReturns.Result); } private static void SimpleMethod() { Console.WriteLine("Simple Task here"); } private static string MethodThatReturns() { Thread.Sleep(2000); // Simuler un temps d'opération, dans une Task on devrait utiliser Task.Delay(ms) return "returns"; }
Task avec opérations I/O
void Main() { Task<string> task = Task.Factory.StartNew<string> (() => GetPosts("https://jsonplaceholder.typicode.com/posts")); DoSomethingElse(); // task.Wait(); // Décommenté, l'exception se produira ici, et le catch n'aura pas d'effet. // Si on doit utiliser le Wait(), le déplacer dans le try try { task.Wait(); Console.WriteLine(task.Result); } catch (AggregateException ex) { Console.WriteLine(ex.Message); } } string GetPosts(string urlv) { // throw null; // Décommenter pour simuler une erreur // WebClient ci-dessous est obsolète, mais on l'a gardé vu que c'est simplement un exemple. using (var client = new System.Net.WebClient()) { return client.DownloadString(urlv); } } void DoSomethingElse() { Console.WriteLine("Something Else"); }
Tasks with continuation
Permet d'exécuter de la logique suite à une logique précédente (appelé antecedent).
Task chaining:
- Possible de passer des données d'un antécédent à une tâche de continuation.
- Possible de passer les exception de l'antécédent à la continuation
- On peut contrôler comment la continuation est invoquée
- Possible d'annuler la continuation
Exemple simple d'utilisation de continuation:
void Main() { Task<string> antecedent = Task.Run(() => DateTime.Today.ToShortDateString()); Task<string> continuation = antecedent.ContinueWith(x => "Today is " + antecedent.Result); Console.WriteLine(continuation.Result); }
Synchronization
Monitor
Exemple de Monitor
, qui est semblable à lock
:
void Main() { Account account = new Account(20000); Task task1 = Task.Factory.StartNew(() => account.WithdrawRandomly()); Task task2 = Task.Factory.StartNew(() => account.WithdrawRandomly()); Task task3 = Task.Factory.StartNew(() => account.WithdrawRandomly()); Task.WaitAll(task1, task2, task3); Console.WriteLine("All tasks completed"); } public class Account { Object caztonLock = new Object(); private int balance; public Account(int initialBalance) { balance = initialBalance; } public int Withdraw(int amount) { if (balance < 0) { throw new Exception("Not enough balance"); } Monitor.Enter(caztonLock); // equivalent to lock(object) {} try { if (balance >= amount) { Console.WriteLine("Amount drawn: {0}", amount); balance = balance - amount; return balance; } } finally { Monitor.Exit(caztonLock); } return 0; } public void WithdrawRandomly() { for (int i = 0; i < 100; i++) { var balance = Withdraw((new Random()).Next(2000, 5000)); if (balance > 0) { Console.WriteLine("Balance left: {0}", balance); } } } }
Reader/Writer Locks
private static ReaderWriterLockSlim readerWriterLockSlim = new ReaderWriterLockSlim(); private static Dictionary<int, string> persons = new Dictionary<int, string>(); private static Random random = new Random(); void Main() { var task1 = Task.Factory.StartNew(Read); var task2 = Task.Factory.StartNew(Write, "Someone"); var task3 = Task.Factory.StartNew(Write, "Simone"); var task4 = Task.Factory.StartNew(Read); var task5 = Task.Factory.StartNew(Read); Task.WaitAll(task1, task2, task3, task4, task5); } private static void Read() { for (int i = 0; i < 10; i++) { readerWriterLockSlim.EnterReadLock(); Thread.Sleep(50); // simulation of reading readerWriterLockSlim.ExitReadLock(); } } private static void Write(object user) { for (int i = 0; i < 10; i++) { int id = GetRandom(); readerWriterLockSlim.EnterWriteLock(); var person = "Person " + i; persons.Add(id, person); readerWriterLockSlim.ExitWriteLock(); Console.WriteLine(user + " added " + person); Thread.Sleep(250); } } private static int GetRandom() // Pour la synchronisation { lock (random) { return random.Next(2000, 3000); } }
Mutex
private static Mutex mutex = new Mutex(); // Initially owned est par défaut à false void Main() { for (int i = 0; i < 10; i++) { Thread thread = new Thread(AquireMutex); thread.Name = string.Format("Thread {0}", i + 1); thread.Start(); } } private static void AquireMutex(object obj) { mutex.WaitOne(); // On peut utiliser la façon suivante pour ne pas bloquer le thread continuellement //if (!mutex.WaitOne(TimeSpan.FromSeconds(1), false)) //{ // return; //} DoSomething(); mutex.ReleaseMutex(); Console.WriteLine("The mutex has been released by {0}", Thread.CurrentThread.Name); } static void DoSomething() { Thread.Sleep(1000); Console.WriteLine("Mutex aquired by {0}", Thread.CurrentThread.Name); }
Semaphore
private static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(3); // '3' est la capacité void Main() { for (int i = 0; i < 10; i++) { new Thread(EnterSemaphore).Start(i + 1); } } void EnterSemaphore(object id) { Console.WriteLine("{0} is waiting to be part of the club", id); semaphoreSlim.Wait(); Console.WriteLine("{0} is part of the club", id); Thread.Sleep(1000 / (int) id); Console.WriteLine("{0} left the club", id); }
Threading in C# Ressources
- Threading in C# (Albahari)
- Threading in C# (LinkedIn Learning)
Threading Avancé
Thread Safety
- Multithreaded code should have thread safety implemented correctly and tested
- Code is thread safe if shared data structures are modified to ensure all threads behave properly, fulfill design specifications, and do not have unintended interactions
- In the .NET Framework, static methods are thread safe
- Not all static methods are thread safe
void Main() { var task1 = Task.Factory.StartNew(AddItem); var task2 = Task.Factory.StartNew(AddItem); var task3 = Task.Factory.StartNew(AddItem); var task4 = Task.Factory.StartNew(AddItem); var task5 = Task.Factory.StartNew(AddItem); Task.WaitAll(task1,task2,task3,task4,task5); foreach (var item in items) { Console.WriteLine(item.Key + ": " + item.Value); } } private static void AddItem() { lock (items) { Console.WriteLine("Lock acquired by {0}", Task.CurrentId); items.Add(items.Count, "World " + items.Count); } Dictionary<int, string> dictionary; lock (items) { Console.WriteLine("Lock 2 acquired by {0}", Task.CurrentId); dictionary = items; } } private static void AddItemNotSafe() { if (items.ContainsKey(1)) { // Ceci devrait être "locked" // do something } else { items.Add(1, "Hello World"); // Ceci devrait être "locked" } }
Thread Affinity
- Thread that instantiates an object is the only thread that is allowed to access its members.
Pros:
- Don't need a lock to access a UI object in Windows Presentation Foundation (WPF)
- Able to access all objects within a thread without a lock, since no other thread can access them
Cons:
- Cannot call members on a thread that is thread safe from a different thread
- Requires request to be marshaled to the thread-safe thread that created the object
Ceci peut mener à une erreur System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
Signaling
Permet de signaler à d'autres threads qu'une ressource peut être accédée.
EventWaitHandle permet le signalement
- AutoResetEvent
- Utilisé quand un thread a besoin d'un accès exclusif à une ressource
- Une seule ressource peut accéder la ressource à la fois
- Se ferme automatiquement
- Un thread attend pour un signal en appelant
WaitOne()
- Calling
Set
signals release of a waiting thread - Si plusieurs threads appellent WaitOne, une file d'attente (queue) se forme
- ManualResetEvent
- CountdownEvent
static EventWaitHandle eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); // Equivalent: static EventWaitHandle eventWaitHandle = new AutoResetEvent(false); void Main() { Task.Factory.StartNew(WorkerThread); Thread.Sleep(2500); // Simulation de temps de traitement eventWaitHandle.Set(); } void WorkerThread() { Console.WriteLine("Waiting to Enter the gate"); eventWaitHandle.WaitOne(); // Logic Console.WriteLine("Gate Entered"); }
Two-way signaling
Le two-way signaling utilise deux EventWaitHandle.
static EventWaitHandle first = new AutoResetEvent(false); static EventWaitHandle second = new AutoResetEvent(false); static object customLock = new object(); static string value = String.Empty; void Main() { Task.Factory.StartNew(WorkerThread); Console.WriteLine("Main thread is waiting"); first.WaitOne(); lock (customLock) { value = "Updating value in main thread"; Console.WriteLine(value); } Thread.Sleep(1000); second.Set(); Console.WriteLine("Released Worker thread"); } void WorkerThread() { Thread.Sleep(1000); lock (customLock) { value = "Updating value in worker thread"; Console.WriteLine(value); } first.Set(); Console.WriteLine("Release main thread"); Console.WriteLine("Worker thread is waiting..."); second.WaitOne(); }
Manual Event Reset
static ManualResetEvent customEvent = new ManualResetEvent(false); // Equivalent: static EventWaitHandle eventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset); void Main() { Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Thread.Sleep(1000); Console.WriteLine("Press a key to release all the threads"); Console.Read(); customEvent.Set(); Thread.Sleep(1000); Console.WriteLine("Press a key again. Thread won't block even if they call WaitOne"); Console.Read(); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Thread.Sleep(1000); Console.WriteLine("Press a key again. Thread will block if they call WaitOne"); Console.Read(); customEvent.Reset(); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Task.Factory.StartNew(CallWaitOne); Thread.Sleep(1000); Console.WriteLine("Press a key again calls Set()"); Console.Read(); customEvent.Set(); Console.ReadLine(); } void CallWaitOne() { Console.WriteLine("{0} has called WaitOne", Task.CurrentId); customEvent.WaitOne(); Console.WriteLine("{0} finally ended", Task.CurrentId); }
Countdown
static CountdownEvent customCountdown = new CountdownEvent(5); void Main() { Task.Factory.StartNew(DoSomething); Task.Factory.StartNew(DoSomething); Task.Factory.StartNew(DoSomething); Task.Factory.StartNew(DoSomething); Task.Factory.StartNew(DoSomething); customCountdown.Wait(); Console.WriteLine("Signal has been called 5 times"); } void DoSomething() { Thread.Sleep(250); Console.WriteLine("{0} is calling signal", Task.CurrentId); customCountdown.Signal(); }
Task Parallel Library (TPL)
- Set of public types and APIs that can be found in two namespaces:
- System.Threading
- System.Threading.Tasks
- Simplifies process of adding parallelism and concurrency to applications
- Value is ability to scale degree of concurrency dynamically
- Handles partitioning of work
- Schedules threads on ThreadPool
- Allows for task cancellation
- Handles state management
- Not all code is suitable for parallelization
- Threading of any type has an associated overhead
- In some cases, multithreading may be slower than sequential code
Exemple simple:
void Main() { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < 10; i++) { Console.WriteLine(i); } stopwatch.Stop(); Console.WriteLine("Time taken: {0}", stopwatch.ElapsedTicks); stopwatch.Start(); Parallel.For(0, 10, i => { Console.WriteLine(i); }); stopwatch.Stop(); Console.WriteLine("Time taken: {0}", stopwatch.ElapsedTicks); }
Le cas en parallèle prend plus de temps dû au overhead. L'instruction n'étant pas assez complexe pour justifier la parallèlisation.
// Générer des images: https://picsum.photos/3840/2160 void Main() { var path = Directory.GetCurrentDirectory(); var files = Directory.GetFiles(path + @"\pictures", "*.jpg"); var normalAlteredPath = path + @"\normalAlteredPath"; var parallelAlteredPath = path + @"\parallelAlteredPath"; Directory.CreateDirectory(normalAlteredPath); Directory.CreateDirectory(parallelAlteredPath); ParallelExecutionMode(files, normalAlteredPath); NormalExecutionMode(files, parallelAlteredPath); } void NormalExecutionMode(string[] files, string alteredPath) { Stopwatch stopwatch = Stopwatch.StartNew(); foreach (var currentFile in files) { var file = Path.GetFileName(currentFile); using (var fileBitmap = new Bitmap(currentFile)) { fileBitmap.RotateFlip(RotateFlipType.Rotate270FlipX); fileBitmap.Save(Path.Combine(alteredPath, file)); Console.WriteLine("Thread {0}", Thread.CurrentThread.ManagedThreadId); } } Console.WriteLine("Normal execution time: {0}", stopwatch.ElapsedMilliseconds); stopwatch.Stop(); } void ParallelExecutionMode(string[] files, string alteredPath) { Stopwatch stopwatch = Stopwatch.StartNew(); Parallel.ForEach(files, currentFile => { var file = Path.GetFileName(currentFile); using (var fileBitmap = new Bitmap(currentFile)) { fileBitmap.RotateFlip(RotateFlipType.Rotate270FlipX); fileBitmap.Save(Path.Combine(alteredPath, file)); Console.WriteLine("Thread {0}", Thread.CurrentThread.ManagedThreadId); } }); Console.WriteLine("Parallel execution time: {0}", stopwatch.ElapsedMilliseconds); stopwatch.Stop(); }
void Main() { var list = Enumerable.Range(0, 100000000).ToArray(); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); ParallelOptions parallelOptions = new ParallelOptions(); parallelOptions.CancellationToken = cancellationTokenSource.Token; parallelOptions.MaxDegreeOfParallelism = System.Environment.ProcessorCount; Console.WriteLine("Press 'x' to cancel"); Task.Factory.StartNew(() => { if (Console.ReadLine() == "x") { cancellationTokenSource.Cancel(); } long total = 0; try { Parallel.For<long>(0, list.Length, parallelOptions, () => 0, (count, parallelLoopState, subtotal) => { Thread.Sleep(200); parallelOptions.CancellationToken.ThrowIfCancellationRequested(); subtotal += list[count]; return subtotal; }, (x) => { Interlocked.Add(ref total, x); }); } catch (OperationCanceledException ex) { Console.WriteLine("Cancelled " + ex.Message); } finally { cancellationTokenSource.Dispose(); } Console.WriteLine("The final sum is {0}", total); }); }
Continuation with state
void Main() { Task<DateTime> task = Task.Run(() => DoSomething()); List<Task<DateTime>> continuationTasks = new List<Task<DateTime>>(); for (int i = 0; i < 3; i++) { task = task.ContinueWith((x, y) => DoSomething(), new Person { Id = i }); continuationTasks.Add(task); } task.Wait(); foreach (var continuation in continuationTasks) { Person person = continuation.AsyncState as Person; Console.WriteLine("Task finished at " + continuation.Result + ". Person id is {0}", person.Id); } } static DateTime DoSomething() { return DateTime.Now; } internal class Person { public int Id { get; set; } }
TaskCompletionSource
void Main() { TaskCompletionSource<Product> taskCompletionSource = new TaskCompletionSource<Product>(); Task<Product> lazyTask = taskCompletionSource.Task; Task.Factory.StartNew(() => { Thread.Sleep(2000); taskCompletionSource.SetResult(new Product { Id = 1, Name = "Some name" }); }); Task.Factory.StartNew(() => { if (Console.ReadLine() == "x") { Product result = lazyTask.Result; Console.WriteLine("Result is {0}", result.Name); } }); Thread.Sleep(5000); } class Product { public int Id { get; set; } public string Name { get; set; } }
PLINQ
Parallel LINQ:
- Automates parallelization
- Considéré déclaratif plutôt qu'impératif
- Opérateurs qui font en sorte que ce n'est pas parallélisé:
- Take, Select, SelectMany, Skip, TakeWhile, SkipWhile, ElementAt
- Anomalies
- Join, GroupBy, GroupJoin, Distinct, Union, Intersect, Except
- Force parallelism:
- .AsParallel().withExecutionMode(ParallelExecution.ForceParallelism)
void Main() { var list = Enumerable.Range(1, 100000); var primeNumbers = list .AsParallel() .Where(IsPrime); Console.WriteLine("{0} prime numbers", primeNumbers.Count()); } bool IsPrime(int x) { if (x == 1) return false; if (x == 2) return true; if (x % 2 == 0) return false; var boundary = (int)Math.Floor(Math.Sqrt(x)); for (int i = 3; i <= boundary; i += 2) { if (x % i == 0) { return false; } } return true; }
Degree of Parallelism
void Main() { List<string> websites = new List<string>(); websites.Add("apple.com"); websites.Add("google.com"); websites.Add("microsoft.com"); List<PingReply> responses = websites .AsParallel() .WithDegreeOfParallelism(websites.Count()) .Select(PingSites) .ToList(); foreach (var response in responses) { Console.WriteLine(response.Address + " " + response.Status + " " + response.RoundtripTime); } Console.ReadLine(); } private static PingReply PingSites(string websiteName) { Ping ping = new Ping(); return ping.Send(websiteName); }
Thread Marshalling
Pattern Matching
void Main() { var circle = new Circle(5); var circleRadius100 = new Circle(250); var rectangle = new Rectangle(420, 1337); var square = new Rectangle(70, 70); var shapes = new List<Shape> { circle, circleRadius100, rectangle, square }; var randomShape = shapes[new Random().Next(shapes.Count)]; CSharp6Feature(randomShape); CSharp7Feature(randomShape); CSharp8Feature(randomShape); CSharp9Feature(randomShape); } private void CSharp6Feature(Shape shape) { Console.WriteLine("=== C# 6 Pattern matching features ==="); if (shape is Circle) // 'is' operator { var circle = (Circle)shape; Console.WriteLine($"Circle with radius {circle.Radius}"); } else { Console.WriteLine($"Shape is something else"); } } private void CSharp7Feature(Shape shape) { Console.WriteLine("=== C# 7 Pattern matching features ==="); if (shape is Circle circle) // implicit casting { Console.WriteLine($"Circle with radius {circle.Radius}"); } else { Console.WriteLine($"Shape is something else"); } // using switch switch (shape) { case Circle c: Console.WriteLine($"Circle with radius {c.Radius}"); break; case Rectangle r when r.Height == r.Width: Console.WriteLine($"This is a square"); break; default: Console.WriteLine($"Shape is something else"); break; } } private void CSharp8Feature(Shape shape) { Console.WriteLine("=== C# 8 Pattern matching features ==="); if (shape is Circle { Radius: 10 }) { Console.WriteLine($"Circle with radius of 10"); } var shapeDetails = shape switch { Circle => "This is a circle", // we can drop the 'cir' is not used Rectangle rec when rec.Height == rec.Width => "This is a square", _ => "Shape is something else" }; } private void CSharp9Feature(Shape shape) { Console.WriteLine("=== C# 9 Pattern matching features ==="); if (shape is not Rectangle) // 'not' added { Console.WriteLine($"This is not a rectangle"); } if (shape is Circle { Radius: > 100 and < 200, Area: >= 1000 }) { Console.WriteLine($"Circle with radius greater than 100"); } // that can be used like so: if (shape is not null) {...} var shapeDetails = shape switch { Circle => "This is a circle", // we can drop the 'cir' is not used Rectangle rec when rec.Height == rec.Width => "This is a square", { Area: 100 } => "Area is 100", _ => "Shape is something else" }; var areaDetails = shape.Area switch { >= 100 and <= 200 => "Area is between 100 and 200", _ => "" }; } public static class Extensions { public static bool IsLetter(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z'; } public abstract class Shape { public abstract double Area { get; } } public class Rectangle : Shape, ISquare { public Rectangle(int height, int width) { Height = height; Width = width; } public override double Area => Height * Width; public int Height { get; set; } public int Width { get; set; } } public class Circle : Shape { private const double PI = Math.PI; public Circle(int diameter) { Diameter = diameter; } public int Diameter { get; set; } public int Radius => Diameter / 2; public override double Area => PI * Radius * Radius; } public interface ISquare { int Height { get; set; } int Width { get; set; } } static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate) => (groupSize, visitDate.DayOfWeek) switch { (<= 0, _) => throw new ArgumentException("Group size must be positive"), (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m, (>= 5 and < 10, DayOfWeek.Monday) => 20.0m, (>= 10, DayOfWeek.Monday) => 30.0m, (>= 5 and < 10, _) => 12.0m, (>= 10, _) => 15.0m, _ => 0.0m };
Reactive
On peut créer des observable de la façon suivante:
using System.Reactive; using System.Reactive.Disposables; // ... var observable = Observable.Create<int>(observer => { for (int i = 0; i < 100; i++) { observer.OnNext(i); } observer.OnCompleted(); return Disposable.Empty; });
Requiert le package System.Reactive
Debug
using System.Diagnostics; //.. Debug.Listeners.Add(new ConsoleTraceListener()); Debug.WriteLine("test");
Localization
CultureInfo
CultureInfo cu = CultureInfo.CreateSpecificCulture("en-CA");
Dates localisés
var frCACultureInfo = new CultureInfo("fr-CA"); var dateTimeInfo = frCACultureInfo.DateTimeFormat; string mois = dateTimeInfo.GetMonthName(DateTime.Now.Month);
CultureInfo invCulture = CultureInfo.InvariantCulture; DateTime dtIn = DateTime.Now; dtIn.ToString("d", invCulture).Dump(); string dt = dtIn.ToString("d", invCulture); (dt.Substring(8, 2) + dt.Substring(0, 2) + dt.Substring(3, 2)) .Dump();
Gestion des PDFs
Lire les PDF en C# avec PDFBox
- Télécharger PDFBox 0.7.3
- Ces fichiers sont contenus dans l'archive :
IKVM.GNU.Classpath.dll
PDFBox-0.7.3.dll
FontBox-0.1.0-dev.dll
IKVM.Runtime.dll
- Ajouter des références au projet à
IKVM.GNU.Classpath.dll
etPDFBox-0.7.3.dll
. - Déposer tous les fichiers dans le répertoire
bin
du projet.
using System; using org.pdfbox.pdmodel; using org.pdfbox.util; namespace PDFReader { class Program { static void Main(string[] args) { PDDocument doc = PDDocument.load("lopreacamasa.pdf"); PDFTextStripper pdfStripper = new PDFTextStripper(); Console.Write(pdfStripper.getText(doc)); } } }
Lire les PDF avec iTextSharp
- Télécharger iTextSharp
using iTextSharp.text; using iTextSharp.text.pdf; // create an instance of the pdfparser class PDFParser pdfParser = new PDFParser(); // extract the text String result = pdfParser.ExtractText(pdfFile);
Sources
Assembly
Retrouver la version d'un autre projet (ou DLL):
var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.ManifestModule.ToString() == "Nom_du_fichier.dll"); if (assembly != null) { string version = assembly.GetName().Version.ToString(); }