RSS

Archives de Tag: C Sharp

Dispose parallèle et efficace

Dans .NET, pour chaque classe utilisée en parallèle (par plusieurs tâches, ou « thread safe » pour les anglophones) et implémentant IDisposable, il faut péniblement implémenter une méthode « Dispose » fonctionnant en parallèle aussi.
Ici je propose une petite structure qui nous facilite la vie, et qui en plus a le mérite d’être efficace: rapide et petite.

Code de la structure:

internal struct DisposeParallèle
{
enum LibéréOuPas { PasEncoreSupprimé, DéjàSupprimé }

int déjàSuppriméOuPas;

internal void Dispose(Action MéthodeDeSuppression)
{
// "Interlocked" est bien plus rapide que "lock".

LibéréOuPas l = (LibéréOuPas)
System.Threading.Interlocked.CompareExchange(
ref this.déjàSuppriméOuPas,
(int)LibéréOuPas.DéjàSupprimé,
(int) LibéréOuPas.PasEncoreSupprimé);

// Ici, "déjàSuppriméOuPas" a toujours pour valeur DéjàSupprimé, et il n'en changera plus. 😉

if (l == LibéréOuPas.PasEncoreSupprimé)
MéthodeDeSuppression();
}
}

Remarques:

  1. On utilise « Interlocked », qui est bien plus rapide que « lock ».
    La preuve: http://www.drdobbs.com/windows/boosting-performance-with-atomic-operati/226900048
  2. On se contente de mémoriser un simple entier 32 bits, là où un « lock » aurait nécessité un objet et un booléen.
    J’aurais aimé ne mémoriser qu’un booléen, d’ailleurs, mais « Interlocked »
    ne propose pas de méthode « CompareExchange » sur un booléen.
  3. En tant que simple structure, on économise les ressources objets, et on n’a pas besoin d’appeler un constructeur.

Exemple d’utilisation:

internal class MaClasse : IDisposable
{
DisposeParallèle disposeParallèle;

void IDisposable.Dispose()
{
this.disposeParallèle.Dispose(this.suppression);
}

void suppression()
{
// votre code...
}

~MaClasse() // si nécessaire
{
((IDisposable)this).Dispose();
}
}

Ça reste assez simple à utiliser, avec un code bien découpé donc facile à maintenir.

Publicités
 

Étiquettes : , , , , , , , ,

Différence en C# entre new et override lors du remplacement d’une fonction à hériter

Au départ, un débutant en C# se demande quelle peut bien être la différence entre new et override, et lequel choisir lorsqu’il souhaite remplacer une méthode dans une classe héritante.

Vocabulaire utilisé ici

  • Classe à hériter = classe de base.
  • Classe héritante = classe dérivée (elle hérite de la classe de base).

En une phrase

Avec override (et donc virtual), le code de la classe à hériter (la classe de base) appellera soit sa propre méthode virtual, soit la méthode d’une classe héritante (dérivée de cette classe de base) si celle-ci la remplace avec override.

Exemple montrant la différence

Le code:

public class ClasseÀHériter
{
    public string FonctionNormale()
    {
        return "FonctionNormale dans la classe de base";
    }
    public virtual string FonctionExplicitementRemplaçable()
    {
        return "FonctionExplicitementRemplaçable dans la classe de base ";
    }
    public string Résultat()
    {
        return
            this.FonctionNormale()  // Appelle toujours la fonction définie dans cette classe.
            + "\n"
            + this.FonctionExplicitementRemplaçable(); // La fonction appelée a pu être remplacée dans une classe héritante.
    }
}

public class ClasseHéritante : ClasseÀHériter
{
    public new string FonctionNormale()
    {
        return "FonctionNormale dans la classe dérivée";
    }
    public override string FonctionExplicitementRemplaçable()
    {
        return "FonctionExplicitementRemplaçable dans la classe dérivée";
    }
    public string AutreRésultat()
    {
        return
            this.FonctionNormale()
            + "\n"
            + this.FonctionExplicitementRemplaçable();
    }
}

public MainWindow()
{
    InitializeComponent();

    // ------------------

    ClasseÀHériter càh = new ClasseÀHériter();
    string scàh = càh.Résultat();

    ClasseHéritante ch = new ClasseHéritante();

    string schr = ch.Résultat();
    string scàhar = ch.AutreRésultat();
}

Résultats:

Classe utilisée Méthode appelée Texte renvoyé par FonctionNormale() Texte renvoyé par FonctionExplicitementRemplaçable()
ClasseÀHériter Résultat() de ClasseÀHériter FonctionNormale dans la classe de base FonctionExplicitementRemplaçable dans la classe de base
ClasseHéritante Résultat() de ClasseÀHériter FonctionNormale dans la classe de base FonctionExplicitementRemplaçable dans la classe dérivée
ClasseHéritante AutreRésultat() de ClasseHéritante FonctionNormale dans la classe dérivée FonctionExplicitementRemplaçable dans la classe dérivée

Interprétation:

  • Lorsqu’on instancie ClasseÀHériter, elle utilise ses propres fonctions.
  • Lorsqu’on hérite de ClasseÀHériter dans ClasseHéritante, la méthode FonctionNormale() appartenant à ClasseÀHériter utilise la méthode FonctionExplicitementRemplaçable() remplacée par ClasseHéritante avec override.
  • Lorsqu’on hérite de ClasseÀHériter (dans ClasseHéritante), la méthode AutreRésultat() de ClasseHéritante utilise les deux méthodes remplacées (avec new et avec override).

Aspect technique

Alors que new se contente de définir une méthode dont le nom remplace celui de la classe à hériter, override redirige les appels à la méthode remplacée même pour le code de la classe de base à hériter.
Cela implique de placer dans la partie statique de la classe à hériter, et dans toutes ses dérivées, un pointeur (un délégué, si vous voulez) qui permet au code de la classe à hériter de trouver un éventuel remplacement (par override) dans les classes héritantes.

Simulation de la virtualisation

Pour comprendre le mécanisme, voici une sorte d’écriture manuelle d’une méthode virtualisée dans une classe:

public class SimulationDeVirtualisation
{
    public delegate string ModèleDeLaMéthodeÀVirtualiser();

    public ModèleDeLaMéthodeÀVirtualiser RedirecteurVersLaMéthodeÀUtiliser; // En fait devrait être statique.

    public SimulationDeVirtualisation() // Constructeur:
    {
        RedirecteurVersLaMéthodeÀUtiliser = MéthodeDebaseRemplaçable;
    }

    private string MéthodeDebaseRemplaçable()
    {
        return "Méthode dans la classe de base";
    }

    public string DitSiMéthodeRemplacée()
    {
        return RedirecteurVersLaMéthodeÀUtiliser();
    }
}

public class ClasseHéritanteAvecMéthodeVirtualisée : SimulationDeVirtualisation
{
    public ClasseHéritanteAvecMéthodeVirtualisée() // Constructeur:
    {
        RedirecteurVersLaMéthodeÀUtiliser = MéthodeDebaseRemplaçante;
    }

    static private string MéthodeDebaseRemplaçante()
    {
        return "Méthode dans la classe dérivée";
    }
}
   
public MainWindow()
{
    InitializeComponent();

    // ------------------

    SimulationDeVirtualisation sv = new SimulationDeVirtualisation();
    string tsv = sv.DitSiMéthodeRemplacée();    // Renvoie "Méthode dans la classe de base".

    ClasseHéritanteAvecMéthodeVirtualisée chamv = new ClasseHéritanteAvecMéthodeVirtualisée();
    string tchamv = chamv.DitSiMéthodeRemplacée(); // Renvoie "Méthode dans la classe dérivée".
}

Je n’ai pas pu implémenter aussi parfaitement que je l’aurai voulu, à cause de limitations de C#, mais on voit bien que la classe de base utilise soit sa propre fonction virtuelle, soit la fonction remplaçante implémentée dans la classe dérivée.

À noter que RedirecteurVersLaMéthodeÀUtiliser devrait être statique, aussi bien dans la classe de base que dans chaque dérivation de cette classe, et que dans ce cas il est inutile de l’initialiser dans le constructeur de chaque classe.

Bien entendu, le compilateur C# s’occupe de ces détails à notre place lorsqu’on utilise les mots-clés virtual et override.
Mais il est bon de savoir ce qu’implique cette utilisation, en terme de ressources et de performances.

Conclusion(s)

  • Virtual vous est utile lorsque vous écrivez une classe à hériter (une classe de base), et que vous voulez y utiliser des informations qui n’existeront que dans les classes qui en hériteront.
  • L’utilisation de Virtual entraîne l’ajout d’un pointeur caché (au sens du C) dans la partie statique des classes à hériter et des classes héritantes, et une redirection dans le code de la classe à hériter. Soit un peu plus de complication.
    Mieux vaut donc éviter de créer des classes virtuelles et leur préférer des remplacements par new lorsque c’est possible. Surtout pour des petites classes souvent instanciées.
  •  
Poster un commentaire

Publié par le 2012-06-24 dans Programmation

 

Étiquettes : , , , , , , , , , ,