ZetCode

C# EndOfStreamException

最后修改于 2025 年 4 月 20 日

本教程解释了在使用流时如何在 C# 中处理 EndOfStreamException。 当尝试读取超出流末尾的内容时,会发生此异常。

EndOfStreamException 在尝试读取超出流末尾的内容时抛出。 它通常在使用 BinaryReader、StreamReader 和其他基于流的类时遇到。

EndOfStreamException 指示底层流已到达其末尾。 正确处理可以防止在处理未知长度的流时发生崩溃。

基本的 EndOfStreamException 示例

此示例演示了使用 BinaryReader 读取超出文件末尾时如何发生 EndOfStreamException。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        try
        {
            using (var stream = new MemoryStream(new byte[4]))
            using (var reader = new BinaryReader(stream))
            {
                // Read one int (4 bytes) - OK
                int value1 = reader.ReadInt32();
                Console.WriteLine($"First read: {value1}");

                // Attempt to read another int - throws EndOfStreamException
                int value2 = reader.ReadInt32();
                Console.WriteLine($"Second read: {value2}");
            }
        }
        catch (EndOfStreamException ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
            Console.WriteLine("Attempted to read past end of stream");
        }
    }
}

该代码创建一个包含 4 个字节 (足以容纳一个 int) 的 MemoryStream。 读取一个整数后,尝试读取另一个整数会抛出 EndOfStreamException。 该示例显示了发生此异常的基本场景 - 尝试读取比流中存在的数据更多的数据时。

BinaryReader 成功读取了第一个 4 字节整数,但第二次读取尝试失败,因为流中没有更多数据。 该异常被捕获并使用信息性消息优雅地处理。 这说明了在读取流时检查流位置或处理此异常的重要性。

检查流位置

此示例演示了如何通过在读取之前检查流位置来避免 EndOfStreamException。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        using (var stream = new MemoryStream(new byte[8]))
        using (var reader = new BinaryReader(stream))
        {
            while (reader.BaseStream.Position < reader.BaseStream.Length)
            {
                int value = reader.ReadInt32();
                Console.WriteLine($"Read value: {value}");
            }

            Console.WriteLine("Reached end of stream successfully");
        }
    }
}

通过比较 Position 和 Length 属性,我们可以读取到末尾而不发生异常。 这是一种主动的流读取方法。 该示例使用一个包含 8 个字节(足以容纳两个整数)的 MemoryStream。 只要当前位置没有到达流的末尾,while 循环就会继续读取。

此方法比捕获异常更有效,并且在流结构已知时首选此方法。 它演示了在您控制读取过程并可以在每次读取操作之前检查位置的情况下,读取流的良好实践。

读取可变长度数据

此示例处理从文件中读取可变长度数据时的 EndOfStreamException。

Program.cs
using System;
using System.IO;

class Program
{
    static void Main()
    {
        string filePath = "data.bin";
        
        try
        {
            using (var stream = File.OpenRead(filePath))
            using (var reader = new BinaryReader(stream))
            {
                while (true)
                {
                    try
                    {
                        int id = reader.ReadInt32();
                        string name = reader.ReadString();
                        Console.WriteLine($"ID: {id}, Name: {name}");
                    }
                    catch (EndOfStreamException)
                    {
                        Console.WriteLine("Finished reading file");
                        break;
                    }
                }
            }
        }
        catch (FileNotFoundException)
        {
            Console.WriteLine($"File not found: {filePath}");
        }
    }
}

该代码尝试读取记录,直到发生 EndOfStreamException,该异常用作终止条件。 当事先不知道确切的记录数时,此模式很有用。 该示例显示了一个常见场景,其中二进制文件包含多个记录,每个记录由一个整数 ID 和一个字符串组成。

内部 try-catch 块捕获 EndOfStreamException,以便在文件结束时优雅地退出读取循环。 当处理未单独存储记录数的文件,或者处理不包含记录数元数据的旧文件格式时,此方法特别有用。

自定义流包装器

此示例演示了如何创建自定义流包装器,该包装器提供具有显式流结束检查的安全读取方法。

Program.cs
using System;
using System.IO;

public class SafeBinaryReader : BinaryReader
{
    public SafeBinaryReader(Stream input) : base(input) { }

    public bool TryReadInt32(out int value)
    {
        value = 0;
        if (BaseStream.Position + 4 > BaseStream.Length)
            return false;

        value = ReadInt32();
        return true;
    }

    public bool TryReadString(out string value)
    {
        value = null;
        try
        {
            value = ReadString();
            return true;
        }
        catch (EndOfStreamException)
        {
            return false;
        }
    }
}

class Program
{
    static void Main()
    {
        byte[] data = new byte[10];
        new Random().NextBytes(data);

        using (var stream = new MemoryStream(data))
        using (var reader = new SafeBinaryReader(stream))
        {
            while (reader.TryReadInt32(out int value))
            {
                Console.WriteLine($"Read integer: {value}");
            }

            Console.WriteLine("No more integers to read");
        }
    }
}

SafeBinaryReader 类使用 TryRead 方法扩展了 BinaryReader,这些方法返回 false 而不是抛出异常。 这提供了更清晰的控制流。 该示例演示了如何围绕 BinaryReader 创建一个更友好的包装器,该包装器避免了预期情况(如流结束)的异常。

