web-dev-qa-db-fra.com

Renvoyer plusieurs résultats d'une méthode

J'essaie d'améliorer mes compétences en utilisant les blocs Try Catch et une meilleure gestion des erreurs.

J'ai une classe qui effectue une tâche commune, dans ce cas, la récupération d'un Facebook AccessToken. En cas de succès, je veux renvoyer la chaîne AccessToken, sinon je veux renvoyer un message d'erreur. Ce sont les deux chaînes, donc pas de problème. Mais lorsque vous vérifiez la valeur de retour du côté appelant du code, comment pouvez-vous le faire efficacement? 

C'est comme si j'avais besoin de renvoyer 2 valeurs. Dans le cas d'une tentative réussie, renvoyer = true, "ACESSCODEACXDJGKEIDJ" ou, en cas d'échec, renvoyer = false, "Oups, une erreur s'est produite" + ex.ToString ();

Ensuite, vérifier la valeur de retour est facile (en théorie). Je pourrais penser à renvoyer simplement un vrai/faux pour le retour et en définissant ensuite une variable de session pour les chaînes.

Quel est le moyen de renvoyer plusieurs résultats d'une méthode?

13
Chad Richardson

Créez une classe de résultat et retournez-la à la place ...

public class Result
{
   public bool Success {get;set;}
   public string AccessToken {get;set;}
   public string ErrorMessage {get;set;}
}


public Result GetFacebookToken()
{
   Result result = new Result();

   try{
      result.AccessToken = "FACEBOOK TOKEN";
      result.Success = true;
   }
   catch(Exception ex){
      result.ErrorMessage = ex.Message;
      result.Success = false;
   }

   return result;
}

Ensuite, vous pouvez appeler ce code comme ...

Result result = GetFacebookToken();

if(result.Success)
{
   //do something with result.AccessToken
}
else
{
   //do something with result.ErrorMessage 
}
22
musefan

2 possibilités me viennent à l'esprit 

  1. Utilisez le modèle TryXXX (utilisé dans certaines méthodes BCL telles que DateTime.TryParse ).
  2. Concevez une classe contenant le statut de l'opération et le résultat, puis demandez à votre méthode de renvoyer cette classe.

Voyons d'abord le motif TryXXX. C'est en gros une méthode qui renvoie une valeur booléenne et le résultat sous la forme du paramètre out.

public bool TryXXX(string someInput, out string someResult, out string errorMessage)
{
    ...
}

qui sera consommé comme ceci:

string someResult;
string errorMessage;
if (!TryXXX("some parameter", out someResult, out errorMessage))
{
    // an error occurred => use errorMessage to get more details
}
else
{
    // everything went fine => use the results here
}

Dans la seconde approche, vous concevriez simplement une classe qui contiendrait toutes les informations nécessaires:

public class MyResult
{
    public bool Success { get; set; }
    public string ErrorMessage { get; set; }

    public string SomeResult { get; set; }
}

et que votre méthode retourne cette classe:

public MyResult MyMethod(string someParameter)
{
    ...
}

qui sera consommé comme ceci:

MyResult result = MyMethod("someParameter");
if (!result.Success)
{
    // an error occurred => use result.ErrorMessage to get more details
}
else
{
    // everything went fine => use the result.SomeResult here
}

Bien entendu, les résultats peuvent être n’importe quel autre objet complexe au lieu d’une chaîne (comme illustré dans cet exemple).

8
Darin Dimitrov

Pour compléter la réponse de musefan, j'aime bien le même motif, mais avec un type de résultat générique, de sorte que je puisse l'utiliser dans l'ensemble du code:

public class Result
{
    public bool Success { get; set; }
    public string ErrorMessage { get; set; }
}

public class Result<T> : Result
{
    public T Data;
}

Une des raisons pour lesquelles j'aime ceci et le fait de lever une exception sur une fonction qui renvoie des données, c'est que cela vous permet de mapper cette fonction sur une collection, en capturant les détails de l'exception dans les messages d'erreur, de sorte que vous n'avez pas à vous soucier d'une exception le un élément explose toute la chaîne. C'est utile dans des situations telles que l'analyse de lignes extraites d'un fichier de données plat, où les lignes réussies doivent avancer, mais les erreurs doivent être traitées individuellement:

public static Result<Thing> ParseThing(string line)
{
     try 
     {
          // Parse a Thing (or return a parsing error.)
          return new Result<Thing> { Data = thing, Success = true };
     }
     catch (Exception ex)
     {
          return new Result<Thing> { Data = null, Success = false, ErrorMessage = "..." };
     }
}

...

var results = lines.Select(ParseThing);

foreach (var result in results)
{
    // Check result.Success and deal with successes/failures here.
}

Bien sûr, vous avez toujours la possibilité de supprimer une exception de cette fonction pour des situations vraiment exceptionnelles, lorsque vous souhaitez faire exploser toute la chaîne de traitement.

P.S. Chaque jour est le jour où je souhaiterais que C # ait plusieurs valeurs de retour.

6
user1454265

Essayer un tuple?

public Tuple<bool, string> ReturnsBoolAndString() {
    return Tuple.Create(false, "string");
}
5
epylar

Pour ce faire, vous pouvez retourner un objet contenant à la fois un statut Succès/Échec et un message d'erreur détaillé.

Quelque chose comme:

class Result
{
   bool IsSuccessful { get; set; }
   string DetailedStatus { get; set; }
}
3
Ilya Kogan

Une implémentation plus générique sera

C #

public class ReturnMessage<T>
{
    //indicates success or failure of the function
    public bool IsSuccess { get; set; }
    //messages(if any)
    public string Message { get; set; }
    //data (if any)
    public T Data { get; set; }
}

VB.NET

