Imposer des appels de fonction obligatoires en C#

Le problème : Appels de fonction non vérifiés

En C#, il est assez courant de créer des fonctions qui retournent un statut, aidant les développeurs à surveiller le succès ou l’échec des opérations. Cependant, un piège courant est que certains développeurs peuvent entièrement ignorer ces statuts retournés. Cela peut entraîner des conséquences indésirables si une gestion d’erreur appropriée n’est pas exécutée.

Par exemple, considérons une classe Status, qui pourrait contenir des informations sur la réussite d’une opération ou s’il y a eu des problèmes lors de l’exécution :

Status MyFunction()
{
   if(...) // quelque chose de mauvais
     return new Status(false, "Une erreur est survenue");
   else
     return new Status(true, "OK");
}

L’attente est que tous les appelants de MyFunction vérifient le statut retourné :

Status myStatus = MyFunction();
if (!myStatus.IsOK())
   // gérer cela, montrer un message,...

Néanmoins, certains appelants pourraient paresseusement ignorer la vérification de statut :

// Ignorer le statut retourné
MyFunction(); // appeler la fonction et ignorer le Status retourné

Peut-on prévenir cela ?

Cela soulève une question importante : Comment pouvons-nous nous assurer que chaque appelant vérifie le statut retourné ? En C++, on pourrait utiliser un destructeur pour vérifier le statut à la fin du cycle de vie de l’objet. Cependant, en C#, les destructeurs sont généralement mal vus en raison de la nature non déterministe du ramasse-miettes.

La solution : Utiliser des délégués pour les appels de fonction obligatoires

Bien que C# manque de la capacité d’imposer qu’une valeur de retour de méthode soit appelée ou vérifiée, un moyen de contournement innovant implique l’utilisation de délégués. Cette technique ne se contente pas de retourner une valeur ; au lieu de cela, elle exige que l’appelant traite explicitement le statut retourné via un mécanisme de rappel.

Exemple d’implémentation

Regardons une implémentation simple utilisant des délégués :

using System;

public class Exemple
{
    public class Jouet
    {
        private bool dansPlacard = false;
        public void Jouer() { Console.WriteLine("Jouer."); }
        public void Ranger() { dansPlacard = true; }
        public bool EstDansPlacard { get { return dansPlacard; } }
    }

    public delegate void RappelJouetUtilisation(Jouet jouet);

    public class Parent
    {
        public static void DemanderJouet(RappelJouetUtilisation callback)
        {
            Jouet jouet = new Jouet();
            callback(jouet);
            if (!jouet.EstDansPlacard)
            {
                throw new Exception("Vous n'avez pas mis votre jouet dans le placard !");
            }
        }
    }

    public class Enfant
    {
        public static void Jouer()
        {
            Parent.DemanderJouet(delegate(Jouet jouet)
            {
                jouet.Jouer();
                // Oups ! J'ai oublié de ranger le jouet !
            });
        }
    }

    public static void Main()
    {
        Enfant.Jouer();
        Console.ReadLine();
    }
}

Explication du code

Dans cet exemple, lorsque Parent.DemanderJouet est appelé, il attend une fonction de rappel qui effectuera des opérations avec l’objet Jouet. Après l’exécution du rappel, il vérifie si le jouet a été rangé :

  • Si le rappel n’appelle pas jouet.Ranger(), une exception est levée. Cela garantit que l’appelant doit s’occuper du Jouet à la fin de son utilisation, imposant efficacement que les opérations de suivi nécessaires se produisent.
  • Cette méthode ne garantit pas que l’appelant se souviendra de vérifier le statut, mais elle impose une structure qui élève l’importance de l’opération.

Conclusion

Bien qu’il puisse ne pas être possible en C# d’imposer des vérifications sur les valeurs retournées aussi rigoureusement qu’en C++, nous pouvons tirer parti des délégués pour créer un modèle qui communique efficacement la nécessité d’une action supplémentaire, renforçant ainsi la fiabilité de notre code. En utilisant ce modèle, nous nous assurons que les développeurs utilisant votre code ne peuvent pas facilement contourner des vérifications essentielles. Bien que cela puisse sembler un peu inhabituel, cela constitue un moyen utile de créer une culture de responsabilité dans la gestion des erreurs.