C#:从用户输入创建一个字符串数组,然后如果无法将其解析为 int 数组,则重试输入

C#: Creating a string array from user input, then if it can't be parsed into int array, retrying the input

我一直在尝试用 C# 制作一个矩阵控制台应用程序,但在检查用户输入的可行性时我遇到了困难。这是代码:

//INPUT OF VALUES INTO MATRIX A BY ROWS
int ValuesCounter = 2; //index for new arrays created on fail.
string[] valuesValuesCounter = Console.ReadLine().Split(' '); //read user input into string array
int[] valuescheckValuesCounter = new int[valuesValuesCounter.Length]; //test array to check if user input can be parsed into int
for (int i = 0; i <= valuesValuesCounter.Length; i++) // loop for checking parseability of individual array items
{
    while(!int.TryParse(valuesValuesCounter[i], out valuescheckValuesCounter[i])) //same
    {
        /*if it can't be parsed, create new array and try again*/
        ValuesCounter += 2;
        Console.WriteLine("Wrong values! Please try again!");
        valuesValuesCounter = Console.ReadLine().Split(' ');
        valuescheckValuesCounter = new int[valuesValuesCounter.Length];
    }
}

我怀疑这是不是很有效。我试过使用 ArrayList 来代替,因为你可以只删除其中的值,但它不能拆分,所以我不知道如何让它工作。执行此操作的更好方法是什么?
也尝试进行我自己的研究,但没有找到任何解决方案(也许它在那里,我只是因为没有看到它而愚蠢,我不知道)。

How to delete an array in c#?

C# arraylist user input
int.TryParse out into an object array(这看起来很接近,但我没有看到任何重试输入的东西,以防无法解析)。

好了。我很笨,所以如果这个问题看起来很愚蠢,那是因为提出这个问题的人很愚蠢。

您不需要在 for 循环中使用 while 循环。只需检查每个项目的有效性和中断,如果遇到无效字符串,当然如果所有都有效(循环将结束)并且 i 将等于数组的长度:

bool success = false;
do
{
     int i = 0;
     string[] valuesValuesCounter = Console.ReadLine().Split(' ');
     int[] valuescheckValuesCounter = new int[valuesValuesCounter.Length];
     for (int i = 0; i <= valuesValuesCounter.Length; i++)
        if(!int.TryParse(valuesValuesCounter[i], out valuescheckValuesCounter[i]) break;
     success = i == valuesValuesCounter.Length;
}while(!success);

另一种方法是使用 Linq:

do
{
    int i = 0;
    string[] valuesValuesCounter = Console.ReadLine().Split(' ');
    int[] valuescheckValuesCounter = new int[valuesValuesCounter.Length];
    success = valuesValuesCounter.All(x => int.TryParse(x, out valuescheckValuesCounter[i++]);
}while(!success);

首先,让用户体验尽可能的好。一次输入一个矩阵可能容易出错,让用户的生活更轻松,一步一步来。

其次,制作执行简单任务的小方法;如果出现新需求,它们更容易推理、更容易编写、更容易调试并且更容易修改。不要让方法做太多事情,它只会让你头疼,并且花费不必要的调试时间。如果您正在学习,如果您似乎正在分解看似荒谬的简单任务,请不要担心,您不会后悔的。永远记住,你能想到的最复杂的问题总是通过解决更小、更容易的问题来解决。

因此,您首先需要的是一种提示用户输入有效整数的方法,让我们试试看:

private static int GetIntegerFromUser(string prompt)
{
    int value;
    Console.Write($"{prompt}: ");

    while (!int.TryParse(Console.ReadLine(), out value))
    {
        Console.WriteLine("That is not a valid value. Please try again.");
        Console.Write($"{prompt}: ");
    }

    return value;
}

看看效果如何?这种方法在生活中只有一个目标;让用户输入一个有效的整数。不要管整数是做什么用的,它不关心,那不是它的工作,这个方法会一直询问用户,直到实现它的人生目标。

好的,下一步是什么?填充矩阵时,我们需要知道:

  1. 行数
  2. 列数

好的,让我们编写一些方法来获取:

private static (int Rows, int Columns) GetMatrixSize()
{
    var rows = GetIntegerFromUser("Please enter number of rows");
    var columns = GetIntegerFromUser("Please enter number of columns");
    return (rows, columns);
}

并且,

private static int[,] GetMatrixValues(int rows, int columns)
{
    var matrix = new int[rows, columns];

    for (var row = 0; row < rows; row++)
    {
        for (var column = 0; column < columns; column++)
        {
            matrix[row, column] =
               GetIntegerFromUser($"Enter matrix value [{row}, {column}]");
        }
    }
}

现在您应该明白为什么制作执行简单任务的小方法是个好主意。因为您很有可能会重用它们,而且一旦您知道该方法可以正常工作,它就会在任何地方正常工作。

好的,我们已经拥有了所需的一切,现在只需将它们放在一起即可:

public static int[,] GetMatrixFromUser()
{
    var size = GetMatrixSize();
    return GetMatrixValues(size.Rows, size.Columns);
}

读起来容易吗?

所以,我们完成了!真的吗?好吧……不。我们根本没有价值验证,这可能是个问题。如果有人决定输入负数行会怎样?好的,没问题,让我们创建一个方法来确保值在有效范围内:

private static bool IsInRange(int value, 
                              int lowerInclusiveBound,
                              int upperExclusiveBound,
                              string messageOnFailedValidation)
{
    if (value >= lowerInclusiveBound &&
        value < upperExclusiveBound)
        return true;

    Console.WriteLine(messageOnFailedValidation);
    return false;
}

现在,因为我们使用了完成定义明确的任务的小方法,添加新需求很容易完成,只需稍微修改我们的 GetMatrixSize 方法:

private static (int Rows, int Columns) GetMatrixSize()
{
    int rows, columns;

    do
    {
        rows = GetIntegerFromUser("Please enter number of rows");
        columns = GetIntegerFromUser("Please enter number of columns");
    } while (!IsInRange(rows,
                        1,
                        int.MaxValue,
                        "Number of rows must be equal or greater than one.") |
             !IsInRange(columns,
                        1,
                        int.MaxValue,
                        "Number of columns must be equal or greater than one."));

    return (rows, columns);
}

是的,现在我们完成了...

差不多!最后一件事。另一个好习惯是确保你所相信的真理是真实的。 GetMatrixValues 你相信什么是真的?您认为 rowscolumns 将具有有效值。但是你的信仰纯粹是基于信仰,你不是在检查这是不是真的。

但是等等...我已经签到 GetMatrixSize 对吧?是的,但是您有什么保证 GetMatrixValues 以后不会从其他方法调用?还记得可重用性吗?

在这里你可以走两条路。如果值无效,您可以抛出异常,或者您可以断言这些值实际上是有效的。因为该方法是私有的并且具有无效值可能意味着调用堆栈某处存在错误,所以这里更好的选择是断言你的真相:

private static int[,] GetMatrixValues(int rows, int columns)
{
    Debug.Assert(rows > 0 && columns > 0);
    var matrix = ...
}

还有其他仅凭信仰的真理吗?谁在确保 upperExclusiveBoundlowerInclusiveBoundIsInRange 中确实有意义?是否有人阻止您拨打以下电话:IsInRange(10, 100, -100, ...)?像这样的调用可能是您代码中其他地方的错误,通过使这些错误更容易发现来让您的生活更轻松。再次声明你的真相:

private static bool IsInRange(int value, 
                              int lowerInclusiveBound,
                              int upperExclusiveBound,
                              string messageOnFailedValidation)
{  
    Debug.Assert(upperExclusiveBound > lowerInclusiveBound);

    if ...
}  

如果您应该检查 GetIntegerFromUser 中的 promptIsInRange 中的 messageOnFailedValidation 是否为空或空字符串等,也可以作为一个很好的例子.

是的,现在我们真的完成了。