C#基础-14-异常处理与文件输入输出

前文:

异常处理

异常是在程序执行期间出现的问题。C# 中的异常是对程序运行时出现的特殊情况的一种响应,比如尝试除以零。

异常提供了一种把程序控制权从某个部分转移到另一个部分的方式。C# 异常处理时建立在四个关键词之上的:

  • try:一个 try 块标识了一个将被激活的特定的异常的代码块。后跟一个或多个 catch 块。
  • catch:程序通过异常处理程序捕获异常。catch 关键字表示异常的捕获。
  • finallyfinally 块用于执行给定的语句,不管异常是否被抛出都会执行。例如,如果您打开一个文件,不管是否出现异常文件都要被关闭。
  • throw:当问题出现时,程序抛出一个异常。使用 throw 关键字来完成。

语法

假设一个代码块将出现异常,用一个方法使用 trycatch 关键字捕获异常。try/catch 块内的代码为受保护的代码,示例:

try
{
   // 引起异常的语句
}
catch( ExceptionName e1 )
{
   // 错误处理代码
}
catch( ExceptionName e2 )
{
   // 错误处理代码
}
catch( ExceptionName eN )
{
   // 错误处理代码
}
finally
{
   // 要执行的语句
}

异常类

C# 异常是使用类来表示的。

异常类主要是直接或间接地派生于 System.Exception 类。

System.ApplicationExceptionSystem.SystemException 类是派生于 System.Exception 类的异常类。

System.ApplicationException 类支持由应用程序生成的异常。所以程序员定义的异常都应派生自该类。

System.SystemException 类是所有预定义的系统异常的基类。

下表列出了一些派生自 System.SystemException 类的预定义的异常类:

异常类 描述
System.IO.IOException 处理 I/O 错误。
System.IndexOutOfRangeException 处理当方法指向超出范围的数组索引时生成的错误。
System.ArrayTypeMismatchException 处理当数组类型不匹配时生成的错误。
System.NullReferenceException 处理当依从一个空对象时生成的错误。
System.DivideByZeroException 处理当除以零时生成的错误。
System.InvalidCastException 处理在类型转换期间生成的错误。
System.OutOfMemoryException 处理空闲内存不足生成的错误。
System.StackOverflowException 处理栈溢出生成的错误。

处理方法

C# 以 trycatch 块的形式提供了一种结构化的异常处理方案。使用这些块,把核心程序语句与错误处理语句分离开。

这些错误处理块是使用 trycatchfinally 关键字实现的。下面是一个当除以零时抛出异常的实例:

using System;
namespace ErrorHandlingApplication
{
    class DivNumbers
    {
        int result;
        DivNumbers()
        {
            result = 0;
        }
        public void division(int num1, int num2)
        {
            try
            {
                result = num1 / num2;
            }
            catch (DivideByZeroException e)
            {
                Console.WriteLine($"Exception caught: {e}");
            }
            finally
            {
                Console.WriteLine($"Result: {result}");
            }

        }
        static void Main(string[] args)
        {
            DivNumbers d = new DivNumbers();
            d.division(25, 0);
            Console.ReadKey();
        }
    }
}

输出:

自定义异常

用户自定义的异常类是派生自 ApplicationException 类,示例:

using System;
namespace UserDefinedException
{
   class TestTemperature
   {
      static void Main(string[] args)
      {
        Temperature temp = new Temperature();
        try
        {
            // 1 调用 Temperature 类的 ShowTemp() 函数
            temp.ShowTemp();
        }
        // 5 捕获到异常,创建一个异常 TempIsZeroException 的实例 e
        catch(TempIsZeroException e)
        {
            // 6 输出异常实例 e 的错误信息,Message 为异常的固定返回格式,或一个空的字符串
            Console.WriteLine($"TempIsZeroException: {e.Message}");
        }
        // Console.ReadKey();
      }
   }
}
public class TempIsZeroException: ApplicationException
{
    public TempIsZeroException(string message): base(message)
    {
    }
}
public class Temperature
{
    // 2 故意设置 _temperature 为 0 触发异常
    int _temperature = 0;
    public void ShowTemp()
    {
        if(_temperature == 0)
        {
            // 3 如果 _temperature 为 0 则创建一个异常,传入要显示的信息给 message
            throw (new TempIsZeroException("Zero Temperature found"));
        }
        else
        {
            // 4 如果 _temperature 不为 0 的时候直接输出其值
            Console.WriteLine($"Temperature: {_temperature}");
        }
    }
}

输出:

抛出对象

如果异常是直接或间接派生自 System.Exception 类,可以抛出一个对象,可以在 catch 块中使用 throw 语句来抛出当前的对象,如下所示:

