简体   繁体   English

从引发的异常中恢复或返回

[英]Recover or return from a thrown exception

For many years my main language was Perl, and I regularly validated user input without a problem. 多年以来,我的主要语言是Perl,而且我会定期验证用户输入的内容而不会出现问题。 Now I'm using a lot of C# and want to migrate toward the throw/catch style of validating user input and recovering/returning from thrown exceptions. 现在,我正在使用大量的C#,并希望朝着验证用户输入并从引发的异常中恢复/返回的引发/捕获样式迁移。 I'm using a very naive (ie, stupid) method of doing this, and feel an urgent need to move to something a little more mature and less stupid. 我正在使用一种非常幼稚(即愚蠢)的方法来执行此操作,并且迫切需要转移到更成熟,更不愚蠢的东西上。 I have copied a function that returns an integer from a prompt. 我复制了一个从提示返回整数的函数。 I'm recovering from user errors by using the dreaded GOTO statement. 我正在使用可怕的GOTO语句从用户错误中恢复。 What is the better way to do this? 什么是更好的方法呢?

Thanks, CC. 谢谢,CC。

private static int GetInput(string v)
{
    begin:
    Console.Write(v);
    string strradius = Console.ReadLine();
    int intradius;
    try
    {
        intradius = int.Parse(strradius);
        if (intradius < 1)
            throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentNullException)
    {
        Console.WriteLine("You must enter a value.");
        goto begin;
    }
    catch (FormatException)
    {
        Console.WriteLine("You must enter a valid number.");
        goto begin;
    }
    catch (ArgumentOutOfRangeException)
    {
        Console.WriteLine("Your number is out of range");
        goto begin;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
        goto begin;
    }
    finally
    {
        Console.WriteLine("Okay");
    }
    return intradius;
} 

First, a good rule of thumb as to when to use goto is never. 首先,永远不会有何时使用goto的良好经验法则。 Really, other than for a handful of very rare exceptional circumstances, you'd never want to use it. 确实,除了少数极少数特殊情况外,您永远都不想使用它。

Next, to your question, using exceptions to validate input is a bad idea in general. 接下来,对于您的问题,使用异常来验证输入通常是一个坏主意。 As most people pointed out it's expensive. 正如大多数人指出的那样,这很昂贵。 Exceptions should be used to handle exceptional circumstances, so I would in fact not use them at all. 应该使用异常来处理特殊情况,因此我实际上根本不会使用它们。

Instead, you can use a do-while loop, and repeat as long as the user inputs an incorrect input. 相反,您可以使用do-while循环,并在用户输入不正确的输入时重复执行。 You exit the loop once you get a proper input. 一旦获得正确的输入,便退出循环。 If in case an exception occurs, you shouldn't really continue the process. 如果发生异常,则不应真正继续该过程。 Either handle it outside (ie, no try-catch inside your method) or else if you must do a try-catch then simply print a message and exit the method. 要么在外部处理它(即,方法内部没有try-catch ),否则,如果您必须进行try-catch则只需打印一条消息并退出该方法。 But I would not use exception handling for a method like this. 但是我不会对这种方法使用异常处理。 Also it's a good idea to actually change the return type to bool, so you indicate to the outside world whether the method succeeded or not by the return type. 实际将返回类型更改为bool也是一个好主意,因此您可以通过返回类型向外界指示该方法是否成功。 You an use an out parameter to actually return the converted int . 您使用out参数实际返回转换后的int

private static bool GetInput(string msg, out int converted)
{
    bool result = false;
    converted = 0;
    do
    {
        Console.Write(msg);
        string str = Console.ReadLine();
        result = int.TryParse(str, out converted);
        if (result && converted < 1)
        {
            Console.WriteLine("Your number is out of range");
            result = false;
        }
        if (!result && string.IsNullOrEmpty(str))
        {
            Console.WriteLine("You must enter a value.");
        }
        if (!result && !string.IsNullOrEmpty(str))
        {
            Console.WriteLine("You must enter a valid number.");
        }
    } while (!result);

    return result;
}

Using goto statements in C# code is highly frowned-upon because it makes code difficult to read, debug, and maintain (for more info, read this ). 在C#代码中使用goto语句是高度皱起了眉头,在因为它使代码难以阅读,调试和维护(有关更多信息,请阅读 )。 Loops, if/then statements, or method calls can be used instead of goto statements. 可以使用循环,if / then语句或方法调用代替goto语句。 Also, try \\ catch blocks should be used sparingly, to catch exceptions that you are unable to handle. 另外,应谨慎使用try \\ catch块,以捕获无法处理的异常。

In your case, we can use a while loop to continue to loop until a valid number is entered, and we can use the int.TryParse method to attempt to parse the string and get an integer result. 在您的情况下,我们可以使用while循环继续循环直到输入有效数字为止,并且可以使用int.TryParse方法尝试解析字符串并获取整数结果。 This method returns a Boolean that indicates success, and takes an out parameter that will be set to the integer result. 此方法返回一个指示成功的Boolean ,并采用将设置为整数结果的out参数。

My suggestion for your method would be to have it take in a string that will be used as a prompt for the user (asking them to enter a number), and return the integer result of their input. 我对您的方法的建议是让它接受一个字符串,该字符串将用作提示用户(要求他们输入数字),并返回其输入的整数结果。

