简体   繁体   中英

Returning multiple results from a method

I trying to improve my skills using Try Catch blocks and better error handling.

I have a class that performs a common task, in this case retrieving a Facebook AccessToken. If successful, I want to return the AccessToken string, if not I want to return an error message. These are both strings, so no problem. But when checking the return value on the calling side of the code, how can you do this effectively?

It's like I need to return 2 values. In the case of a successful attempt, return = true, "ACESSCODEACXDJGKEIDJ", or if it fails, return = false, "Ooops, there was an error" + ex.ToString();

Then checking the return value is easy (in theory). I could think of returning simply a true/false for return and then setting a Session variable for the strings.

What is a way to return multiple results from a method?

Create a Result class and return that instead...

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

Then you can call this code like...

Result result = GetFacebookToken();

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

2 possibilities spring to mind

  1. Use the TryXXX pattern (used in some BCL methods such as DateTime.TryParse ).
  2. Design a class that contains the status of the operation and the result and then have your method return this class.

Let's first see the TryXXX pattern. It's basically a method that returns a boolean value and the result as out parameter.

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

which will be consumed like this:

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
}

In the second approach you would simply design a class that will contain all the necessary information:

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

    public string SomeResult { get; set; }
}

and then have your method return this class:

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

which will be consumed like this:

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
}

Of course the results can be any other complex object instead of (as shown in this example) a string.

To build on musefan's answer, I like the same pattern but with a generic Result type so I can use it throughout the whole codebase:

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

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

One reason I like this vs. throwing an exception out of a function that otherwise returns data, is that this helps you map that function over a collection, capturing exception details in error messages, so you don't have to worry about an exception on one item blowing up the whole chain. This is good for situations like parsing lines out of a flat data file, where successful lines should move forward but any errors should be dealt with individually:

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

Of course, you still have the option of throwing an exception out of that function for truly exceptional situations when blowing up the whole chain of processing is what you want.

PS Every day is the day I wish C# had multiple return values.

Try a tuple?

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

A pretty way to do this is return an object that includes both a Success/Failure status and a detailed error message.

Something like:

class Result
{
   bool IsSuccessful { get; set; }
   string DetailedStatus { get; set; }
}

A more generic implementation will be

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

By this method one can pass the ex.Message in the catch block and Data<T> in the try block

If successful, I want to return the AccessToken string, if not I want to return an error message. These are both strings, so no problem. But when checking the return value on the calling side of the code, how can you do this effectively?

C# doesn't really use error messages, we use exceptions . The correct way to do this is to just throw an exception, and let the caller ignore or catch it.

If it's not "exceptional" to fail (eg., if some users have tokens and some don't), then an alternative would be to return a null string to indicate the absence of a token (and still throw an exception for "exceptional" cases such as not being able to contact Facebook, etc.). I don't think that's the case for you, since your example failure included an Exception object.

Bottom line, is that you generally leave exception handling (catch) to the top-most of the stack (usually, the UI) since that has the most context of the current operation. It's of no use to catch an exception, reformat to a string, and then return that instead - losing valuable exception info along the way. Just let the caller have the exception instead, and they can decide how to present that failure to the user (or to carry on without FB integration).

This is obviously mocked up, but hopefully gets my point across (code speaks louder than words):

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

I wouldn't return the error message. Return a meaningful value or error out and let it bubble up. How you handle the error is up to you, but at a minimum I would gracefully handle it on the front end and log/notify someone on the backend.

If you insist on returning something even when your function errors out then I would return an object that has the following members:

Value - String
Success - Bool

Then you can check for success and handle the value accordingly.

You are definitely correct that using an outside storage location (a session variable, for example) is the wrong way.

The correct approach depends on whether or not you consider an error to be an exceptional circumstance. If not, then follow the example set in the framework by prefixing your function with the word Try and having its signature look like this:

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
}

The important thing to note here is that this approach is generally used when you only care whether or not the operation worked (and you don't really care why it didn't work if it failed) and have a reasonable expectation that it may not.

If a failure is exceptional (meaning that a properly configured program should not encounter this error), then you should use an exception. In fact, if your function cannot actually do anything with the exception you're getting, then there's no point in actually catching it. Proper exception handling means letting the exception "bubble up" to whatever layer in your program can actually do something meaningful and appropriate with the exception.

This also simplifies your scenario, since you only need to return a string.

Why don't you create a class with 3 properties. success (bool), message (string) and token (string). You can create an instance of that class, populate the values and return that.

If you want to return 2 objects, you can do something like this:

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

        errorMessage = "";
        return true;
    }

then you get the bool and the error message

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM