Outils pour utilisateurs

Outils du site


developpement:dotnet:csharp:introduction

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 };

Source

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()); });
    }
}

Source

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 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

  1. Télécharger PDFBox 0.7.3
  2. Ces fichiers sont contenus dans l'archive :
    1. IKVM.GNU.Classpath.dll
    2. PDFBox-0.7.3.dll
    3. FontBox-0.1.0-dev.dll
    4. IKVM.Runtime.dll
  3. Ajouter des références au projet à IKVM.GNU.Classpath.dll et PDFBox-0.7.3.dll.
  4. 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

  1. 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();
}

Ressources

developpement/dotnet/csharp/introduction.txt · Dernière modification : 2023/10/06 05:06 de sgariepy