【C# .NET10 Preview4】非同期 ZIP アーカイブ

# はじめに

[.NET10 Preview4](https://dotnet.microsoft.com/ja-jp/download/dotnet/10.0) で、ZIP アーカイブの非同期処理が追加されました。

[New async Zip APIs](https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview4/libraries.md#new-async-zip-apis)

```cs
var stream = new MemoryStream();
{
    await using var zip = await ZipArchive.CreateAsync(stream, ZipArchiveMode.Create, leaveOpen: true, entryNameEncoding: Encoding.UTF8);
    for (var n = 0; n < 5; n++)
    {
        var entry = zip.CreateEntry($"test{n}.txt");
        await using var entryStream = await entry.OpenAsync();
        using var writer = new StreamWriter(entryStream);
        writer.Write($"Hello, World! {n}");
    }
}

stream.Position = 0;
{
    await using var zip = await ZipArchive.CreateAsync(stream, ZipArchiveMode.Read, leaveOpen: false, entryNameEncoding: Encoding.UTF8);
    var got = new List<(string name, string content)>();
    foreach (var n in zip.Entries)
    {
        await using var entryStream = await n.OpenAsync();
        using var reader = new StreamReader(entryStream);
        var content = await reader.ReadToEndAsync();
        got.Add((n.FullName, content));
    }

    Assert.Equal([
        ("test0.txt","Hello, World! 0"),
        ("test1.txt","Hello, World! 1"),
        ("test2.txt","Hello, World! 2"),
        ("test3.txt","Hello, World! 3"),
        ("test4.txt","Hello, World! 4"),
    ], got);
}
```

## テストコード

<details>
<summary>テストコード</summary>

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

public class __NewAsyncZipAPIsTest
{
    [Fact]
    async Task HowToUse()
    {
        var stream = new MemoryStream();
        {
            await using var zip = await ZipArchive.CreateAsync(stream, ZipArchiveMode.Create, leaveOpen: true, entryNameEncoding: Encoding.UTF8);
            for (var n = 0; n < 5; n++)
            {
                var entry = zip.CreateEntry($"test{n}.txt");
                await using var entryStream = await entry.OpenAsync();
                using var writer = new StreamWriter(entryStream);
                writer.Write($"Hello, World! {n}");
            }
        }

        stream.Position = 0;
        {
            await using var zip = await ZipArchive.CreateAsync(stream, ZipArchiveMode.Read, leaveOpen: false, entryNameEncoding: Encoding.UTF8);
            var got = new List<(string name, string content)>();
            foreach (var n in zip.Entries)
            {
                await using var entryStream = await n.OpenAsync();
                using var reader = new StreamReader(entryStream);
                var content = await reader.ReadToEndAsync();
                got.Add((n.FullName, content));
            }

            Assert.Equal([
                ("test0.txt","Hello, World! 0"),
                ("test1.txt","Hello, World! 1"),
                ("test2.txt","Hello, World! 2"),
                ("test3.txt","Hello, World! 3"),
                ("test4.txt","Hello, World! 4"),
            ], got);
        }
    }

    async void HowToUse2()
    {
        // Extract a Zip archive
        await ZipFile.ExtractToDirectoryAsync("archive.zip", "destinationFolder", overwriteFiles: true);

        // Create a Zip archive
        await ZipFile.CreateFromDirectoryAsync("sourceFolder", "archive.zip", CompressionLevel.SmallestSize, includeBaseDirectory: true, entryNameEncoding: Encoding.UTF8);

        // Open an archive
        await using ZipArchive archive = await ZipFile.OpenReadAsync("archive.zip");
    }
}
```

</details>

## つかいどころ

例えば今までだと画面 UI が固まるのを避けるために `Task.Run()` で実行していた圧縮ファイル関係の処理を、素直に書けるようになるかもしれません。  
データ処理の部分をフロントに書くなという話なのですが、適当にコードを書くときはやったりします。  
ストリームリーダーも非同期メソッドが用意されているのでうまいこと非同期処理を活用して `Task.Run({...})` のネストをなくせば、コードが少し見やすくなるかもしれません。例外処理も簡単になります。

## おわりに

[ZipArchive は Preview1 でパフォーマンスが向上](https://qiita.com/abetakahiro123/items/2153df9795cd0f12da47#ziparchive-%E3%81%AE%E3%83%91%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%B3%E3%82%B9%E3%81%A8%E3%83%A1%E3%83%A2%E3%83%AA%E4%BD%BF%E7%94%A8%E9%87%8F%E3%81%AE%E6%94%B9%E5%96%84)しています。世の中的に使用頻度が高そうなので優先度も高そうです。  
Preview4 では [GZipStream のパフォーマンス向上](https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview4/libraries.md#performance-improvement-in-gzipstream-for-concatenated-streams) もあるようです。

[.NET10 Preview4](https://dotnet.microsoft.com/ja-jp/download/dotnet/10.0)

関連  
[【C#】.NET10 Preview1 ｷﾀ━━(ﾟ∀ﾟ)━━!!](https://qiita.com/abetakahiro123/items/2153df9795cd0f12da47)  
[【C# .NET10 Preview1】値型の配列をスタックに作成する最適化の検証](https://qiita.com/abetakahiro123/items/9113ee6347580c5d0e39)  
[【C# .NET10 Preview2】参照型がスタックに置かれる最適化](https://qiita.com/abetakahiro123/items/cedd77ba25de1b8db8d1)  
[【C# .NET10 Preview3】null 条件付き代入](https://qiita.com/abetakahiro123/items/db05ae0cab986183cfab)  
[【C# .NET10 Preview3】参照型の小さな配列のスタック割り当て](https://qiita.com/abetakahiro123/items/339412e890dfe6274941)  
[【C# .NET10 Preview3】拡張メソッドの機能追加](https://qiita.com/abetakahiro123/items/2599717fee4998774d5d)  
[【C# .NET10 Preview4】try-finally をインライン化する最適化の追加](https://qiita.com/abetakahiro123/items/5b8064c5d546f32f2c76)