在C#开发中,从URL下载文件是经常会遇到的需求,无论是下载远程静态资源、同步第三方接口返回的文件,还是实现客户端自动更新功能,都需要用到对应的下载逻辑。目前主流的实现方式有两种,分别是使用WebClient类和HttpClient类,下面分别介绍两种方式的实现细节。

使用WebClient实现文件下载
WebClient是.NET框架中封装好的轻量级网络操作类,提供了简单的同步和异步下载方法,适合快速实现简单的文件下载需求。
同步下载实现
同步下载会阻塞当前线程直到下载完成,适合在控制台程序或者不需要考虑界面卡顿的场景使用。
using System;
using System.Net;
class Program
{
static void Main()
{
// 目标文件URL
string fileUrl = "https://ipipp.com/test/demo.zip";
// 本地保存路径
string savePath = @"D:downloaddemo.zip";
try
{
using (WebClient client = new WebClient())
{
// 执行同步下载
client.DownloadFile(fileUrl, savePath);
Console.WriteLine("文件下载完成,保存路径:" + savePath);
}
}
catch (Exception ex)
{
Console.WriteLine("下载失败,错误信息:" + ex.Message);
}
}
}
异步下载实现
异步下载不会阻塞当前线程,还可以通过事件获取下载进度,适合在WinForm、WPF等带有界面的程序中使用。
using System;
using System.Net;
class Program
{
static void Main()
{
string fileUrl = "https://ipipp.com/test/demo.zip";
string savePath = @"D:downloaddemo.zip";
using (WebClient client = new WebClient())
{
// 注册下载进度变化事件
client.DownloadProgressChanged += (sender, e) =>
{
Console.WriteLine($"下载进度:{e.ProgressPercentage}%");
};
// 注册下载完成事件
client.DownloadFileCompleted += (sender, e) =>
{
if (e.Error == null)
{
Console.WriteLine("文件下载完成,保存路径:" + savePath);
}
else
{
Console.WriteLine("下载失败,错误信息:" + e.Error.Message);
}
};
// 执行异步下载
client.DownloadFileAsync(new Uri(fileUrl), savePath);
// 防止主线程退出导致下载中断
Console.ReadLine();
}
}
}
使用HttpClient实现文件下载
HttpClient是.NET中更推荐使用的网络请求类,支持异步操作,更符合现代异步编程模式,也支持更灵活的配置,比如设置请求超时、添加请求头等。
基础异步下载实现
使用HttpClient下载文件需要手动处理响应流和本地文件流的写入,适合需要自定义下载逻辑的场景。
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string fileUrl = "https://ipipp.com/test/demo.zip";
string savePath = @"D:downloaddemo.zip";
try
{
using (HttpClient client = new HttpClient())
{
// 发送GET请求获取文件流
using (HttpResponseMessage response = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
// 获取响应流
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
using (FileStream fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
// 缓冲区大小设置为8KB
byte[] buffer = new byte[8192];
int bytesRead;
// 循环读取流并写入本地文件
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
}
}
}
}
Console.WriteLine("文件下载完成,保存路径:" + savePath);
}
catch (Exception ex)
{
Console.WriteLine("下载失败,错误信息:" + ex.Message);
}
}
}
带进度回调的下载实现
HttpClient本身没有内置的进度回调,需要手动计算已下载的字节数来统计进度,适合需要展示下载进度的场景。
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
// 定义进度回调委托
public delegate void ProgressCallback(long totalBytes, long downloadedBytes);
static async Task DownloadFileWithProgress(string fileUrl, string savePath, ProgressCallback progressCallback)
{
using (HttpClient client = new HttpClient())
{
// 先发送HEAD请求获取文件总大小,部分服务器可能不支持
long totalBytes = 0;
try
{
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Head, fileUrl))
using (HttpResponseMessage response = await client.SendAsync(request))
{
if (response.Content.Headers.ContentLength.HasValue)
{
totalBytes = response.Content.Headers.ContentLength.Value;
}
}
}
catch
{
// 获取不到总大小则进度按0计算
totalBytes = 0;
}
using (HttpResponseMessage response = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
using (Stream contentStream = await response.Content.ReadAsStreamAsync())
using (FileStream fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
byte[] buffer = new byte[8192];
int bytesRead;
long downloadedBytes = 0;
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
downloadedBytes += bytesRead;
// 触发进度回调
progressCallback?.Invoke(totalBytes, downloadedBytes);
}
}
}
}
}
static async Task Main()
{
string fileUrl = "https://ipipp.com/test/demo.zip";
string savePath = @"D:downloaddemo.zip";
try
{
await DownloadFileWithProgress(fileUrl, savePath, (total, downloaded) =>
{
if (total > 0)
{
double progress = (double)downloaded / total * 100;
Console.WriteLine($"下载进度:{progress:F2}%");
}
else
{
Console.WriteLine($"已下载:{downloaded / 1024}KB");
}
});
Console.WriteLine("文件下载完成,保存路径:" + savePath);
}
catch (Exception ex)
{
Console.WriteLine("下载失败,错误信息:" + ex.Message);
}
}
}
两种实现方式对比
下面从使用场景、灵活性、异步支持等方面对比WebClient和HttpClient的差异,方便开发者选择合适的方案。
| 对比项 | WebClient | HttpClient |
|---|---|---|
| 使用难度 | 低,封装了简单的方法,开箱即用 | 中等,需要手动处理流写入,代码量更多 |
| 异步支持 | 支持,基于事件异步模式 | 支持,基于Task的异步模式,更符合现代编程习惯 |
| 灵活性 | 低,自定义配置项少 | 高,可自定义请求头、超时时间、代理等配置 |
| 适用场景 | 简单下载需求、快速原型开发 | 复杂下载需求、需要自定义配置的场景、新项目开发 |
注意事项
- 下载前需要检查本地保存目录是否存在,不存在需要先创建,否则会抛出目录不存在的异常。
- 大文件下载时建议设置合适的缓冲区大小,避免占用过多内存,同时可以添加断点续传逻辑优化下载体验。
- 如果是下载HTTPS地址的文件,需要确保本地的证书配置正确,避免出现证书验证失败的异常。
- 下载完成后建议校验文件的大小或者哈希值,确保文件下载完整没有被篡改。
常见问题解答
下载时提示远程服务器返回错误404怎么办
首先检查URL是否正确,是否存在拼写错误,然后确认目标文件是否真的存在于服务器上,也可以在浏览器中直接访问该URL验证是否可访问。
下载大文件时内存占用过高怎么解决
不要一次性将整个文件流读取到内存中,而是使用缓冲区循环读取响应流并写入本地文件,就像上面的HttpClient示例代码那样,每次只读取固定大小的字节到内存中。
如何实现断点续传
可以在请求头中添加Range头,指定从哪个字节开始下载,同时本地文件使用追加写入的模式,具体实现需要根据服务器的支持情况调整。
C#URL下载文件HttpClientWebClient文件流操作修改时间:2026-06-11 15:33:30