惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
腾讯CDC
G
Google Developers Blog
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
美团技术团队
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志

博客园_首页

Linux实操--组管理、权限管理和定时任务 Java + EasyExcel 实现单个接口导出多个Excel Mem0 源码解析系列(二):提示词工程的深度剖析 Openclaw TaskFlow究竟是什么?和普通Skill技能有什么区别 博文阅读密码验证 - 博客园 嘉立创开源:应该是全网MicroPython教程最多的开发板 Hermes Agent 集成实践:从协议到生产 2026年AI编程工具横评:Cursor、Codex、Claude Code、Zed、Windsurf Java程序员必看的RAG入门教程 2026 AI效率神器:Superpowers + Claude Code 保姆级教程 本地大模型部署全攻略:从 0 到 1 玩转 Ollama 【从0到1构建一个ClaudeAgent】内存管理-上下文压缩 .NET 高级开发 | 设计、实现一个事件总线框架 电子小白入门之NE555 3. WorkBuddy:隐藏玩法,一键召唤专家,让 AI 以"专家身份"给你干活 和AI一起搞事情#3:Claude Teammate 游戏开发翻车实录 【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory C# .NET 周刊|2026年3月3期 我在 Debian 11 上把 K8s 单机搭起来了,过程没你想的那么顺(/opt 目录版) 深度学习进阶(七)Data-efficient Image Transformer CLI+Skill搭建浏览器AI自动化框架,告别一切重复枯燥任务 告别Token账单无底洞:OpenClaw本地部署,重塑企业数据主权的唯一解 FastAPI+Vue:文件分片上传+秒传+断点续传,这坑我帮你踩平了! SBTI 爆火后,我做了个程序员版的 CBTI。。已开源 + 附开发过程 多模态检索开始进入工程期:用 Sentence Transformers 搭建可落地的 Multimodal RAG 100多行代码实现一个最简单的Agent(用ReAct) Claude Code 通关手册(八):推荐 5 个 Hooks,代码质量提升 3 倍 老板:“有人截图了!”。安全部门:“收到,马上查暗水印!” - why技术 技术之外,皆是人间 C#/.NET/.NET Core技术前沿周刊 | 第 69 期(2026年4.01-4.12)
.NET 8 Web开发入门(二):C# 现代语法速成——为 Web API 量...
码农刚子 · 2026-05-08 · via 博客园_首页

一、前言:从“能写”到“写得好”

在上一篇文章中,我们成功运行了第一个控制台程序。如果你有其他语言的基础,可能会觉得C#的语法有些似曾相识。但在现代.NET 8的Web开发中,我们极少使用传统的“类继承”或“繁琐的属性封装”,而是大量使用语法糖新特性来降低代码量,提高安全性。

这一篇,我们将专注于三个核心领域:

  1. 数据载体:如何优雅地定义API的输入输出?
  2. 流程控制:如何处理耗时的数据库查询而不卡死服务器?
  3. 数据处理:如何像写SQL一样处理内存中的列表?

二、数据载体的革命:从 Class 到 Record

在Web API开发中,我们每天都在和“数据”打交道。前端发来JSON,我们解析成对象;数据库查出数据,我们封装成对象返回给前端。这些对象,通常被称为 DTO (Data Transfer Objects)

2.1 传统 Class 的痛点

在旧时代,定义一个用户数据类通常是这样的:

public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }

    // 为了比较值是否相等,可能还需要重写 Equals 和 GetHashCode...
}

这太繁琐了。为了解决定义冗长、且容易被意外修改的问题,C# 9 引入了 Record(记录类型)

2.2 使用 Record 定义不可变模型

在 Program.cs 中,你可以用一行代码定义一个强类型的模型:

public record User(int Id, string Name, string Email);

这就是全部。编译器会自动为你生成:

  • 构造函数
  • 只读属性(默认不可变,这对多线程环境非常安全)
  • EqualsGetHashCode 和 ToString 方法

实战演练: 让我们在一个控制台应用中测试 Record 的特性。

// 定义 Record
public record User(int Id, string Name, string Email);

class Program
{
    static void Main()
    {
        // 1. 构造对象
        var user1 = new User(1, "张三", "zhangsan@example.com");
        Console.WriteLine(user1); // 输出: User { Id = 1, Name = 张三, Email = zhangsan@example.com }

        // 2. 值相等性比较
        var user2 = new User(1, "张三", "zhangsan@example.com");
        // 即使是两个不同的对象实例,内容相同也被认为相等
        Console.WriteLine(user1 == user2); // 输出: True (这是 Class 做不到的)

        // 3. 不可变性与 with 表达式
        // user1.Name = "李四"; // 错误!Record 默认是只读的,无法直接修改
        
        // 如果需要修改,使用 with 创建一个副本
        var user3 = user1 with { Name = "李四" };
        Console.WriteLine(user3); // 输出: User { Id = 1, Name = 李四, Email = zhangsan@example.com }
    }
}