Catch(Exception e)
{
   ...
   Throw e
}

文件输入输出

一个 文件 是一个存储在磁盘中带有 指定名称目录路径数据集合

当打开文件进行读写时,它变成一个

从根本上说,流是通过通信路径传递的字节序列。

有两个主要的流:

  • 输入流: 输入流用于从文件读取数据(读操作)。
  • 输出流: 输出流用于向文件写入数据(写操作)。

IO 类

System.IO 命名空间有各种不同的类,用于执行各种文件操作,如创建和删除文件、读取或写入文件,关闭文件等。

一些 System.IO 命名空间中常用的非抽象类:

类名 描述
BinaryReader 从二进制流读取原始数据。
BinaryWriter 以二进制格式写入原始数据。
BufferedStream 字节流的临时存储。
Directory 有助于操作目录结构。
DirectoryInfo 用于对目录执行操作。
DriveInfo 提供驱动器的信息。
File 有助于处理文件。
FileInfo 用于对文件执行操作。
FileStream 用于文件中任何位置的读写。
MemoryStream 用于随机访问存储在内存中的数据流。
Path 对路径信息执行操作。
StreamReader 用于从字节流中读取字符。
StreamWriter 用于向一个流中写入字符。
StringReader 用于读取字符串缓冲区。
StringWriter 用于写入字符串缓冲区。

FileStream类

System.IO 命名空间中的 FileStream 类有助于文件的读写与关闭。

该类派生自抽象类 Stream

创建一个 FileStream 对象来创建一个新的文件,或打开一个已有的文件,语法如下:

FileStream <object_name> = new FileStream( <file_name>,
<FileMode Enumerator>, <FileAccess Enumerator>, <FileShare Enumerator>);

例如,创建一个 FileStream 对象 F 来读取名为 sample.txt 的文件:

FileStream F = new FileStream("sample.txt", FileMode.Open, FileAccess.Read, FileShare.Read);

FileMode 枚举定义了各种打开文件的方法,其成员有:

  • Append:打开一个已有的文件,并将光标放置在文件的末尾。如果文件不存在,则创建文件。
  • Create:创建一个新的文件。如果文件已存在,则删除旧文件,然后创建新文件。
  • CreateNew:指定操作系统应创建一个新的文件。如果文件已存在,则抛出异常。
  • Open:打开一个已有的文件。如果文件不存在,则抛出异常。
  • OpenOrCreate:指定操作系统应打开一个已有的文件。如果文件不存在,则用指定的名称创建一个新的文件打开。
  • Truncate:打开一个已有的文件,文件一旦打开,就将被截断为零字节大小。然后我们可以向文件写入全新的数据,但是保留文件的初始创建日期。如果文件不存在,则抛出异常。

FileAccess 枚举的成员有:ReadReadWriteWrite

FileShare 枚举的成员有:

  • Inheritable:允许文件句柄可由子进程继承。Win32 不直接支持此功能。
  • None:谢绝共享当前文件。文件关闭前,打开该文件的任何请求(由此进程或另一进程发出的请求)都将失败。
  • Read:允许随后打开文件读取。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取的请求(由此进程或另一进程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。
  • ReadWrite:允许随后打开文件读取或写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行读取或写入的请求(由此进程或另一进程发出)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。
  • Write:允许随后打开文件写入。如果未指定此标志,则文件关闭前,任何打开该文件以进行写入的请求(由此进程或另一进过程发出的请求)都将失败。但是,即使指定了此标志,仍可能需要附加权限才能够访问该文件。
  • Delete:允许随后删除文件。

实例:

using System;
using System.IO;

namespace FileIOApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            // 如果 test.dat 存在,则打开它;如果不存在,则创建新文件。
            FileStream F = new FileStream("test.dat", 
            FileMode.OpenOrCreate, FileAccess.ReadWrite);
            // 往文件 test.dat 里写入 1-20 的字节
            for (int i = 1; i <= 20; i++)
            {
                F.WriteByte((byte)i);
            }
            // 重置文件指针到文件开头 0
            F.Position = 0;
            // 读取并输出文件中的内容
            for (int i = 0; i <= 20; i++)
            {
                Console.Write(F.ReadByte() + " ");
            }
            // 关闭文件
            F.Close();
            Console.ReadKey();
        }
    }
}

输出:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -1

最后一个 -1 是因读取超出文件长度导致的默认返回值。

文本文件读写

StreamReader类

StreamReader 类继承自抽象基类 TextReader ,表示阅读器读取一系列字符。

