Repository: dotnet/runtime
File: src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.Async.cs (C#)

Code before:
    /// Relative path information is interpreted as relative to the current working directory.</param>
    /// <param name="overwrite">True to indicate overwrite.</param>
    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
    /// <returns>A task that represents the asynchronous extract operation. The task completes when the entry contents have been written to the destination file.</returns>
    public static async Task ExtractToFileAsync(this ZipArchiveEntry source, string destinationFileName, bool overwrite, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        ExtractToFileInitialize(source, destinationFileName, overwrite, out FileStreamOptions fileStreamOptions);

        // When overwriting, extract to a temporary file first to avoid corrupting the destination file
        // if an exception occurs during extraction (e.g., password-protected archive, corrupted data).
        string extractPath = destinationFileName;
        string? tempPath = null;

        if (overwrite && File.Exists(destinationFileName))
        {
            string? directory = Path.GetDirectoryName(destinationFileName);
            if (string.IsNullOrEmpty(directory))
                directory = ".";
            tempPath = Path.Combine(directory, Path.GetRandomFileName());
            extractPath = tempPath;
        }

        try
        {
            FileStream fs = new FileStream(extractPath, fileStreamOptions);
            await using (fs)
            {
                Stream es = await source.OpenAsync(cancellationToken).ConfigureAwait(false);
                await using (es)
                {
                    await es.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
                }
            }

            // Move the temporary file to the destination only after successful extraction
            if (tempPath is not null)
            {
                File.Move(tempPath, destinationFileName, overwrite: true);
            }

            ExtractToFileFinalize(source, destinationFileName);
        }
        catch
        {
            // Clean up the temporary file if extraction failed
            if (tempPath is not null && File.Exists(tempPath))
            {
                // Ignore exceptions during cleanup; the original exception is more important
                try { File.Delete(tempPath); } catch (IOException) { } catch (UnauthorizedAccessException) { }

Review comment (suggestion):
@copilot+claude-opus-4.5, do fileStreamOptions include FileOptions.Asynchronous? They should for the async entrypoints.

Code after:
    /// Relative path information is interpreted as relative to the current working directory.</param>
    /// <param name="overwrite">True to indicate overwrite.</param>
    /// <param name="cancellationToken">The cancellation token to monitor for cancellation requests.</param>
    /// <returns>A task that represents the asynchronous extract operation. The task completes when the entry contents have been written to the destination file.</returns>
    public static async Task ExtractToFileAsync(this ZipArchiveEntry source, string destinationFileName, bool overwrite, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        ExtractToFileInitialize(source, destinationFileName, overwrite, useAsync: true, out FileStreamOptions fileStreamOptions);

        // When overwriting, extract to a temporary file first to avoid corrupting the destination file
        // if an exception occurs during extraction (e.g., password-protected archive, corrupted data).
        string extractPath = destinationFileName;
        string? tempPath = null;

        if (overwrite && File.Exists(destinationFileName))
        {
            // Use GetTempFileName for a unique temp file in the system temp directory.
            // This avoids conflicts and ensures cleanup by the OS if the process crashes.
            tempPath = Path.GetTempFileName();
            extractPath = tempPath;
        }

        try
        {
            FileStream fs = new FileStream(extractPath, fileStreamOptions);
            await using (fs.ConfigureAwait(false))
            {
                Stream es = await source.OpenAsync(cancellationToken).ConfigureAwait(false);
                await using (es.ConfigureAwait(false))
                {
                    await es.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
                }
            }

            // Move the temporary file to the destination only after successful extraction
            if (tempPath is not null)
            {
                File.Move(tempPath, destinationFileName, overwrite: true);
            }

            ExtractToFileFinalize(source, destinationFileName);
        }
        catch
        {
            // Clean up the temporary file if extraction failed
            if (tempPath is not null && File.Exists(tempPath))
            {
                // Ignore exceptions during cleanup; the original exception is more important
                try { File.Delete(tempPath); } catch { }
            }