C# 传统Class定义与现代Record定义的代码量对比截图

【刚子提示】 在Web API中,Record是定义DTO的首选。它的不可变性保证了数据在传递过程中不被意外篡改,且其内置的值比较逻辑让单元测试和状态判断变得异常简单。

三、Web开发的生命线:异步编程 (Async/Await)

这是本篇最重要的章节。很多新手开发的网站在几个人访问时没问题,一旦并发量上来就卡死,原因往往是不懂异步编程。

3.1 为什么需要异步?

想象你在一家餐厅吃饭(服务器处理请求)。

  • 同步模式:服务员下单后,站在厨房门口等菜做好,期间不服务其他客人。如果厨房慢,餐厅效率极低。
  • 异步模式:服务员下单后,把单子给厨房,立即回去服务下一桌客人。等菜好了,厨房通知服务员端菜。

在Web开发中,“厨房”就是数据库、文件系统或外部API。如果使用同步代码访问数据库,服务器线程会被阻塞等待,资源被白白浪费。.NET通过 async 和 await 关键字优雅地解决了这个问题。

3.2 Task:未来的承诺

在.NET中,Task 或 Task 代表一个“正在进行”或“将来完成”的任务。

  • Task:代表一个没有返回值的异步操作(如写入文件)。
  • Task:代表一个将来会返回 T 类型结果的操作(如查询数据库)。

3.3 实战:模拟异步数据查询

我们通过模拟网络请求来体会异步的妙处。

using System;
using System.Diagnostics;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("开始处理异步请求...");
        
        var stopwatch = Stopwatch.StartNew();
        
        // 模拟同时查询三个独立的数据源
        // 如果是同步,总耗时 = 1秒 + 2秒 + 3秒 = 6秒
        // 如果是异步并发,总耗时 ≈ Max(1, 2, 3) = 3秒
        
        Task<string> dbTask = GetDataFromDbAsync();     // 耗时1秒
        Task<string> apiTask = CallExternalApiAsync();  // 耗时2秒
        Task<string> fileTask = ReadFileAsync();        // 耗时3秒

        // await 会“暂停”当前函数的执行,但释放线程给其他请求使用
        // 当所有任务完成时,继续向下执行
        await Task.WhenAll(dbTask, apiTask, fileTask);
        
        Console.WriteLine($"数据1: {dbTask.Result}");
        Console.WriteLine($"数据2: {apiTask.Result}");
        Console.WriteLine($"数据3: {fileTask.Result}");
        
        stopwatch.Stop();
        Console.WriteLine($"总耗时: {stopwatch.ElapsedMilliseconds} ms");
    }

    // 模拟异步方法:关键字 async
    static async Task<string> GetDataFromDbAsync()
    {
        await Task.Delay(1000); // 模拟I/O等待
        return "数据库数据";
    }

    static async Task<string> CallExternalApiAsync()
    {
        await Task.Delay(2000);
        return "API响应数据";
    }

    static async Task<string> ReadFileAsync()
    {
        await Task.Delay(3000);
        return "文件内容";
    }
}

同步阻塞与异步非阻塞处理流程的时间轴对比图

代码解析

  1. async 关键字:标记方法为异步方法,允许在内部使用 await
  2. await 关键字:这是核心。它告诉程序:“这里需要等待,你可以去处理别的事情,等结果出来了再回来继续执行下一行”。
  3. Task.WhenAll:用于并发执行多个任务。在Web开发中,如果你需要查询三个不相关的数据源,并发查询能大幅提升接口响应速度。

四、数据处理的利器:LINQ

在Web后端,我们经常需要对列表数据进行筛选、排序或转换。传统的 foreach 循环代码量大且易出错。LINQ (Language Integrated Query) 提供了一种声明式的方式来处理数据。

4.1 方法语法 vs 查询语法

LINQ有两种写法,但在现代.NET开发中,方法语法配合Lambda表达式更为流行。

假设我们有一个订单列表:

public record Order(int Id, string Product, decimal Price, bool IsPaid);

4.2 常见操作实战

让我们看看在开发中如何使用LINQ。

using System;
using System.Collections.Generic;
using System.Linq; // 必须引入这个命名空间

