# C# 输入和输出

> 原文： [https://zetcode.com/lang/csharp/io/](https://zetcode.com/lang/csharp/io/)

本章专门介绍 C# 中的输入和输出。 C# 中的输入和输出基于流。

## C# 流

流是字节序列的抽象，例如文件，输入/输出设备，进程间通信管道或 TCP/IP 套接字。 流将数据从一个点传输到另一点。 流还能够处理数据。 例如，他们可以压缩或加密数据。 在 .NET Framework 中，`System.IO`命名空间包含允许对数据流和文件进行读写的类型。

C# 为`File`类中的 I/O 操作提供了高级方法，为`StreamReader`或`StreamWriter`等类提供了较低级的方法。

## 处理异常

I/O 操作容易出错。 我们可能会遇到`FileNotFoundException`或`UnauthorizedAccessException`之类的异常。 与 Java 不同，C# 不会强制程序员手动处理异常。 由程序员决定是否手动处理异常。 如果在`try/catch/finally`结构中未手动处理该异常，则该异常由 CLR 处理。

## 释放资源

I/O 资源必须释放。 可以使用`Dispose()`方法在`finally`子句中手动释放资源。 `using`关键字可用于自动释放资源。 同样，`File`类中的方法为我们释放了资源。

## 示例文本文件

在示例中，我们使用以下简单文本文件：

`thermopylae.txt`

```cs
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.

```

## C# `File.ReadAllText`

`File`提供了用于创建，复制，删除，移动和打开单个文件的静态方法，并有助于创建`FileStream`对象。

> **注意**：`File.ReadAllText()`不适合读取非常大的文件。

`File.ReadAllText()`打开一个文件，以指定的编码读取文件中的所有文本，然后关闭该文件。

`Program.cs`

```cs
using System;
using System.IO;
using System.Text;

namespace ReadFileIntoString
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            var text = File.ReadAllText(path, Encoding.UTF8);
            Console.WriteLine(text);
        }
    }
}

```

该程序读取`thermopylae.txt`文件的内容，并将其打印到控制台。

```cs
var text = File.ReadAllText(path, Encoding.UTF8);

```

我们一次性将整个文件读成字符串。 在第二个参数中，我们指定编码。

```cs
$ dotnet run
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.

```

这是输出。

## C# `File.ReadAllLines`

`File.ReadAllLines()`打开一个文本文件，将文件的所有行读入字符串数组，然后关闭文件。

`File.ReadAllLines()`是一种使用 C# 读取文件的便捷方法。 处理非常大的文件时，不应使用它。

`Program.cs`

```cs
using System;
using System.IO;

namespace ReadAllLines
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            string[] lines = File.ReadAllLines(path);

            foreach (string line in lines)
            {
                Console.WriteLine(line);
            }
        }
    }
}

```

该示例将文件中的所有行读入字符串数组。 我们在`foreach`循环中遍历数组，并将每一行打印到控制台。

## C# 创建文件

`File.CreateText()`创建或打开用于写入 UTF-8 编码文本的文件。 如果文件已经存在，则其内容将被覆盖。

`Program.cs`

```cs
using System;
using System.IO;

namespace CreateFileEx
{
    class Program
    {
        static void Main()
        {
            var path = @"C:\Users\Jano\Documents\cars.txt";

            using (StreamWriter sw = File.CreateText(path))
            {
                sw.WriteLine("Hummer");
                sw.WriteLine("Skoda");
                sw.WriteLine("BMW");
                sw.WriteLine("Volkswagen");
                sw.WriteLine("Volvo");
            }
        }
    }
}

```

在示例中，我们创建一个`cars.txt`文件，并将一些汽车名称写入其中。

```cs
using (StreamWriter sw = File.CreateText(path))

```

`CreateText()`方法创建或打开一个文件，用于写入 UTF-8 编码的文本。 它返回一个`StreamWriter`对象。

```cs
sw.WriteLine("Hummer");
sw.WriteLine("Skoda");
...

```

我们向流写入两行。

```cs
$ cat C:\Users\Jano\Documents\cars.txt
Hummer
Skoda
BMW
Volkswagen
Volvo

```

我们已成功将五个汽车名称写入文件。

## C# 创建，最后写入，最后访问时间

使用`File`类，我们可以获取文件的创建，最后写入和最后访问时间。 `Exists()`方法确定指定的文件是否存在。

`Program.cs`

```cs
using System;
using System.IO;

namespace FileTimes
{
    class Program
    {
        static void Main(string[] args)
        {
           var path = @"C:\Users\Jano\Documents\cars.txt";

            if (File.Exists(path))
            {
                Console.WriteLine(File.GetCreationTime(path));
                Console.WriteLine(File.GetLastWriteTime(path));
                Console.WriteLine(File.GetLastAccessTime(path));
            }
        }
    }
}

```

如果存在指定的文件，我们将确定其创建，最后写入和最后访问时间。

```cs
if (File.Exists(path))

```

如果调用方具有所需的权限并且路径包含现有文件的名称，则`Exists()`方法返回`true`。 否则为`false`。 如果`path`为`null`，无效路径或长度为零的字符串，则此方法还返回`false`。

```cs
Console.WriteLine(File.GetCreationTime(path));
Console.WriteLine(File.GetLastWriteTime(path));
Console.WriteLine(File.GetLastAccessTime(path));

```

我们得到指定文件的创建时间，上次写入时间和上次访问时间。

```cs
$ dotnet run
10/13/2019 1:59:03 PM
10/13/2019 1:59:03 PM
10/13/2019 1:59:03 PM

```

这是一个示例输出。

## C# 复制文件

`File.Copy()`方法将现有文件复制到新文件。 它允许覆盖同名文件。

`Program.cs`

```cs
using System;
using System.IO;

namespace CopyFileEx
{
    class Program
    {
        static void Main(string[] args)
        {
            var srcPath = @"C:\Users\Jano\Documents\cars.txt";
            var destPath = @"C:\Users\Jano\Documents\cars2.txt";

            File.Copy(srcPath, destPath, true);

            Console.WriteLine("File copied");
        }
    }
}

```

然后，我们将文件的内容复制到另一个文件。

```cs
var srcPath = @"C:\Users\Jano\Documents\cars.txt";
var destPath = @"C:\Users\Jano\Documents\cars2.txt";

```

这是源文件和目标文件。

```cs
File.Copy(srcPath, destPath, true);

```

`Copy()`方法复制文件。 第三个参数指定是否应覆盖文件（如果存在）。

```cs
$ dotnet run
File copied

```

This is a sample output.

## C# `IDisposable`接口

流实现`IDisposable`接口。 实现此接口的对象必须尽早手动处理。 这是通过在`finally`块中调用`Dispose()`方法或利用`using`语句来完成的。

`Program.cs`

```cs
using System;
using System.IO;

namespace ManualRelease
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamReader sr = null;
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            try
            {
                sr = new StreamReader(path);
                Console.WriteLine(sr.ReadToEnd());
            }
            catch (IOException e)
            {
                Console.WriteLine("Cannot read file");
                Console.WriteLine(e.Message);

            }
            catch (UnauthorizedAccessException e)
            {
                Console.WriteLine("Cannot access file");
                Console.WriteLine(e.Message);

            }
            finally
            {
                 sr?.Dispose();
            }
        }
    }
}

```

在此示例中，我们从磁盘上的文件读取字符。 我们手动释放分配的资源。

```cs
sr = new StreamReader(path);
Console.WriteLine(sr.ReadToEnd());

```

`StreamReader`类用于读取字符。 其父级实现`IDisposable`接口。

```cs
} catch (IOException e)
{
    Console.WriteLine("Cannot read file");
    Console.WriteLine(e.Message);

} catch (UnauthorizedAccessException e)
{
    Console.WriteLine("Cannot access file");
    Console.WriteLine(e.Message);
}

```

可能的异常在`catch`块中处理。

```cs
finally
{
    sr?.Dispose();
}

```

在`finally`块中，`Dispose()`方法清理资源。 使用空条件运算符时，仅当变量不是`null`时，才调用该方法。

## C# 使用语句

`using`语句定义一个范围，在该范围的末尾将放置一个对象。 它提供了一种方便的语法，可确保正确使用`IDisposable`对象。

`Program.cs`

```cs
using System;
using System.IO;

namespace AutomaticCleanup
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            using (var sr = new StreamReader(path))
            {
                Console.WriteLine(sr.ReadToEnd());
            }
        }
    }
}

```

该示例读取`thermopylae.txt`文件的内容。 资源通过`using`语句释放。 如果我们不处理 IO 异常，则 CLR 将处理它们。

## C# `using`声明

`using`声明是在`using`关键字之后的变量声明。 它告诉编译器声明的变量应放在封闭范围的末尾。 从 C# 8.0 开始，`using`声明可用。

`Program.cs`

```cs
using System;
using System.IO;

namespace UsingDeclaration
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            using var sr = new StreamReader(path);

            Console.WriteLine(sr.ReadToEnd());
        }
    }
}

```

该示例读取`thermopylae.txt`文件的内容。 当`sr`变量超出范围时（在`Main()`方法的末尾），将自动清除资源。

## C# `MemoryStream`

`MemoryStream`是用于处理计算机内存中数据的流。

`Program.cs`

```cs
using System;
using System.IO;

namespace MemoryStreamEx
{
    class Program
    {
        static void Main(string[] args)
        {
            using var ms = new MemoryStream(6);

            ms.WriteByte(9);
            ms.WriteByte(11);
            ms.WriteByte(6);
            ms.WriteByte(8);
            ms.WriteByte(3);
            ms.WriteByte(7);

            ms.Position = 0;

            int rs = ms.ReadByte();

            do
            {
                Console.WriteLine(rs);
                rs = ms.ReadByte();

            } while (rs != -1);
        }
    }
}

```

我们用`MemoryStream`将六个数字写入存储器。 然后，我们读取这些数字并将其打印到控制台。

```cs
using var ms = new MemoryStream(6);

```

该行创建并初始化一个容量为六个字节的`MemoryStream`对象。

```cs
ms.WriteByte(9);
ms.WriteByte(11);
ms.WriteByte(6);
...

```

`WriteByte()`方法在当前位置的当前流中写入一个字节。

```cs
ms.Position = 0;

```

我们使用`Position`属性将光标在流中的位置设置为开头。

```cs
do
{
    Console.WriteLine(rs);
    rs = ms.ReadByte();

} while (rs != -1);

```

在这里，我们从流中读取所有字节并将其打印到控制台。

```cs
$ dotnet run
9
11
6
8
3
7

```

这是示例的输出。

## C# `StreamReader`

`StreamReader`从字节流中读取字符。 默认为 UTF-8 编码。

`Program.cs`

```cs
using System;
using System.IO;

namespace ReadFileEx
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            using var sr = new StreamReader(path);

            while (sr.Peek() >= 0)
            {
                Console.WriteLine(sr.ReadLine());
            }
        }
    }
}

```

我们读取文件的内容。 这次我们使用`ReadLine()`方法逐行读取文件。

```cs
while (sr.Peek() >= 0)
{
    Console.WriteLine(sr.ReadLine());
}

```

`Peek()`方法返回下一个可用字符，但不使用它。 它指示我们是否可以再次调用`ReadLine()`方法。 如果没有要读取的字符，则返回`-1`。

## C# 计数行

在下一个示例中，我们将对行进行计数。

`Program.cs`

```cs
using System;
using System.IO;

namespace CountingLines
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 0;
            var path = @"C:\Users\Jano\Documents\thermopylae.txt";

            using var sr = new StreamReader(path);

            while (sr.ReadLine() != null)
            {
                count++;
            }

            Console.WriteLine("There are {0} lines", count);
        }
    }
}

```

我们正在计算文件中的行数。

```cs
while(stream.ReadLine() != null)
{
    count++;
}

```

在`while`循环中，我们使用`ReadLine()`方法从流中读取一行。 如果到达输入流的末尾，它将从流或`null`中返回一行。

```cs
$ dotnet run
There are 3 lines

```

该文件有三行。

## C# `StreamWriter`

`StreamWriter`以特定编码将字符写入流。

`Program.cs`

```cs
using System;
using System.IO;

namespace WriteToFile
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\newfile.txt";

            using var sw = new StreamWriter(path);

            sw.WriteLine("Today is a beautiful day.");
        }
    }
}

```

该示例使用`StreamWriter`将字符串写入文件。

```cs
using (var sw = new StreamWriter(path))

```

我们创建一个新的`StreamWriter`。 默认值是 UTF-8。 `StreamWriter`将路径作为参数。 如果文件存在，它将被覆盖； 否则，将创建一个新文件。

```cs
$ dotnet run
$ cat C:\Users\Jano\Documents\newfile.txt
Today is a beautiful day.

```

我们已经使用`cat`命令显示了文件的内容。

## C# `FileStream`

`FileStream`为文件提供流，同时支持同步和异步读取和写入操作。

`StreamReader`和`StreamWriter`处理文本数据，而`FileStream`处理字节。

`Program.cs`

```cs
using System.IO;
using System.Text;

namespace FileStreamEx
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents\newfile2.txt";

            using var fs = new FileStream(path, FileMode.Append);

            var text = "Фёдор Михайлович Достоевский\n";
            byte[] bytes = new UTF8Encoding().GetBytes(text);

            fs.Write(bytes, 0, bytes.Length);
        }
    }
}

```

我们用俄语西里尔字母写一些文本到文件中。

```cs
using System.Text;

```

`UTF8Encoding`类位于`System.Text`命名空间中。

```cs
using var fs = new FileStream(path, FileMode.Append);

```

创建一个`FileStream`对象。 第二个参数是打开文件的模式。 附加模式将打开文件（如果存在）并查找到文件末尾，或创建一个新文件。

```cs
var text = "Фёдор Михайлович Достоевский";

```

这是俄文西里尔文的文字。

```cs
byte[] bytes = new UTF8Encoding().GetBytes(text);

```

从俄语西里尔字母文本创建一个字节数组。

```cs
fs.Write(bytes, 0, bytes.Length);

```

我们将字节写入文件流。

```cs
$ cat C:\Users\Jano\Documents\newfile2.txt
Фёдор Михайлович Достоевский

```

我们显示创建文件的内容。

## C# `XmlTextReader`

我们可以使用流来读取 XML 数据。 `XmlTextReader`是用于读取 C# 中的 XML 文件的类。 该类是仅转发和只读的。

我们有以下 XML 测试文件：

`languages.xml`

```cs
<?xml version="1.0" encoding="utf-8" ?>
<languages>
    <language>Python</language>
    <language>Ruby</language>
    <language>Javascript</language>
    <language>C#</language>
</languages>

```

此文件包含自定义 XML 标记之间的语言名称。

`Program.cs`

```cs
using System;
using System.IO;
using System.Xml;

namespace ReadingXMLFile
{
    public class Program
    {
        static void Main()
        {
            string path = @"C:\Users\Jano\Documents\languages.xml";

            using (var xreader = new XmlTextReader(path))
            {
                xreader.MoveToContent();

                while (xreader.Read())
                {
                    var node = xreader.NodeType switch
                    {
                        XmlNodeType.Element => String.Format("{0}: ", xreader.Name),
                        XmlNodeType.Text => String.Format("{0} \n", xreader.Value),
                        _ => ""
                    };

                    Console.Write(node);
                }
            }
        }
    }
}

```

本示例从 XML 文件读取数据并将其打印到终端。

```cs
using System.Xml;

```

`System.Xml`命名空间包含与 Xml 读写相关的类。

```cs
using (var xreader = new XmlTextReader(path))

```

创建一个`XmlTextReader`对象。 它是一种读取器，可提供对 XML 数据的快速，非缓存且仅前向访问。 它以文件名作为参数。

```cs
xreader.MoveToContent();

```

`MoveToContent()`方法移至 XML 文件的实际内容。

```cs
while (xreader.Read())

```

该行从流中读取下一个节点。 如果没有更多节点，则`Read()`方法返回`false`。

```cs
var node = xreader.NodeType switch
{
    XmlNodeType.Element => String.Format("{0}: ", xreader.Name),
    XmlNodeType.Text => String.Format("{0} \n", xreader.Value),
    _ => ""
};

Console.Write(node);

```

在这里，我们打印元素名称和元素文本。

```cs
$ dotnet run
language: Python
language: Ruby
language: Javascript
language: C#

```

这是示例的输出。

## C# 创建，移动目录

`System.IO.Directory`是一个类，具有用于在目录和子目录中创建，移动和枚举的静态方法。

`Program.cs`

```cs
using System;
using System.IO;

namespace DirectoryEx
{
    class Program
    {
        static void Main(string[] args)
        {
            Directory.CreateDirectory("temp");
            Directory.CreateDirectory("newdir");
            Directory.Move("temp", "temporary");
        }
    }
}

```

我们创建两个目录，然后重命名其中一个目录。 目录在项目文件夹中创建。

```cs
Directory.CreateDirectory("temp");

```

`CreateDirectory()`方法创建一个新目录。

```cs
Directory.Move("temp", "temporary");

```

`Move()`方法为指定的目录提供一个新名称。

## C# `DirectoryInfo`

`DirectoryInfo`公开了用于在目录和子目录中创建，移动和枚举的实例方法。

`Program.cs`

```cs
using System;
using System.IO;

namespace ShowContents
{
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\Users\Jano\Documents";
            var dirInfo = new DirectoryInfo(path);

            string[] files = Directory.GetFiles(path);
            DirectoryInfo[] dirs = dirInfo.GetDirectories();

            foreach (DirectoryInfo subDir in dirs)
            {
                Console.WriteLine(subDir.Name);
            }

            foreach (string fileName in files)
            {
                Console.WriteLine(fileName);
            }
        }
    }
}

```

我们使用`DirectoryInfo`类遍历特定目录并打印其内容。

```cs
var path = @"C:\Users\Jano\Documents";
var DirInfo = new DirectoryInfo(path);

```

我们显示指定目录的内容。

```cs
string[] files = Directory.GetFiles(path);;

```

我们使用静态`GetFiles()`方法获取目录的所有文件。

```cs
DirectoryInfo[] dirs = dir.GetDirectories();

```

我们得到所有目录。

```cs
foreach (DirectoryInfo subDir in dirs)
{
    Console.WriteLine(subDir.Name);
}

```

在这里，我们遍历目录并将其名称打印到控制台。

```cs
foreach (string fileName in files)
{
    Console.WriteLine(fileName);
}

```

在这里，我们遍历文件数组并将其名称打印到控制台。

在本章中，我们介绍了 C# 中的输入/输出操作。