下面列出了 StreamReader 类中三个常用的方法:

  • public override void Close() :关闭 StreamReader 对象和基础流,并释放任何与读者相关的系统资源。
  • public override int Peek() :返回下一个可用的字符,但不使用它。
  • public override int Read() :从输入流中读取下一个字符,并把字符位置往前移一个字符。

示例 C 盘根目录下有一个 Jamaica.txt 文件:

Down the way where the nights are gay
And the sun shines daily on the mountain top
I took a trip on a sailing ship
And when I reached Jamaica
I made a stop

读取该文件的内容:

using System;
using System.IO;

namespace FileApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                // 创建一个 StreamReader 的实例来读取文件 
                // using 语句也能关闭 StreamReader
                using (StreamReader sr = new StreamReader("C:/Jamaica.txt", Encoding.UTF8))
                {
                    string line;
                   
                    // 从文件读取并显示行,直到文件的末尾 
                    while ((line = sr.ReadLine()) != null)
                    {
                        Console.WriteLine(line);
                    }
                }
            }
            catch (Exception e)
            {
                // 向用户显示出错消息
                Console.WriteLine("The file could not be read:");
                Console.WriteLine(e.Message);
            }
            Console.ReadKey();
        }
    }
}

使用 using 语句自动管理资源,退出 using 块时自动关闭文件流。

运行后输出文件内容。

StreamWriter类

StreamWriter 类继承自抽象类 TextWriter ,表示编写器写入一系列字符。

下面列出一些常用的方法:

  • public override void Close() :关闭当前的 StreamWriter 对象和基础流。
  • public override void Flush() :清理当前编写器的所有缓冲区,使得所有缓冲数据写入基础流。
  • public virtual void Write(bool value) :把一个布尔值的文本表示形式写入到文本字符串或流(继承自 TextWriter)。
  • public override void Write( char value ) :把一个字符写入到流。
  • public virtual void Write( decimal value ) :把一个十进制值的文本表示形式写入到文本字符串或流。
  • public virtual void Write( double value ) :把一个 8 字节浮点值的文本表示形式写入到文本字符串或流。
  • public virtual void Write( int value ) :把一个 4 字节有符号整数的文本表示形式写入到文本字符串或流。
  • public override void Write( string value ) :把一个字符串写入到流。
  • public virtual void WriteLine() :把行结束符写入到文本字符串或流。

示例:

using System;
using System.IO;

namespace FileApplication
{
    class Program
    {
        static void Main(string[] args)
        {

            string[] names = new string[] {"Zara Ali", "Nuha Ali"};
            using (StreamWriter sw = new StreamWriter("names.txt"))
            {
                foreach (string s in names)
                {
                    sw.WriteLine(s);

                }
            }

            // 从文件中读取并显示每行
            string line = "";
            using (StreamReader sr = new StreamReader("names.txt"))
            {
                while ((line = sr.ReadLine()) != null)
                {
                    Console.WriteLine(line);
                }
            }
            Console.ReadKey();
        }
    }
}

输出:

Zara Ali
Nuha Ali

二进制文件读写

BinaryReader类

BinaryReader 类用于从文件读取二进制数据。

一个 BinaryReader 对象通过向它的构造函数传递 FileStream 对象而被创建。

常用方法:

  • public override void Close() :关闭 BinaryReader 对象和基础流。
  • public virtual int Read() :从基础流中读取字符,并把流的当前位置往前移。
  • public virtual bool ReadBoolean() :从当前流中读取一个布尔值,并把流的当前位置往前移一个字节。
  • public virtual byte ReadByte() :从当前流中读取下一个字节,并把流的当前位置往前移一个字节。
  • public virtual byte[] ReadBytes( int count ) :从当前流中读取指定数目的字节到一个字节数组中,并把流的当前位置往前移指定数目的字节。
  • public virtual char ReadChar() :从当前流中读取下一个字节,并把流的当前位置按照所使用的编码和从流中读取的指定的字符往前移。

BinaryWriter类

BinaryWriter 类用于向文件写入二进制数据。

一个 BinaryWriter 对象通过向它的构造函数传递 FileStream 对象而被创建。

常用方法:

  • public override void Close() :关闭 BinaryWriter 对象和基础流。
  • public virtual void Flush() :清理当前编写器的所有缓冲区,使得所有缓冲数据写入基础设备。
  • public virtual long Seek( int offset, SeekOrigin origin ) :设置当前流内的位置。
  • public virtual void Write( bool value ) :把一个单字节的布尔值写入到当前流中,0 表示 false,1 表示 true。
  • public virtual void Write( byte value ) :把一个无符号字节写入到当前流中,并把流的位置往前移一个字节。
  • public virtual void Write( byte[] buffer ) :把一个字节数组写入到基础流中。