Public Class ReturnMessage(Of T)
    'indicates success or failure of the function
    Public Property IsSuccess As Boolean
    'messages(if any)
    Public Property Message As String
    'data (if any)
    Public Property Data As T
End Class

Par cette méthode, on peut passer le ex.Message dans le bloc catch et le Data<T> dans le bloc try

2
naveen

En cas de succès, je veux renvoyer la chaîne AccessToken, sinon je veux renvoyer un message d'erreur. Ce sont les deux chaînes, donc pas de problème. Mais lorsque vous vérifiez la valeur de retour du côté appelant du code, comment pouvez-vous le faire efficacement?

C # n'utilise pas vraiment les messages d'erreur, nous utilisons exceptions . La bonne façon de faire est de simplement lever une exception et de laisser l’appelant l’ignorer ou l’attraper. 

Si ce n’est pas «exceptionnel» d’échouer (par exemple, si certains utilisateurs ont des jetons et d’autres pas), une autre solution consisterait à renvoyer une chaîne nulle pour indiquer l’absence de jeton (tout en lançant une exception pour "exceptionnel"). "cas tels que l'impossibilité de contacter Facebook, etc.). Je ne pense pas que ce soit le cas pour vous, car votre exemple d'échec incluait un objet Exception.

En bout de ligne, vous laissez généralement le traitement des exceptions (catch) dans la partie supérieure de la pile (généralement l'interface utilisateur), car il contient le plus de contexte de l'opération en cours. Il ne sert à rien d'attraper une exception, de la reformater en chaîne, puis de la renvoyer à la place - en perdant des informations précieuses sur les exceptions en cours de route. Laissez simplement l'appelant avoir l'exception à la place, et il pourra décider de la présentation de cet échec à l'utilisateur (ou de poursuivre sans intégration du FB).

Ceci est évidemment moqué, mais j'espère que je fais comprendre (le code parle plus fort que les mots):

class Facebook {
   ...
   public string GetAccessToken(string username, string password) {
      // can throw WebException if can't connect to FB
      this.Connect(); 

      // returns null token if not a Facebook user
      if (!this.IsUser(username)) return null;

      // can throw ArgumentException if password is wrong
      var fbInfo = this.GetInfo(username, password);

      return fbInfo.AccessToken;
   }
   ...
}

class Page {
   void Page_Load(object sender, EventArgs e) {
      var fb = new Facebook();

      string accessToken;
      try {
         accessToken = fb.GetAccessToken(this.User.Name, this.txtPassword.Text);
      } catch (WebException ex) {
         Log(ex);
         this.divError.Text = "Sorry, Facebook is down";
         // continue processing without Facebook
      } catch (ArgumentException ex) {
         // Don't log - we don't care
         this.divError.Text = "Your password is invalid";
         // stop processing, let the user correct password
         return;
      } catch (Exception ex) {
         Log(ex);
         // Unknown error. Stop processing and show friendly message
         throw;
      }

      if (!string.IsNullOrEmpty(accessToken)) {
         // enable Facebook integration 
         this.FillFacebookWallPosts(accessToken);
      } else {
         // disable Facebook integration
         this.HideFacebook();
      }
   }
}
2
Mark Brackett

Je ne retournerais pas le message d'erreur. Renvoie une valeur significative ou une erreur et laissez-la bouillonner. C'est vous qui décidez de la façon dont vous gérez l'erreur, mais au minimum, je la traiterais gracieusement sur le serveur frontal et consignerais/notifierais quelqu'un sur le serveur.

Si vous insistez pour retourner quelque chose même si votre fonction est erronée, je renverrais un objet ayant les membres suivants:

Value - String
Success - Bool

Ensuite, vous pouvez vérifier le succès et gérer la valeur en conséquence.

1
Abe Miessler

Vous avez certainement raison de dire qu’utiliser un emplacement de stockage externe (une variable de session, par exemple) est le chemin faux .

La bonne approche dépend de si vous considérez ou non une erreur comme une circonstance exceptionnelle . Sinon, suivez l'exemple du cadre en préfixant votre fonction avec le mot Try et en donnant à sa signature l'aspect suivant:

public bool TryGetFacebookToken(<necessary parameters>, out string token)
{
    ... set the token within the body and return true if it succeeded or false if it did not
}

La chose importante à noter ici est que cette approche est généralement utilisée lorsque vous vous souciez seulement de savoir si l'opération a fonctionné (et vous ne vous souciez pas vraiment pourquoi cela n'a pas fonctionné si elle a échoué) et que vous avez une attente raisonnable. que ce ne peut pas.

Si un échec est exceptionnel (ce qui signifie qu'un programme correctement configuré ne devrait pas rencontre cette erreur), vous devriez alors utiliser une exception. En fait, si votre fonction ne peut réellement faire rien à l'exception de ce que vous obtenez, il est alors inutile de l'attraper. Une gestion correcte des exceptions implique de laisser l'exception "bouger" vers n'importe quelle couche de votre programme, ce qui peut réellement faire quelque chose de significatif et approprié avec l'exception.

Cela simplifie également votre scénario car il vous suffit de renvoyer une chaîne.

1
Adam Robinson

Pourquoi ne créez-vous pas une classe avec 3 propriétés? succès (bool), message (chaîne) et jeton (chaîne). Vous pouvez créer une instance de cette classe, renseigner les valeurs et la renvoyer.

0
Magrangs

Si vous voulez retourner 2 objets, vous pouvez faire quelque chose comme ceci:

    private bool TestThing(out string errorMessage)
    {
        bool error = true;
        if(error)
        {
            errorMessage = "This is a message!";
            return false;
        }

        errorMessage = "";
        return true;
    }

alors vous obtenez le bool et le message d'erreur

0
joe_coolish