For example: 例如:

private static int GetIntFromUser(string prompt, int minValue = int.MinValue, 
    int maxValue = int.MaxValue)
{           
    int result;
    string errorMsg = $"ERROR: Input must be a valid number from {minValue} to {maxValue}";

    while(true)
    {
        Console.Write(prompt);
        string input = Console.ReadLine();

        if (!int.TryParse(input, out result) || result < minValue || result > maxValue)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(errorMsg);
            Console.ResetColor();
        }
        else
        {
            break;
        }
    }

    return result;
}

In practice we can now call this method to get numbers from the user and we'll know they are valid without having to do any additional validation: 实际上,我们现在可以调用此方法从用户那里获取数字,我们将知道它们是有效的,而无需进行任何其他验证:

private static void Main()
{
    // Age must be between 5 and 100
    var age = GetIntFromUser("Please enter your age: ", 5, 100);

    // Weight must be a positive number (minimum of zero)
    var weight = GetIntFromUser("Please enter your weight: ", 0);

    // No restrictions on favorite number
    var favNum = GetIntFromUser("Enter your favorite whole number: ");

    // This is a similar method I wrote to pause the program with a prompt
    GetKeyFromUser("\nDone! Press any key to exit...");
}

Output 产量

![在此处输入图片描述

I'd write it something like this (though I'd probably give the user a chance to give up): 我会这样写(尽管我可能会给用户一个放弃的机会):

 private static int GetInput(string v)
 {
     int intradius = 0;   //needs to be initialized to keep compiler happy
     while (true)
     {
         Console.Write($"{v}: ");
         string strradius = Console.ReadLine();
         if (!int.TryParse(strradius, out intradius))
         {
             Console.WriteLine($"An integer is required: [{strradius}] is not an integer");
         }
         else if (intradius < 1)
         {
             Console.WriteLine($"The entered number [{intradius}] is out of range, it must be one or greater");
         }
         else
         {
             break;      //breaking out of the while loop, the input is good
         }
     }

     return intradius;
 }

For recoverable validation use conditional code/checks rather than relying on exceptions. 对于可恢复的验证,请使用条件代码/检查,而不要依赖异常。 Exceptions are expensive from a performance perspective primarily because they will generate a call stack. 从性能的角度来看,异常的代价很高,主要是因为异常将生成调用堆栈。

Instead, look at something like: 相反,请看以下内容:

private static int GetInput(string v)
{
    Console.Write(v);
    string strradius = Console.ReadLine();

    if (string.IsNullOrEmpty(strradius)
    {
        Console.WriteLine("You must enter a value.");
        return 0;
    }

    int intradius;

    bool result = int.TryParse(strradius, out intradius);
    if (!result)
        Console.WriteLine("You must enter a valid number.");
    else if (intradius < 1)
        Console.WriteLine("Your number is out of range");

    Console.WriteLine("Okay");
    return intradius;
} 

Personally, I like to wrap business logic results: 就个人而言,我喜欢包装业务逻辑结果:

// Result container.
private class RadiusValidationResults
{
   public bool IsSuccessful {get; private set;}
   public int Radius {get; private set;}
   public string FailureReason {get; private set;}

   private RadiusValidationResults()
   { }

   public static RadiusValidationResults Success(int result)
   {
      return new RadiusValidationResults { IsSuccessful = true, Radius = result };
    }

    public static RadiusValidationResults Failure(string failureReason)
    {
      return new RadiusValidationResults { FailureReason = failureReason };
    }
}

// Validation business logic.
private static RadiusValidationResult ValidateRadius(string input)
{
    if (string.IsNullOrEmpty(input)
        return RadiusValidationResult.Failure("You must enter a value.");

    int radius;

    if (!int.TryParse(strradius, out radius))
        return RadiusValidationResult.Failure("You must enter a valid number.");
    else if (intradius < 1)
        return RadiusValidationResult.Failure("Your number is out of range");

    return RadiusValidationResult.Success(radius);   
}

then your calling method that interacts with the Console: 然后您与控制台交互的调用方法:

private static int GetInput()
{
   try
   {
      var result = ValidateRadius(Console.ReadLine());
      if(!result.IsSuccessful)
         Console.WriteLine(result.FailureReason);
      else
         Console.WriteLine("Okay");

      return result.Radius;
   catch // Here you can handle specific exception types, or bubble to a higher level. Log exception details and either terminate or present users with a generic "Whoops" and let them retry the operation.
   {
       Console.WriteLine("An unexpected error occurred.")
   }
}

This means that your business logic (validating) can be unit tested without a hard dependency on the data source or outputs. 这意味着您可以对业务逻辑(验证)进行单元测试,而无需严格依赖数据源或输出。 (Console) The code should be succinct and easy to understand what is being done. (控制台)代码应简洁明了,易于理解正在执行的操作。 A Try/Catch can be added to GetInput to handle the exceptional case. 可以将Try / Catch添加到GetInput以处理特殊情况。 Generally let exceptions bubble to a high-enough level to handle them. 通常,让异常冒到足够高的水平来处理它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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