实例:

using System;
using System.IO;

namespace BinaryFileApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            BinaryWriter bw;
            BinaryReader br;
            int i = 25;
            double d = 3.14157;
            bool b = true;
            string s = "I am happy";
            // 创建文件
            try
            {
                bw = new BinaryWriter(new FileStream("mydata",
                                FileMode.Create));
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message + "\n Cannot create file.");
                return;
            }
            // 写入文件
            try
            {
                bw.Write(i);
                bw.Write(d);
                bw.Write(b);
                bw.Write(s);
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message + "\n Cannot write to file.");
                return;
            }

            bw.Close();
            // 读取文件
            try
            {
                br = new BinaryReader(new FileStream("mydata",
                                FileMode.Open));
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message + "\n Cannot open file.");
                return;
            }
            try
            {
                i = br.ReadInt32();
                Console.WriteLine($"Integer data: {i}");
                d = br.ReadDouble();
                Console.WriteLine($"Double data: {d}");
                b = br.ReadBoolean();
                Console.WriteLine($"Boolean data: {b}");
                s = br.ReadString();
                Console.WriteLine($"String data: {s}");
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message + "\n Cannot read from file.");
                return;
            }
            br.Close();
            Console.ReadKey();
        }
    }
}

输出:

Integer data: 25
Double data: 3.14157
Boolean data: True
String data: I am happy

Windows系统文件操作

DirectoryInfo类

DirectoryInfo 类派生自 FileSystemInfo 类。

它提供了各种用于创建、移动、浏览目录和子目录的方法。该类不能被继承。

常用属性:

  • Attributes :获取当前文件或目录的属性。
  • CreationTime :获取当前文件或目录的创建时间。
  • Exists :获取一个表示目录是否存在的布尔值。
  • Extension :获取表示文件存在的字符串。
  • FullName :获取目录或文件的完整路径。
  • LastAccessTime :获取当前文件或目录最后被访问的时间。
  • Name :获取该 DirectoryInfo 实例的名称。

常用方法:

  • public void Create() :创建一个目录。
  • public DirectoryInfo CreateSubdirectory( string path ) :在指定的路径上创建子目录。指定的路径可以是相对于 DirectoryInfo 类的实例的路径。
  • public override void Delete() :如果为空的,则删除该 DirectoryInfo。
  • public DirectoryInfo[] GetDirectories() :返回当前目录的子目录。
  • public FileInfo[] GetFiles() :从当前目录返回文件列表。

FileInfo类

FileInfo 类派生自 FileSystemInfo 类。

它提供了用于创建、复制、删除、移动、打开文件的属性和方法,且有助于 FileStream 对象的创建。该类不能被继承。

常用属性:

  • Attributes :获取当前文件的属性。
  • CreationTime :获取当前文件的创建时间。
  • Directory :获取文件所属目录的一个实例。
  • Exists :获取一个表示文件是否存在的布尔值。
  • Extension :获取表示文件存在的字符串。
  • FullName :获取文件的完整路径。
  • LastAccessTime :获取当前文件最后被访问的时间。
  • LastWriteTime :获取文件最后被写入的时间。
  • Length :获取当前文件的大小,以字节为单位。
  • Name :获取文件的名称。

常用方法:

  • public StreamWriter AppendText() :创建一个 StreamWriter,追加文本到由 FileInfo 的实例表示的文件中。
  • public FileStream Create() :创建一个文件。
  • public override void Delete() :永久删除一个文件。
  • public void MoveTo( string destFileName ) :移动一个指定的文件到一个新的位置,提供选项来指定新的文件名。
  • public FileStream Open( FileMode mode ) :以指定的模式打开一个文件。
  • public FileStream Open( FileMode mode, FileAccess access ) :以指定的模式,使用 read、write 或 read/write 访问,来打开一个文件。
  • public FileStream OpenRead() :创建一个只读的 FileStream。
  • public FileStream OpenWrite() :创建一个只写的 FileStream。

输出 C 盘下 Windows 文件夹内文件列表实例:

using System;
using System.IO;

namespace WindowsFileApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            // 创建一个 DirectoryInfo 对象
            DirectoryInfo mydir = new DirectoryInfo(@"C:\Windows");

            // 获取目录中的文件以及它们的名称和大小
            FileInfo [] f = mydir.GetFiles();
            foreach (FileInfo file in f)
            {
                Console.WriteLine("File Name: {0} Size: {1}",
                                    file.Name, file.Length);
            }
            Console.ReadKey();
        }
    }
}