Question

J'ai une classe "Status" en C#, utilisée comme ceci :

Status MyFunction()
{
   if(...) // something bad
     return new Status(false, "Something went wrong")
   else
     return new Status(true, "OK");
}

Vous avez eu l'idée.Tous les appelants de MyFunction devrait vérifiez le statut renvoyé :

Status myStatus = MyFunction();
if ( ! myStatus.IsOK() )
   // handle it, show a message,...

Les appelants paresseux peuvent cependant ignorer le statut.

MyFunction(); // call function and ignore returned Status

ou

{
  Status myStatus = MyFunction(); 
} // lose all references to myStatus, without calling IsOK() on it

Est-il possible de rendre cela impossible ?par exemple.une exception de lancement

En général:est-il possible d'écrire une classe C# sur laquelle vous avoir appeler une certaine fonction?

Dans la version C++ de la classe Status, je peux écrire un test sur un bool privé bIsChecked dans le destructeur et sonner quelques cloches quand quelqu'un ne vérifie pas cette instance.

Quelle est l’option équivalente en C# ?J'ai lu quelque part que "Vous ne voulez pas de destructeur dans votre classe C#"

La méthode Dispose de l’interface IDisposable est-elle une option ?

Dans ce cas, il n’y a aucune ressource non gérée à libérer.De plus, il n’est pas déterminé quand le GC disposera de l’objet.Lorsqu'il sera finalement supprimé, est-il toujours possible de savoir où et quand vous avez ignoré cette instance de Status spécifique ?Le mot-clé "using" aide, mais encore une fois, ce n'est pas le cas. requis pour les appelants paresseux.

Était-ce utile?

La solution

Je suis presque certain que vous ne pouvez pas obtenir l’effet souhaité en tant que valeur de retour d’une méthode.C# ne peut tout simplement pas faire certaines des choses que C++ peut faire.Cependant, une façon quelque peu moche d’obtenir un effet similaire est la suivante :

using System;

public class Example
{
    public class Toy
    {
        private bool inCupboard = false;
        public void Play() { Console.WriteLine("Playing."); }
        public void PutAway() { inCupboard = true; }
        public bool IsInCupboard { get { return inCupboard; } }
    }

    public delegate void ToyUseCallback(Toy toy);

    public class Parent
    {
        public static void RequestToy(ToyUseCallback callback)
        {
            Toy toy = new Toy();
            callback(toy);
            if (!toy.IsInCupboard)
            {
                throw new Exception("You didn't put your toy in the cupboard!");
            }
        }
    }

    public class Child
    {
        public static void Play()
        {
            Parent.RequestToy(delegate(Toy toy)
            {
                toy.Play();
                // Oops! Forgot to put the toy away!
            });
        }
    }

    public static void Main()
    {
        Child.Play();
        Console.ReadLine();
    }
}

Dans l'exemple très simple, vous obtenez une instance de Toy en appelant Parent.RequestToy, et je lui passe un délégué.Au lieu de renvoyer le jouet, la méthode appelle immédiatement le délégué avec le jouet, qui doit appeler PutAway avant son retour, sinon la méthode RequestToy lèvera une exception.Je ne prétends pas à la sagesse de l'utilisation de cette technique - en effet, dans tous les exemples de "quelque chose s'est mal passé", une exception est presque certainement un meilleur pari - mais je pense que cela se rapproche le plus possible de votre demande initiale.

Autres conseils

Je sais que cela ne répond pas directement à votre question, mais si "quelque chose s'est mal passé" dans votre fonction (circonstances inattendues), je pense que vous devriez lancer une exception plutôt que d'utiliser des codes de retour d'état.

Laissez ensuite à l'appelant le soin d'intercepter et de gérer cette exception s'il le peut, ou de lui permettre de se propager s'il est incapable de gérer la situation.

L'exception levée peut être d'un type personnalisé si cela est approprié.

Pour attendu résultats alternatifs, je suis d'accord avec la suggestion de @Jon Limjap.J'aime les types de retour booléens et le préfixe du nom de la méthode avec "Try", à la :

bool TryMyFunction(out Status status)
{
}

Si vous souhaitez vraiment demander à l'utilisateur de récupérer le résultat de MyFunction, vous souhaiterez peut-être l'annuler à la place et utiliser une variable out ou ref, par exemple :

void MyFunction(out Status status)
{
}

Cela peut paraître moche, mais au moins cela garantit qu'une variable est transmise à la fonction qui récupérera le résultat dont vous avez besoin.

@Ian,

Le problème avec les exceptions est que si cela se produit un peu trop souvent, vous risquez de dépenser trop de ressources système pour l'exception.Une exception devrait vraiment être utilisée pour exceptionnel des erreurs, des messages pas totalement attendus.

Même System.Net.WebRequest lève une exception lorsque le code d'état HTTP renvoyé est un code d'erreur.La façon typique de le gérer est d’enrouler un try/catch autour de lui.Vous pouvez toujours ignorer le code d'état dans le bloc catch.

