MCP 70-483 Programming in C# のお勉強 「4-1 I/O 操作を実行する」

MCP試験 [70-483 Programming in C#](https://www.microsoft.com/ja-jp/learning/exam-70-483.aspx) の学習材料。

目次は[こちら](http://qiita.com/takutoy/items/863ab5523cf59173257a)

# 4-1 I/O 操作を実行する

## [ファイル (System.IO.*)](https://msdn.microsoft.com/ja-jp/library/system.io(v=vs.110).aspx)
### ファイルの移動、コピー、削除

File クラスを使う

```csharp
string fromfile = @"C:\work\1.txt";
string tofile = @"C:\work\2.txt";

// fromfile が存在するか確認
bool fromfile_exists = File.Exists(fromfile);

// fromfile を tofile に移動
// tofile が存在したら IOException
File.Move(fromfile, tofile);

// fromfile を tofile にコピー
// tofile が存在したら IOException
File.Copy(fromfile, tofile);

// fromfile を tofile にコピー
// tofile が存在したら上書き
File.Copy(fromfile, tofile, true);

// tofile を削除
File.Delete(tofile);
```

### フォルダ、ファイルの列挙

Directory クラスを使う

```csharp
string dir = @"C:\work";

string[] workFiles = Directory.GetFiles(dir);
string[] workExcelFiles = Directory.GetFiles(dir, "*.xls?");
string[] workAllFiles = Directory.GetFiles(dir, "*", SearchOption.AllDirectories);

// ファイルの列挙と同様に、フォルダの列挙も可能。
string[] workDirs = Directory.GetDirectories(dir);
 
// string[] ではなく、IEnumerable<string> でも取得できる。
// ファイル数がとても多い場合や AllDirectories オプションを使う場合、
// こちらの方がパフォーマンスが良くなるなることが多い。
IEnumerable<string> workAllFilesIe = Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories);
```

列挙メソッド
- Directory.GetFiles()
- Directory.EnumerateFiles()
- Directory.GetDirectories()
- Directory.EnumerateDirectories() 

searchPattern(第二引数)に指定できる特殊文字

|文字|意味|
|:-:|:--|
|*|0文字以上の文字列|
|?|0または1文字|

### FileInfo と DirectoryInfo
File クラスと Directory クラスはファイルやフォルダを文字列で扱える静的クラス。
FileInfo クラスや DirectoryInfo クラスを使うとファイルやフォルダをオブジェクトとして扱うこともできる。

```csharp
string dir = @"C:\work";

DirectoryInfo di = new DirectoryInfo(dir);
bool dirExists = di.Exists;
DateTime dirCreateTime = di.CreationTime;

FileInfo[] fis = di.GetFiles("*");
FileInfo fi = fis[0];
string fileFullName = fi.FullName;
string fileName = fi.Name;
```

### パスの作成

```csharp
// フォルダ名の連結には Path.Combile() メソッドを使う
string exeDir = AppDomain.CurrentDomain.BaseDirectory; // EXEがあるフォルダ

string logDir = Path.Combine(exeDir, "Logs"); // ./Logs
Directory.CreateDirectory(logDir);

string logFile = Path.Combine(logDir, "app.log"); // ./Logs/app.log
File.AppendAllText(logFile, "log");
```



### ファイルの読み書き

File クラスを使うと簡単にできる。

```csharp
string file = @"C:\work\test.txt";

// 読み
string text = File.ReadAllText(file);

// 書き
File.WriteAllText(file, "newtext");    
```
他、下記のメソッドもある。

- string[] File.ReadAllLines(string path)
- byte[] File.ReadAllBytes(string path)
- void File.WriteAllLines(string path, string[] contents)
- void File.WriteAllBytes(string path, byte[] bytes)
- void File.AppendAllText(string path, string contents)
- void File.AppendAllLines(string path, string[] contents)


Stream で読み書き

大きいファイルを扱うときは string や byte[] だとメモリを食うので FileStream を使うとよい。ファイルのアクセス共有や様々なオプションを指定したい場合も FileStream を使う。
 
```csharp
var file = @"C:\Windows\notepad.exe";

byte[] buffer = new byte[4096];
using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
    // 最後の4KBだけ読む
	fs.Seek(-4096, SeekOrigin.End);
	int l = fs.Read(buffer, 0, buffer.Length);
}
```

Stream の主なオペレーションは下記3つ。

- Read
- Write
- Seek

Stream の種類によっては使用できないオペレーションもある。例えば ReadOnly で開いた FileStream には Write できない。実行可否はCan～プロパティで確認できる。

- CanRead
- CanWrite
- CanSeek

使い終わったら Close() または Dispose() も忘れないように。

StreamReader, StreamWriter

StreamReader/Writerを使うとストリームを文字列で扱える。

```csharp
string file = @"C:\work\test.txt";

// ファイルに文字列で書き込む StreamWriter
using (StreamWriter sw = new StreamWriter(file))
{
    sw.Write("test");
}

// ファイルから文字列を読む StreamReader
using (StreamReader sr = new StreamReader(file))
{
    string line = sr.ReadLine();
}

// FileStream から StreamReader を作成
using (StreamReader sr = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
    string line = sr.ReadLine();
}
```

文字コード

文字列の読み込み・書き込みには文字コード(Encoding)を指定できる。

```csharp
string file = @"C:\work\test.txt";

// Fileを使ってUTF-8エンコーディングでファイルに書き込む
File.WriteAllText(file, "にほんご", Encoding.UTF8);

// StreamReaderをつかってShift-JIS(CP932)でファイルから読み込む
using (StreamReader sr = new StreamReader(file, Encoding.GetEncoding(932)))
{
    string line = sr.ReadLine();
}
```

## [ネットワーク (System.Net.*)](https://msdn.microsoft.com/ja-jp/library/mt481553(v=vs.110).aspx)

### TcpClient

TCPクライアント。ネットワークライブラリを作るでもない限り TCP を生で使うことはほとんどないと思うので紹介だけ。

```csharp
using (TcpClient tc = new TcpClient())
{
    tc.Connect("www.microsoft.com", 80);
    NetworkStream stream = tc.GetStream();

    string httpRequest = "GET / HTTP/1.0\r\nHost: www.microsoft.com\r\n\r\n";
    byte[] httpRequestData = Encoding.ASCII.GetBytes(httpRequest);
    stream.Write(httpRequestData, 0, httpRequestData.Length);

    using (var sr = new StreamReader(stream))
    {
        string httpResponse = sr.ReadToEnd();
        Console.WriteLine(httpResponse);
    }
}
```

### WebRequest

HTTPクライアント。HTTPを細かく弄る必要があるときは使うかも。WebRequest には HTTP を扱う HttpWebRequest だけでなく、FtpWebRequest や FileWebRequest なんかがあるけど、たぶん HTTP しか使わないでしょう。

```csharp
WebRequest req = WebRequest.Create("http://www.microsoft.com/");
using (WebResponse res = req.GetResponse())
using (Stream responseStream = res.GetResponseStream())
using (StreamReader responseReader = new StreamReader(responseStream))
{
    string response = responseReader.ReadToEnd();
    Console.WriteLine(response);
}

// WebRequest を使うときは Close() を忘れないように。
// ・WebResponse.Close()
// または、
// ・GetResponseStream() で取得した Stream の Close()
// ※両方 Close() しても問題はない。もちろんDispose()でもOK。
```

### WebClient

HTTPクライアントその２。コンテンツのダウンロード・アップロードしたい時は WebClient が楽。FTP も扱える。

```csharp
WebClient wc = new WebClient();
string content = wc.DownloadString("http://www.microsoft.com/");
Console.WriteLine(content);
```

WebClient の主要なメソッド

ダウンロード

- void DownloadFile(string address, string fileName)
- byte[] DownloadData(string address)
- string DownloadString(string address)
- Stream OpenRead(string address)

アップロード

- byte[] UploadData(string address, byte[] data)
- byte[] UploadFile(string address, string fileName)
- string UploadString(string address, string data)
- byte[] UploadValues(string address, NameValueCollection data)
- Stream OpenWrite(string address)

アップロードのみ、NameValueCollection オブジェクトを指定できる UploadValues() メソッドが用意されている点に注意。これはフォームデータを送信(POST)したいときに使う。

大きなコンテンツをダウンロードしたりアップロードしたりするときは、string や byte[] ではなく Stream で扱える OpenRead() や OpenWrite() を使うとよい。

### HttpClient

.NET 4.5 で追加された WebAPI に特化したクライアント。同時複数接続もサポートされている。(WebClient では同時接続できない)

```csharp
public async Task DownloadMicrosoft()
{
    HttpClient hc = new HttpClient();
    string html = await hc.GetStringAsync("http://www.micorosoft.com/");
    Console.WriteLine(html);
}
```

上記サンプルの GetStringAsync() の他、HTTP のメソッド GET, POST, PUT, DELETE に対応したメソッドがそれぞれ提供されている。

- GetAsync(string requestUri)
- PostAsync(string requestUri, HttpContent content)
- PutAsync(string requestUri, HttpContent content)
- DeleteAsync(string requestUri)

HttpClient は非同期 Task メソッドしか提供していないのが特徴。非同期IOについては次のセクションで説明。

## 非同期IO

IO 処理はデータサイズ、Diskアクセス速度、ネットワーク品質などに影響され、長い待ち時間が発生する場合がある。この間、スレッドは何もしない時間を過ごしてしまい、無駄。IO待ちの間はスレッドには別の処理をさせておくほうが効率がよい。これを実現するのが非同期処理。

.NET4.0以降、ほとんどの IO 関連メソッドには非同期バージョンのメソッドも提供されるようになっている。

### Task と async/await

非同期メソッドは Task クラスで実現されている。使い方：

- メソッドに async キーワードを付ける
- Asyncメソッド(またはTaskAsyncメソッド)を使う
- Asyncメソッドの前に await を付ける 

同期コードと非同期コードの比較

同期バージョン：ダウンロード処理中、スレッドが占有される。

```csharp
private void button1_Click(object sender, EventArgs e)
{
    WebClient wc = new WebClient();
    var html = wc.DownloadString("http://www.google.co.jp/");
    textBox1.Text = html;
}
```

非同期バージョン：ダウンロード処理中、スレッドが占有されない。

```csharp
private async void button1_Click(object sender, EventArgs e)
{
    WebClient wc = new WebClient();
    var html = await wc.DownloadStringTaskAsync("http://www.google.co.jp/");
    textBox1.Text = html;
}
```

### Stream と StreamReader/StreamWriter のサンプル

```csharp
public async Task TestAsyncIO()
{
    using (FileStream fs = new FileStream(@"C:\work\test", FileMode.Create))
    {
        byte[] buf = new byte[4096];
        await fs.WriteAsync(buf, 0, buf.Length);
    }
    
    using (StreamReader sr = new StreamReader(@"C:\work\test2"))
    {
        string text = await sr.ReadToEndAsync();
        Console.WriteLine(text);
    }

    using (StreamWriter sw = new StreamWriter(@"C:\work\test3"))
    {
        await sw.WriteLineAsync("test");
    }
}
```

### HttpClient で同時ダウンロード

非同期メソッドはこんな使い方もできる。

```csharp
public async Task Http()
{
    HttpClient hc = new HttpClient();
    Task<string> dl1 = hc.GetStringAsync("http://www.microsoft.com/");
    Task<string> dl2 = hc.GetStringAsync("https://www.microsoft.com/ja-jp/");
    Task<string> dl3 = hc.GetStringAsync("https://www.microsoft.com/en-us/");

    // すべてのダウンロード完了を待つ
    await Task.WhenAll(dl1, dl2, dl3);

    string html1 = await dl1;
    string html2 = await dl2;
    string html3 = await dl3;
}
```

## その他覚えておいた方がよさそうなキーワード
- ファイルシステム、レジストリ
- ファイルの属性（セキュリティ属性）
- FileMode, FileAccess, FileShare 列挙型



## リソース
- [ファイル システムとレジストリ (C# プログラミング ガイド)](https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/file-system/)

- [非同期プログラミング](https://docs.microsoft.com/ja-jp/dotnet/csharp/async)

## Quiz

1. 1GBのファイルがある。このファイルの 500,000,000 ～ 500,004,000 バイトのテキストを取得したい。どうやる？
2. WebRequest, WebClient, HttpClient, どんな時、どれを使う？
3. TcpClient, WebRequest のサンプルを非同期Taskメソッドを使うように書き換えろ。