TryReadInt32 方法检查在读取之前是否剩余足够的字节,而 TryReadString 在内部捕获 EndOfStreamException。 当您希望提供显式的流结束检测,而不强制调用者为正常的控制流使用异常处理时,此模式很有用。 它演示了如何创建更健壮的流处理组件。

网络流处理

此示例演示了从 NetworkStream 读取时 EndOfStreamException 的处理,其中流可能会意外关闭。

Program.cs
using System;
using System.IO;
using System.Net.Sockets;

class Program
{
    static void Main()
    {
        try
        {
            using (var client = new TcpClient("example.com", 80))
            using (var stream = client.GetStream())
            using (var reader = new BinaryReader(stream))
            {
                // Simulate reading response (in real code, follow protocol)
                while (true)
                {
                    try
                    {
                        byte[] data = reader.ReadBytes(1024);
                        if (data.Length == 0)
                            break;
                            
                        Console.WriteLine($"Read {data.Length} bytes");
                    }
                    catch (EndOfStreamException)
                    {
                        Console.WriteLine("Connection closed by remote host");
                        break;
                    }
                }
            }
        }
        catch (SocketException ex)
        {
            Console.WriteLine($"Network error: {ex.Message}");
        }
    }
}

网络流可能会意外关闭,因此 EndOfStreamException 处理对于健壮的网络代码至关重要。 此示例演示了从到 Web 服务器的 TCP 连接读取数据。 while 循环尝试读取 1KB 块中的数据,直到发生空读取(指示正常关闭)或发生 EndOfStreamException(指示突然关闭)。

该示例显示了对预期流结束条件和网络错误的正确处理。 在实际应用中,您通常会遵循特定的协议,而不仅仅是读取到流结束。 此模式对于网络编程尤其重要,因为连接可能会意外终止。

读取部分数据

此示例演示了当 EndOfStreamException 在记录中间发生时,如何处理部分读取。

Program.cs
using System;
using System.IO;

class Program
{
    static void ReadData(BinaryReader reader)
    {
        try
        {
            int id = reader.ReadInt32();
            string name = reader.ReadString();
            DateTime timestamp = new DateTime(reader.ReadInt64());
            
            Console.WriteLine($"Complete record: {id}, {name}, {timestamp}");
        }
        catch (EndOfStreamException)
        {
            Console.WriteLine("Warning: Incomplete record at end of file");
            // Log or handle partial data
        }
    }

    static void Main()
    {
        // Create test file with partial record
        using (var stream = new MemoryStream())
        {
            using (var writer = new BinaryWriter(stream))
            {
                writer.Write(1);
                writer.Write("Complete Record");
                writer.Write(DateTime.Now.Ticks);
                
                // Partial record
                writer.Write(2);
                writer.Write("Partial");
                // Missing timestamp
            }

            stream.Position = 0;
            using (var reader = new BinaryReader(stream))
            {
                ReadData(reader); // Reads complete record
                ReadData(reader); // Attempts to read partial record
            }
        }
    }
}

在读取复杂的记录时,EndOfStreamException 可能会在记录中间发生。 此示例演示了如何检测和处理此类情况。 该代码定义了一个方法,该方法读取包含整数、字符串和 DateTime 的记录。 当文件末尾存在部分记录时,将捕获并处理 EndOfStreamException。

该示例创建一个测试流,其中包含一条完整记录和一条部分记录(缺少时间戳)。 这演示了如何优雅地处理损坏或截断的文件,在这些文件中记录可能不完整。 在生产代码中,您可以记录此类事件或尝试恢复,具体取决于您的要求。

与其他异常结合

此示例显示了从文件读取时的全面异常处理,包括 EndOfStreamException。

Program.cs
using System;
using System.IO;

class Program
{
    static void ProcessFile(string path)
    {
        try
        {
            using (var stream = File.Open(path, FileMode.Open))
            using (var reader = new BinaryReader(stream))
            {
                while (reader.BaseStream.Position < reader.BaseStream.Length)
                {
                    try
                    {
                        int id = reader.ReadInt32();
                        double value = reader.ReadDouble();
                        Console.WriteLine($"Record: {id}, {value}");
                    }
                    catch (EndOfStreamException)
                    {
                        Console.WriteLine("Unexpected end of file");
                        break;
                    }
                    catch (IOException ex)
                    {
                        Console.WriteLine($"I/O error: {ex.Message}");
                        break;
                    }
                }
            }
        }
        catch (FileNotFoundException)
        {
            Console.WriteLine($"File not found: {path}");
        }
        catch (UnauthorizedAccessException)
        {
            Console.WriteLine($"Access denied: {path}");
        }
    }

    static void Main()
    {
        ProcessFile("data.bin");
    }
}

该示例演示了一个完整的具有多个异常处理程序的文件处理方法。 EndOfStreamException 与其他 I/O 错误分开捕获,以进行特定处理。 外部 try-catch 处理文件访问问题,而内部 try-catch 处理流读取错误。

这种结构允许对不同的错误条件(找不到文件与读取错误与意外的文件结束)进行不同的响应。 该示例展示了 C# 中健壮的文件处理的最佳实践,其中不同的异常需要不同的处理策略。 该代码在提供全面的错误处理的同时保持了可读性。

来源

EndOfStreamException 类文档

本教程介绍了在使用流时如何在 C# 中处理 EndOfStreamException,包括预防、检测和恢复技术。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。 迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程