如何在C#中正确处理空白、null或空行

4
我有一些使用文本文件的 C# 代码,我似乎无法使其正确处理空行或空格行。
我的代码:
        while (!file.EndOfStream)
        {
            line = file.ReadLine();

            bool isComment = (line[0] == '/') && (line[1] == '/');
            bool isPoint = (line[0] == '(') && (line[line.Length - 1] == ')');
            bool isWhiteSpace = string.IsNullOrEmpty(line);

            Debug.Log("Comment: " + isComment + ", Point: " + isPoint + ", WhiteSpace: " + isWhiteSpace + "Value: '" + line + "'");

            if (!isComment && !isPoint && !isWhiteSpace) { Application.Quit(); }
            else if (isPoint)
            {
                //Strip parenthesis
                line = line.Remove(line.Length - 1, 1).Remove(0, 1);

                //break into float array
                string[] arr = line.Split(',');

                float xVal = float.Parse(arr[0]);
                float yVal = float.Parse(arr[1]);
                float zVal = float.Parse(arr[2]);

                Vector3 currentVector = new Vector3(xVal, yVal, zVal);
                results.Add(currentVector);
            }
        }

你可以看到我正在使用Vector3进行一些操作。如果这一行是注释行或空白行,我希望它什么也不做。如果它注意到括号,我希望它假设它是一个Vector3并解析它。最后,如果这是一个既不是这些的行,我希望它停止运行。以下是我使用记事本创建的示例文本文件:
//This is a comment
// ... and so is this!
(0, -1.5, 3)
(1, 4, 1.23)

(3, 5, 2)

请注意第二个和第三个 Vector3 之间有一个空隙。在这种特殊情况下,该行完全为空,它不包含空格或任何东西,我只是在记事本中按了[Enter][Enter]。当我的脚本到达这一行时,它似乎触发了file.EndOfStream布尔值...但它并不是文件的结尾!我该如何解决这个问题?我的while循环是否有更合适的条件?我还尝试过读入该行并检查它是否为null作为while条件,这是一种更流行的方法,但这种做法对我的情况也不起作用。

** 注意:“file”是类型为StreamReader的变量 **


7
一个空字符串和一个完全由空格组成的字符串并不相同。一个新行是一个空格字符,如果你想检查字符串是否为空、为 null 或纯粹是空格,可以使用 string.IsNullOrWhitespace - Preston Guillot
好的,我之前不知道这个……所以“空”行是指像这样的东西:string line = ""; 吗?如果你的定义成立,那么我认为在文件中永远不会出现空行,只会有空格或null。我的想法正确吗? - Drifter64
我怀疑它在EndOfStream检查上失败了,几乎肯定是因为if (!isComment && !isPoint && !isWhiteSpace) { Application.Quit(); }而退出的。 - Rob
Steve: 如果一行不是注释、空白或者Vector3,我希望程序完全退出。 - Drifter64
你们都想得很好。我认为答案可能就在这里。我会进行一些测试,我认为与David的line[0]答案有关,所以我会先使用Preston关于IsNullOrWhitespace的想法来检查行是否为空格或null,再检查其他布尔值。 - Drifter64
显示剩余4条评论
2个回答

8
这更像是一条风格建议而不是答案,虽然这也可以防止你看到的问题。
首先,使用 StreamReader 时,当您调用 ReadLine 时,仅在到达文件结尾时才会收到 null 结果。 您还不关心行开头和结尾的空格,并且可能不关心完全为空白的行。 所以你可以用这个来测试文件结束和空行:
string line;
while ((line = file.ReadLine()) != null)
{
    line = line.Trim();
    if (line == "")
        continue;
}

接下来是一些关于起始/结束字符的测试,这在某些情况下仍然会引起问题。具体而言,在只有一个字符的行中读取第二个字符将会引发异常。

不要对未经测试的字符串使用索引,可以使用StartsWithEndsWith方法进行测试:

bool isComment = line.StartsWith("//");
bool isPoint = line.StartsWith("(") && line.EndsWith(")");

最后,在解析点值的代码中,您假设任何以(开头并以)结尾的行至少有2个逗号,并且文本将正确解析。这是一个错误的假设。

更好的处理方式是检测并处理每种情况,将解析功能拆分为可以重复使用的方法。

以下是我的版本:

public class Program
{
    public static void Main()
    {
        List<Vector3> results = new List<Vector3>();
        using (var file = System.IO.File.OpenText(@"C:\temp\test.txt"))
        {
            string line;
            while ((line = file.ReadLine()?.Trim()) != null)
            {
                // skip empty lines and comments
                if (line == string.Empty || line.StartsWith("//"))
                    continue;
                // parse all other lines as vectors, exit program on error
                try
                {
                    Vector3 vector = ParseVector(line);
                    results.Add(vector);
                }
                catch (FormatException e)
                {
                    Console.WriteLine("Parse error on line: {0}", line);
                    throw;
                }
            }
        }

        foreach (var v in results)
            Console.WriteLine("({0},{1},{2})", v.X, v.Y, v.Z);
    }

    // parse string in format '(x,y,z)', all as floats
    // throws FormatException on any error
    public static Vector3 ParseVector(string text)
    {
        if (!text.StartsWith("(") || !text.EndsWith(")"))
            throw new FormatException();
        string[] parts = text.Substring(1, text.Length - 1).Split(',');
        if (parts.Length != 3)
            throw new FormatException();
        float x = float.Parse(parts[0]);
        float y = float.Parse(parts[1]);
        float z = float.Parse(parts[2]);
        return new Vector3(x, y, z);
    }
}

如果你不想使用异常,你可以返回null或者使用TryParse方法的模式,返回一个布尔类型的成功/失败指示器,并使用out参数将结果写入。在这种情况下,我更喜欢使用异常。


0

David是正确的。我遇到了一个数组越界异常。以下是我的已更正并且可运行的代码:

        while (!file.EndOfStream)
        {
            line = file.ReadLine();

            bool isWhiteSpace = false;
            bool isComment = false;
            bool isPoint = false;

            isWhiteSpace = string.IsNullOrEmpty(line);

            if (!isWhiteSpace)
            {
                isComment = (line[0] == '/') && (line[1] == '/');
                isPoint = (line[0] == '(') && (line[line.Length - 1] == ')');
            }
            Debug.Log("Comment: " + isComment + ", Point: " + isPoint + ", WhiteSpace: " + isWhiteSpace + "Value: '" + line + "'");

            if (!isComment && !isPoint && !isWhiteSpace) { Application.Quit(); }
            else if (isPoint)
            {
                //Strip parenthesis
                line = line.Remove(line.Length - 1, 1).Remove(0, 1);

                //break into float array
                string[] arr = line.Split(',');

                float xVal = float.Parse(arr[0]);
                float yVal = float.Parse(arr[1]);
                float zVal = float.Parse(arr[2]);

                Vector3 currentVector = new Vector3(xVal, yVal, zVal);
                results.Add(currentVector);
            }
        }

你可以将此发布到代码审查中,以获得一些改进建议。 - usr
我知道有一些效率低下的问题。我很高兴能找到更致命的问题。这不是生产代码,而是为我的个人使用而制作的。 - Drifter64
1
我建议利用CR进行学习和个人发展,这比改进单个代码更重要。 - usr
好的。我可以在哪里提交呢?我猜你是在谈论 SO 上的特定位置。 - Drifter64
有一个代码审查Stack Exchange网站。在谷歌上搜索它。同时请遵守该网站的特定规则。如果感兴趣,您可以在此处留下您的提交链接。 - usr
请阅读《Stack Overflow用户的代码审查指南》(http://meta.codereview.stackexchange.com/questions/5777/a-guide-to-code-review-for-stack-overflow-users)。 - holroy

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