class Program
{
    static void Main()
    {
        var orders = new List<Order>
        {
            new Order(1, "笔记本电脑", 8000, true),
            new Order(2, "鼠标", 50, false),
            new Order(3, "键盘", 200, true),
            new Order(4, "显示器", 1500, true),
            new Order(5, "耳机", 300, false)
        };

        // 需求1:查找所有已支付的订单
        var paidOrders = orders.Where(o => o.IsPaid);

        // 需求2:查找金额大于1000的订单,并按金额降序排列
        var expensiveOrders = orders
            .Where(o => o.Price > 1000)
            .OrderByDescending(o => o.Price);

        // 需求3:获取所有订单的产品名称列表 (投影)
        var productNames = orders.Select(o => o.Product).ToList();

        // 需求4:聚合计算 - 计算已支付订单的总金额
        var totalPaid = orders
            .Where(o => o.IsPaid)
            .Sum(o => o.Price);

        // 需求5:分页查询 (常见于Web API)
        // 跳过前2条,取接下来的2条
        var page2Data = orders.Skip(2).Take(2);

        // 打印结果
        Console.WriteLine($"已支付订单总金额: {totalPaid}");
        Console.WriteLine($"第二页数据: {string.Join(", ", page2Data.Select(o => o.Product))}");
    }
}

核心解析

  • Where:过滤。相当于SQL的 WHERE
  • Select:转换/投影。将 Order 对象转换为 String(产品名)或其他DTO。
  • OrderBy/OrderByDescending:排序。
  • Skip/Take:分页神器。Skip((pageNumber - 1) * pageSize).Take(pageSize) 是标准的分页写法。

【刚子提示】 LINQ不仅用于内存中的List,它也是Entity Framework Core(数据库ORM)的基础。当你对数据库使用LINQ时,.NET会自动将LINQ翻译成SQL语句。这意味着你学会了LINQ,就同时掌握了内存数据处理和数据库查询两大利器。

五、安全网:可空引用类型 (NRT)

最后,我们要谈谈“安全”。在Web开发中,最令人头疼的Bug莫过于 NullReferenceException(空引用异常)。

从.NET 6开始,项目默认开启了可空引用类型特性。这虽然叫“类型”,其实是编译器的一个警告机制。

5.1 传统 null 的隐患

string name = null;
Console.WriteLine(name.Length); // 运行时崩溃!报错俗称“未将对象引用设置到对象的实例”

5.2 现代 NRT 写法

在 csproj 文件中,你通常能看到 <Nullable>enable</Nullable>。开启后,编译器会强迫你思考:这个变量会不会为空?

  • string:非空字符串。编译器假设它永远不为null,如果你赋值null会报警告。
  • string?:可为空字符串。编译器知道它可能是null。

防御式编程示例

void PrintLength(string? text)
{
    // 错误写法:编译器警告 text 可能是 null
    // Console.WriteLine(text.Length); 

    // 正确写法1:判空
    if (text != null)
    {
        Console.WriteLine(text.Length);
    }

    // 正确写法2:使用操作符 ?.
    // 如果 text 为 null,整个表达式返回 null,不会报错
    Console.WriteLine(text?.Length); 

    // 正确写法3:如果确定它一定不为空,使用 ! 操作符 (慎用)
    // 如果你违反规则传了null,仍会运行时报错
    // Console.WriteLine(text!.Length); 
}

对于新手,可能会觉得这些警告很烦。但请相信我,每一个警告都是在帮你消灭一个潜在的线上Bug。在Web API接收前端参数时,善用 string? 和 int? 可以帮你自动处理缺失字段的问题。

六、总结与下篇预告

在这篇3000字左右的文章中,我们完成了C#现代语法的突击特训。我们掌握了:

  • Record:定义简洁、不可变的数据模型。
  • Async/Await:避免线程阻塞,提升服务器并发能力。
  • LINQ:优雅地进行数据筛选、排序与分页。
  • NRT:利用编译器检查空引用,提升代码健壮性。

这四项技能,构成了Web API开发的“核心语法引擎”。

下一篇预告

虽然我们学会了语法,但代码都堆在 Program.cs 里显然不够专业。在第三篇文章中,我们将正式进入 ASP.NET Core Web API 的核心架构。我将带你解析 “依赖注入(DI)” 和 “中间件” 的奥秘。这是理解现代Web框架如何运转的关键一课,也是架构师思维转型的起点。

准备好,我们要开始构建真正的引擎了。

原文链接:.NET 8 Web开发入门(二):C# 现代语法速成——为 Web API 量身定制 - 码农刚子的开发笔记