从一个方法中返回多个结果

14

我试图使用Try Catch块和更好的错误处理来提高我的技能。

我有一个执行常见任务的类,例如检索Facebook AccessToken。如果成功,我想返回AccessToken字符串,如果不成功,我想返回错误消息。这两个都是字符串,所以没有问题。但是在代码调用侧检查返回值时,如何有效地执行此操作呢?

就像我需要返回2个值一样。对于成功的尝试,返回=true,“ACESSCODEACXDJGKEIDJ”,或者如果失败,则返回=false,“糟糕,发生错误”+ex.ToString();

然后检查返回值很容易(理论上)。我可以考虑仅返回true/false以及为字符串设置Session变量。

有什么方法可以从方法中返回多个结果?

11个回答

24

创建一个Result类并返回它,而不是使用字典或元组。

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

然后你可以像这样调用这段代码...

Result result = GetFacebookToken();

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

最好使其适用于任何结果类型,而不仅仅是“AccessToken”。 - Alexander

8

我想到了两种可能性:

  1. 使用TryXXX模式(在一些BCL方法中使用,如DateTime.TryParse)。
  2. 设计一个包含操作状态和结果的类,然后让您的方法返回此类。

让我们首先看看TryXXX模式。它基本上是一个返回布尔值和结果作为out参数的方法。

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

这将会被使用成这样:
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
}

在第二种方法中,您只需设计一个包含所有必要信息的类:
public class MyResult
{
    public bool Success { get; set; }
    public string ErrorMessage { get; set; }

    public string SomeResult { get; set; }
}

然后让你的方法返回这个类:

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

这将会像这样被使用:
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
}

当然,结果可以是任何复杂对象,而不仅仅是字符串(如本例所示)。

8

继承musefan的答案,我喜欢相同的模式,但使用通用的Result类型,这样我可以在整个代码库中使用:

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

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

我喜欢这种方法而不是在函数中抛出异常,因为这可以帮助您将该函数映射到集合上,并在错误消息中捕获异常细节,因此您不必担心一个项目的异常会炸毁整个链。在解析扁平数据文件中的行之类的情况下,这是有益的,其中成功的行应向前移动,但应单独处理任何错误:

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

当然,你仍然可以选择从该函数中抛出异常,对于真正的异常情况,当炸毁整个处理链是你想要的时候。

P.S. 每天我都希望C#有多个返回值。


现在它确实具有多个返回值。您可以使用Tuple<T1,T2>或Tuple<T1,T2,T3>等。请参见https://msdn.microsoft.com/en-us/library/dd268536(v=vs.110).aspx - stefann
好的,直接作为语言结构返回多个值,就像Lua或Go一样。 (抱歉,不是要改变目标。)类似于:public Thing,string ParseThing(...){...}var thing,err = ParseThing(...);虽然我还没有仔细考虑过,但我相信肯定有很好的理由说明它不适合该语言。 - user1454265
我知道你的意思并且同意。这可能是在元组结构之上的语法糖。你应该通过Visual Studio提交一个建议。 - stefann

6
尝试使用元组?
public Tuple<bool, string> ReturnsBoolAndString() {
    return Tuple.Create(false, "string");
}

3

一种比较好的方法是返回一个对象,其中包含成功/失败状态和详细的错误信息。

类似于:

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

2
一个更通用的实现将是
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

通过这种方法,可以在catch块中传递ex.Message,在try块中传递Data。

2
如果成功,我想返回 AccessToken 字符串,如果不成功,我想返回一个错误消息。这两个都是字符串,所以没有问题。但是在代码调用方检查返回值时,如何有效地执行此操作?
C# 实际上并不使用错误消息,而是使用异常。正确的方法是抛出异常,让调用者忽略或捕获它。
如果失败不是“异常”(例如,有些用户有令牌,有些没有),那么另一种选择就是返回空字符串来表示缺少令牌(并且仍然为“异常”情况(例如无法联系Facebook等)抛出异常)。我认为这对您来说不是这种情况,因为您的示例故障包括一个Exception对象。
总之,通常将异常处理(catch)留给堆栈顶部(通常是UI),因为它具有当前操作的最多上下文。捕获异常,重新格式化为字符串,然后返回它而不是有用的异常信息是没有用的。只需让调用者拥有异常,他们可以决定如何向用户呈现该故障(或在没有FB集成的情况下继续进行)。
显然,这是虚构的,但希望能够传达我的观点(代码比语言更有说服力)。
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();
      }
   }
}

1

你的观点是正确的,使用外部存储位置(例如会话变量)是错误的方法。

正确的方法取决于您是否认为错误是一种特殊情况。如果不是,那么请遵循框架中设置的示例,使用单词Try作为函数前缀,并使其签名如下:

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
}

这里需要注意的重要一点是,当你只关心操作是否成功(如果失败了,你并不在意失败原因)并且有一个合理的预期它可能失败时,通常会采用这种方法。

如果失败是“异常的”(意思是一个正确配置的程序不应该遇到这个错误),那么就应该使用异常。实际上,如果你的函数不能真正处理你得到的异常,那么捕获它其实是没有意义的。适当的异常处理意味着让异常“冒泡”到你的程序中可以真正做出有意义和适当响应的层中。

这也简化了你的情境,因为你只需要返回一个字符串。


1

我不会返回错误信息。返回一个有意义的值或者报错并让它冒泡上来。如何处理错误由你自己决定,但至少我会在前端优雅地处理它,并在后端记录/通知相关人员。

如果你坚持在函数出错时返回一些东西,那么我会返回一个具有以下成员的对象:

Value - String
Success - Bool

然后您可以检查成功并相应地处理该值。

0
为什么不创建一个具有3个属性的类。success(布尔值),message(字符串)和token(字符串)。您可以创建该类的实例,填充值并返回它。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接