Vous pouvez cependant avoir un paramètre Action< Status> afin que l'appelant soit obligé de transmettre une fonction de rappel qui accepte un statut, puis de vérifier s'il l'a appelé.

void MyFunction(Action<Status> callback)
 { bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");
   callback(status)

   if (!status.isOkWasCalled) 
     throw new Exception("Please call IsOK() on Status"). 
 }

MyFunction(status => if (!status.IsOK()) onerror());

Si vous craignez qu'ils appellent IsOK() sans rien faire, utilisez plutôt Expression< Func< Status,bool>> et vous pourrez ensuite analyser le lambda pour voir ce qu'ils font avec le statut :

void MyFunction(Expression<Func<Status,bool>> callback)
 { if (!visitCallbackExpressionTreeAndCheckForIsOKHandlingPattern(callback))
     throw new Exception
                ("Please handle any error statuses in your callback");


   bool errorHappened = false;

   if (somethingBadHappend) errorHappened = true;

   Status status = (errorHappend)
                     ? new Status(false, "Something went wrong")
                     : new Status(true, "OK");

   callback.Compile()(status);
 }

MyFunction(status => status.IsOK() ? true : onerror());

Ou renoncez complètement à la classe de statut et faites-leur passer un délégué pour le succès et un autre pour une erreur :

void MyFunction(Action success, Action error)
 { if (somethingBadHappened) error(); else success();
 }

MyFunction(()=>;,()=>handleError()); 

Utiliser Status comme valeur de retour me rappelle le "vieux temps" de la programmation C, lorsque vous renvoyiez un entier inférieur à 0 si quelque chose ne fonctionnait pas.

Ne serait-il pas préférable de lancer une exception lorsque (comme vous le dites) quelque chose s'est mal passé?Si un "code paresseux" ne détecte pas votre exception, vous le saurez avec certitude.

Au lieu de forcer quelqu'un à vérifier l'état, je pense que vous devriez supposer que le programmeur est conscient des risques de ne pas le faire et a une raison de prendre cette mesure.Vous ne savez pas comment la fonction sera utilisée à l'avenir et imposer une telle limitation ne fait que restreindre les possibilités.

Ce serait certainement bien que le compilateur vérifie cela plutôt que via une expression.:/ Je ne vois pas comment faire ça cependant...

Vous pouvez lever une exception en :

throw MyException;


[global::System.Serializable]
        public class MyException : Exception
        {
        //
        // For guidelines regarding the creation of new exception types, see
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
        // and
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
        //

        public MyException () { }
        public MyException ( string message ) : base( message ) { }
        public MyException ( string message, Exception inner ) : base( message, inner ) { }
        protected MyException (
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context )
            : base( info, context ) { }
    }

L'exception ci-dessus est entièrement personnalisable selon vos besoins.

Une chose que je dirais est la suivante, je laisserais à l'appelant le soin de vérifier le code de retour, il est de sa responsabilité de fournir simplement les moyens et l'interface.En outre, il est beaucoup plus efficace d'utiliser des codes de retour et de vérifier l'état avec une instruction if plutôt que de lancer des exceptions.Si c'est vraiment un Exceptionnel circonstance, alors jetez-le par tous les moyens...mais disons que si vous ne parvenez pas à ouvrir un appareil, il serait peut-être plus prudent de s'en tenir au code retour.

@Paul, vous pouvez le faire au moment de la compilation avec C# extensible.

GCC a un warn_unused_result attribut idéal pour ce genre de chose.Peut-être que les compilateurs Microsoft ont quelque chose de similaire.

Un modèle qui peut parfois être utile si l'objet auquel le code émet des requêtes ne sera utilisé que par un seul thread (*) consiste à laisser l'objet conserver un état d'erreur et à dire que si une opération échoue, l'objet sera inutilisable jusqu'à ce que le l'état d'erreur est réinitialisé (les requêtes futures devraient échouer immédiatement, de préférence en lançant une exception immédiate qui inclut des informations sur l'échec précédent et la nouvelle requête).Dans les cas où le code appelant anticipe un problème, cela peut permettre au code appelant de gérer le problème plus proprement que si une exception était levée ;les problèmes qui ne sont pas ignorés par le code appelant finiront généralement par déclencher une exception peu de temps après leur apparition.

(*) Si une ressource est accessible par plusieurs threads, créez un objet wrapper pour chaque thread et faites passer les requêtes de chaque thread via son propre wrapper.

Ce modèle est utilisable même dans des contextes où il n'y a pas d'exceptions, et peut parfois s'avérer très pratique dans de tels cas.En général, cependant, une certaine variation du modèle essayer/faire est généralement préférable.Demandez aux méthodes de lever une exception en cas d'échec, sauf si l'appelant l'indique explicitement (en utilisant un TryXX méthode) que des échecs sont attendus.Si les appelants disent que des échecs sont attendus mais ne les gèrent pas, c'est leur problème.On pourrait combiner le try/do avec une deuxième couche de protection en utilisant le schéma ci-dessus, mais je ne suis pas sûr que cela en vaille la peine.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top