首页 > 解决方案 > 修复“IDisposable”的此实现以符合处置模式

问题描述

我在这里有这个工作代码,它是下载一个有进度的文件。当我运行 SonarScanner 时,它抱怨我应该考虑

修复“IDisposable”的这个实现以符合处置模式。

public class HttpClientDownloadWithProgress : IDisposable
{
    private readonly string _downloadUrl;
    private readonly string _destinationFilePath;
    private readonly string _username;
    private readonly string _password;
    private HttpClient _httpClient;
    
    public delegate Task ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage, string fileName);

    public event ProgressChangedHandler ProgressChanged;

    public HttpClientDownloadWithProgress(string downloadUrl, string destinationFilePath, string username, string password)
    {
        _downloadUrl = downloadUrl;
        _destinationFilePath = destinationFilePath;
        _username = username;
        _password = password;
    }

    public async Task StartDownload()
    {            
        _httpClient = new HttpClient { Timeout = TimeSpan.FromDays(1) };

        var byteArray = Encoding.ASCII.GetBytes($"{_username}:{_password}");
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

        using (var response = await _httpClient.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) {
            await DownloadFileFromHttpResponseMessage(response);
        }
            
    }

    private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response)
    {
        response.EnsureSuccessStatusCode();

        var totalBytes = response.Content.Headers.ContentLength;

        using (var contentStream = await response.Content.ReadAsStreamAsync()) {
            await ProcessContentStream(totalBytes, contentStream);
        }
        
    }

    private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream)
    {
        var totalBytesRead = 0L;
        var readCount = 0L;
        var buffer = new byte[8192];
        var isMoreToRead = true;

        using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
        {
            do
            {
                var fileName = Path.GetFileName(_destinationFilePath);

                var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
                if (bytesRead == 0)
                {
                    isMoreToRead = false;
                    await TriggerProgressChanged(totalDownloadSize, totalBytesRead, fileName);
                    continue;
                }

                await fileStream.WriteAsync(buffer, 0, bytesRead);

                totalBytesRead += bytesRead;
                readCount += 1;

                if (readCount % 100 == 0) {
                    // change status to "DOWNLOADED"
                    await TriggerProgressChanged(totalDownloadSize, totalBytesRead, fileName);
                }
                // change status to "DOWNLOADING"
            }
            while (isMoreToRead);
        }
    }

    private async Task TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead, string fileName)
    {
        if (ProgressChanged == null)
            return;

        double? progressPercentage = null;
        if (totalDownloadSize.HasValue)
            progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2);

        await ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage, fileName);
    }

    public void Dispose()
    {
        _httpClient?.Dispose();
    }
}

我的Dispose()方法有什么问题?如何实现处置模式?

标签: c#

解决方案


一些较旧的 linter 喜欢这种模式:

  1. 实施IDisposable并将呼叫转发到Dispose(true)
  2. 实现一个转发到的终结器Dispose(false)
  3. 添加一个virtual protected void Dispose(bool disposing)方法,根据调用是来自终结器还是析构器来实际处理资源。

这确保了 GC 终结器线程有机会清除非托管资源(如果有)。

沿着这些思路

protected void Dispose(bool disposing)
{
    if(disposing){
       _httpClient?.Dispose();
       GC.SuppressFinalize(this); // no need to run the finalizer we have dispose everything we need
    }
   
}

public void Dispose()=>Disposing(true);

public ~HttpClientDownloadWithProgress()=>Disposing(false);
}

但是在您的情况下,继承是否会成为前进的方向是值得怀疑的。我宁愿组合 anHttpClient和 aProgress并让客户端存活更长时间,以便其他组合可以使用它。


推荐阅读