C# 教程

C#(读作 C Sharp) 是微软推出的一种现代化、类型安全的编程语言,运行在 .NET 平台上。它语法优雅、功能强大,是 Windows 桌面、Web、游戏(Unity)、云服务开发的主力语言。

本教程内容

模块内容
基础语法变量、类型、运算符、判断、循环
面向对象类、继承、多态、接口、封装
高级特性泛型、委托、事件、反射、特性
实战文件 IO、正则、多线程、集合

为什么学 C#

  • 跨平台:.NET Core 支持 Windows/macOS/Linux
  • 游戏开发:Unity 官方脚本语言
  • 企业级:ASP.NET 后端、微服务、云原生
  • 语法现代:融合了多种语言的优点

Hello World

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Hello, C#!");
    }
}
✅ 建议
学习时建议安装 Visual Studio 或 VS Code + .NET SDK,边学边敲代码。本教程由浅入深,覆盖 C# 核心技术。

C# 简介

C# 由微软在 2000 年随 .NET Framework 一起发布,由 Anders Hejlsberg 领导设计。它是一门面向对象、类型安全、自动内存管理的现代语言。

核心特点

  • 面向对象:类、继承、多态、接口一应俱全
  • 类型安全:编译期检查,减少运行时错误
  • 自动 GC:垃圾回收,无需手动管理内存
  • 语言集成查询(LINQ):优雅地查询集合
  • 异步编程:async/await 简洁处理并发

C# 与 .NET 的关系

C# 是语言(语法), .NET 是平台(运行时+类库)

C# 代码 → 编译为 IL(中间语言) → 运行时(JIT)执行

.NET 版本演进:
.NET Framework(Windows) → .NET Core(跨平台) → .NET 5/6/7/8(统一)

C# 能做什么

领域技术栈
桌面应用WPF、WinForms
Web 后端ASP.NET Core
游戏Unity
移动应用Xamarin / MAUI
云服务Azure Functions、微服务

开发环境

# 安装 .NET SDK 后验证
dotnet --version
dotnet new console -n Hello   # 创建项目
cd Hello
dotnet run                    # 运行
💡 提示
C# 与 Java 语法相似(都是 C 系),学过 Java 或 C++ 会非常容易上手。区别在于 C# 更现代、特性更丰富。

C# 开发环境

搭建 C# 开发环境:安装 .NET SDK + 选择 IDE,创建并运行第一个项目。

1. 安装 .NET SDK

# Windows/macOS/Linux 都支持
# 到 dotnet.microsoft.com 下载 SDK(LTS 版本)
# 或包管理器安装(macOS):
brew install dotnet-sdk

# 验证
dotnet --version        # 如 8.0.100

2. 选择 IDE

工具适合
Visual StudioWindows 全功能 IDE(推荐)
VS Code轻量 + C# 扩展,跨平台
JetBrains Rider专业级,跨平台

3. 创建控制台项目

dotnet new console -n MyApp
cd MyApp
code .          # VS Code 打开

# 项目结构
# MyApp/
# ├── MyApp.csproj     项目配置
# ├── Program.cs       入口文件
# └── obj/            编译产物

4. 编写并运行

// Program.cs
Console.WriteLine("你好,世界!");
Console.WriteLine($"今天: {DateTime.Now:yyyy-MM-dd}");
dotnet run
# 输出:
# 你好,世界!
# 今天: 2026-08-17

常用命令

dotnet new console     # 新建控制台项目
dotnet new webapi      # 新建 Web API 项目
dotnet add package X   # 安装 NuGet 包
dotnet build           # 编译
dotnet run             # 运行
dotnet test            # 测试
⚠️ 注意
VS Code 需要安装 C# Dev Kit 扩展才有智能提示与调试功能。首次 `dotnet run` 会下载依赖,需联网。

C# 程序结构

理解 C# 程序的基本结构:命名空间、类、方法、入口点。

程序骨架

using System;                 // 1. 引用命名空间

namespace MyApp               // 2. 定义命名空间
{
    class Program             // 3. 定义类
    {
        static void Main()    // 4. 入口方法(程序从这里开始)
        {
            Console.WriteLine("Hello");   // 5. 业务代码
        }
    }
}

各部分作用

部分说明
using System;引入命名空间,可用 Console 等类型
namespace MyApp代码的组织容器,避免命名冲突
class Program类:数据和行为的封装
Main()程序入口,必须为 static

简化写法(.NET 6+)

// 顶级语句: 省略 namespace/class/Main
using System;

Console.WriteLine("Hello");
Console.ReadLine();   // 等待输入(防止窗口闪退)

输入与输出

// 输出
Console.WriteLine("带换行输出");
Console.Write("不换行输出");

// 输入
string name = Console.ReadLine();      // 读一行字符串
int age = int.Parse(Console.ReadLine());  // 读数字

Console.WriteLine($"你好, {name}, 年龄 {age}");

编译与运行

C# 源码(.cs) → 编译 → IL 中间语言 → 运行时 JIT → 机器码
dotnet build  编译
dotnet run    编译并运行
✅ 记忆
程序结构四要素:using(引用)、namespace(容器)、class(类型)、Main(入口)。顶层语句是新版本对样板代码的简化。

C# 基本语法

C# 的基本语法元素:标识符、关键字、注释、语句、块。

标识符与关键字

// 标识符: 变量/方法/类名(字母、数字、下划线, 不能数字开头)
int myVariable = 10;
string UserName = "张三";

// 关键字: 语言保留字, 不能用作标识符
// class, int, string, if, for, using, public, static... 

// @ 前缀可把关键字当标识符(不推荐)
string @class = "测试";

注释

// 单行注释

/* 多行
   注释 */

/// <summary>XML 文档注释(生成文档用)</summary>
public void Method() { }

变量声明与初始化

// 显式类型
int count = 100;
double price = 99.5;
string name = "C#";

// var: 类型推断(编译期确定)
var age = 25;        // int
var city = "北京";   // string

// 常量与只读
const double PI = 3.14159;   // 编译期常量
readonly int MaxSize = 100;  // 运行时赋值一次

语句与块

// 语句以分号结尾
int x = 1;

// 块: 花括号包裹的语句组
if (x > 0)
{
    Console.WriteLine("正数");
}

// 表达式语句: 赋值、调用
x = x + 1;
Console.WriteLine(x);

命名规范

类型规范示例
类/方法PascalCaseStudentService
变量/参数camelCaseuserName
常量PascalCaseMaxCount
接口I + PascalCaseIComparable
✅ 提示
C# 区分大小写(Name ≠ name)。好的命名比注释更重要——用名字表达意图。

C# 数据类型

C# 数据类型分为值类型与引用类型两大体系。

值类型(存值本身)

// 整数
byte b = 255;            // 0~255
short s = -32768;        // 16位
int i = -2147483648;     // 32位(常用)
long l = 9000000000L;    // 64位

// 浮点
float f = 3.14f;         // 32位(需 f 后缀)
double d = 3.1415926535; // 64位(默认)

// 其他
decimal money = 99.99m;  // 高精度十进制(钱, 用 m)
char c = 'A';            // 单字符
bool ok = true;          // 布尔

// 结构体/枚举也是值类型

引用类型(存引用地址)

string name = "Hello";   // 字符串
int[] arr = { 1, 2, 3 }; // 数组
object obj = 42;         // 所有类型的基类

class Person { }         // 自定义类
Person p = new Person(); // 引用类型实例

// 引用类型: 变量存的是堆上对象的地址
// 赋值时共享同一对象
Person p1 = new Person();
Person p2 = p1;          // p1、p2 指向同一对象

值类型 vs 引用类型

维度值类型引用类型
存储栈(直接存值)堆(存地址)
赋值复制值复制引用
默认值0/false/null?null
例子int、double、structstring、class、array

常用类型速查

int.MaxValue        // 2147483647
double.MaxValue     // 1.7976931348623157E+308
string.Length       // 字符串长度

// 类型转换见下一章
⚠️ 注意
decimal 适合金额(精确),double 适合科学计算(有精度误差)。整数运算不会自动转浮点,需注意除法。

C# 类型转换

C# 类型转换:隐式转换、显式转换、Convert 类、Parse/TryParse。

隐式转换(自动)

// 小范围 → 大范围, 安全自动转
int i = 100;
long l = i;            // int → long ✅
double d = i;          // int → double ✅
float f = 3.5f;
double d2 = f;         // float → double ✅

显式转换(强制)

// 大范围 → 小范围, 可能丢失精度, 需强转
double d = 3.99;
int i = (int)d;        // 3 (截断小数)

long l = 1000000L;
int i2 = (int)l;       // 若超出范围会溢出

// 数值转字符串
string s = i.ToString();

Convert 类(推荐)

// 字符串 → 数值(四舍五入)
string str = "3.7";
double d = Convert.ToDouble(str);
int rounded = Convert.ToInt32(3.7);   // 4 (四舍五入)

// 其他
Convert.ToInt32("100");      // "100" → 100
Convert.ToString(123);       // 123 → "123"
Convert.ToBoolean("true");   // → true
Convert.ToDateTime("2026-08-17");

Parse / TryParse(字符串→数值)

// Parse: 失败抛异常
int a = int.Parse("42");
double b = double.Parse("3.14");

// TryParse: 失败返回 false, 不抛异常(推荐!)
string input = Console.ReadLine();
if (int.TryParse(input, out int result))
{
    Console.WriteLine($"解析成功: {result}");
}
else
{
    Console.WriteLine("输入不是数字");
}

装箱与拆箱

// 值类型 → object(装箱): 分配堆内存
int num = 42;
object boxed = num;          // 装箱

// object → 值类型(拆箱): 需强转
int unboxed = (int)boxed;    // 拆箱

// 泛型可避免装箱(性能更好)
✅ 最佳实践
解析用户输入一律用 TryParse;数值与字符串互转用 ToString() / Parse;可能丢精度的强转要明确知道后果。

C# 变量

深入 C# 变量:声明、初始化、生命周期、var 推断。

变量声明

// 声明 + 初始化
int score = 85;

// 先声明后赋值
int total;
total = 100;

// 多个变量一行
int a = 1, b = 2, c = 3;

// var 类型推断
var message = "Hello";      // string
var numbers = new int[3];   // int[]

变量分类

类型位置生命周期
局部变量方法/块内块执行期间
实例字段类中, 非static随对象创建/销毁
静态字段类中, static整个程序生命周期
常量const编译期固定

示例

class Student
{
    // 实例字段: 每个对象一份
    public string Name;

    // 静态字段: 所有对象共享
    public static int Count = 0;

    public void SayHello()
    {
        // 局部变量: 方法内有效
        string greeting = $"你好, {Name}";
        Console.WriteLine(greeting);
        Count++;    // 访问静态字段
    }
}

变量命名

// 规则
// 1. 字母/数字/下划线, 不能数字开头
// 2. 区分大小写
// 3. 不能是关键字
// 4. 局部变量 camelCase, 字段 PascalCase/_camelCase

int age;          // ✅
int _age;         // ✅ (私有字段习惯)
int 2age;         // ❌ 数字开头
string class;     // ❌ 关键字

变量的默认值

// 字段自动有默认值
int n;        // 0
double d;     // 0
bool b;       // false
string s;     // null
int[] arr;    // null

// 局部变量必须显式赋值才能使用
int x;
// Console.WriteLine(x);   // ❌ 编译错误: 未赋值
✅ 提示
C# 是「明确赋值」语言:局部变量不赋值不让用,这避免了大量「用未初始化变量」的 bug。

C# 变量作用域

作用域(Scope)决定变量在哪里可见可用。C# 按块(花括号)划分作用域。

块作用域

public void Demo()
{
    int x = 10;              // 方法级作用域

    if (x > 0)
    {
        int y = 20;          // if 块内作用域
        Console.WriteLine(x + y);   // ✅ 可见
    }
    // Console.WriteLine(y); // ❌ y 超出作用域
    Console.WriteLine(x);    // ✅ 仍可见
}

循环作用域

for (int i = 0; i < 10; i++)
{
    // i 在循环内可见
    Console.WriteLine(i);
}
// Console.WriteLine(i);  // ❌ i 不存在

// 在循环外声明索引
int idx;
for (idx = 0; idx < 10; idx++) { }
Console.WriteLine(idx);    // ✅ 10

遮蔽与优先级

class Demo
{
    int value = 100;        // 字段

    public void Test()
    {
        int value = 5;      // 局部变量遮蔽字段
        Console.WriteLine(value);   // 5 (局部优先)

        // 访问字段: 用 this
        Console.WriteLine(this.value);  // 100
    }
}

静态字段作用域

class Counter
{
    public static int Count = 0;    // 全局共享

    public void Inc() { Count++; }
}

// 任何地方都可访问
Counter.Count++;
Console.WriteLine(Counter.Count);

作用域规则速查

规则说明
块内声明只在块内及嵌套块内可见
同层不可重复声明同名变量在同一作用域报错
嵌套可见内层可访问外层变量
局部遮蔽局部优先于字段(用 this 取字段)
✅ 原则
变量作用域越小越好:就近声明、用完即弃,减少耦合与误用。这是代码整洁的基础。

C# 常量

C# 常量:const(编译期)与 readonly(运行时)。

const 常量

// const: 编译期就确定, 必须用字面量初始化
public const double PI = 3.1415926;
public const int MaxUsers = 1000;
public const string Version = "1.0.0";

// 使用
double area = PI * r * r;
Console.WriteLine($"版本: {Version}");

readonly 字段

class Config
{
    // readonly: 运行时赋值, 且只能在构造函数或声明时
    public readonly string DbConnection;
    public readonly int Timeout = 30;    // 声明时赋值

    public Config(string conn)
    {
        DbConnection = conn;    // 构造函数中赋值
    }

    // 之后不可再改
    // public void Set() { DbConnection = "x"; }  // ❌
}

const vs readonly

维度constreadonly
赋值时机编译期(字面量)声明或构造函数
类型限制内置类型(数值/字符串)任意类型
实例相关隐含 static可实例或 static
场景数学常量、固定值配置、依赖注入

静态常量组(建议)

public static class AppConfig
{
    public const string AppName = "知识库";
    public const int PageSize = 20;
    public static readonly string UploadPath =
        Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "uploads");
}

// 使用
Console.WriteLine(AppConfig.AppName);
Console.WriteLine(AppConfig.PageSize);

注意事项

// 1. const 必须编译期可计算
// const int X = DateTime.Now.Year;   // ❌ 运行时值

// 2. const 编译期替换(引用处直接嵌入值)
//    改 const 需要重新编译所有引用它的程序集

// 3. 字段与局部都用 const
const int DaysPerWeek = 7;
int weeks = 10;
Console.WriteLine(DaysPerWeek * weeks);
✅ 最佳实践
魔法数字(0、100、3600)全部提取为常量或枚举;「配置类」用 static class + const/readonly 统一管理。

C# 运算符

C# 运算符:算术、赋值、比较、逻辑、位、三元、空合并。

算术运算符

int a = 10, b = 3;
a + b;   // 13  加
a - b;   // 7   减
a * b;   // 30  乘
a / b;   // 3   (整数除法, 截断!)
a % b;   // 1   取余

double c = 10.0 / 3;   // 3.333... (浮点除法)
a++;     // 11 自增
b--;     // 2  自减

赋值运算符

int x = 5;
x += 3;    // x = x + 3  → 8
x -= 2;    // 6
x *= 2;    // 12
x /= 4;    // 3
x %= 2;    // 1

比较与逻辑

int age = 20;
age > 18;            // true  大于
age >= 18;           // true
age == 18;           // false 等于
age != 18;           // true  不等于

bool isStudent = true;
bool isVip = false;
isStudent && isVip;   // false 与
isStudent || isVip;   // true  或
!isStudent;           // false 非

三元运算符

int score = 85;
string result = score >= 60 ? "及格" : "不及格";
// 条件 ? 真值 : 假值

位运算符

int flags = 0b0101;
int mask = 0b0011;
flags & mask;      // 0b0001 与
flags | mask;      // 0b0111 或
flags ^ mask;      // 0b0110 异或
~flags;            // 取反
flags << 1;        // 左移 → 0b1010
flags >> 1;        // 右移 → 0b0010

空合并运算符

string name = null;
string display = name ?? "匿名用户";   // "匿名用户"
// ?? : 左边为 null 时用右边

// 空条件运算符
string s = "hello";
int? len = s?.Length;     // s 为 null 则整体 null

// 组合
string config = GetConfig() ?? GetDefault() ?? "fallback";

运算符优先级(从高到低)

() → ++ -- → * / % → + - → << >> → 比较 → == != → & → ^ → | → && → || → ?: → 赋值
不确定时用括号!
✅ 注意
整数除法会截断小数;&&/|| 短路(左侧已定则右侧不执行);?? 是处理 null 的利器。

C# 判断

C# 判断语句:if/else、switch、三元,控制程序分支。

if / else if / else

int score = 85;

if (score >= 90)
{
    Console.WriteLine("优秀");
}
else if (score >= 60)
{
    Console.WriteLine("及格");
}
else
{
    Console.WriteLine("不及格");
}

嵌套与简化

// 嵌套(可读性差, 尽量避免过深)
bool isVip = true;
int amount = 500;

if (isVip)
{
    if (amount > 300)
    {
        Console.WriteLine("VIP 大客户");
    }
}

// 合并条件
if (isVip && amount > 300)
{
    Console.WriteLine("VIP 大客户");
}

// 单语句可省略花括号(建议保留)
if (score >= 60) Console.WriteLine("通过");

switch 语句

string status = "pending";

switch (status)
{
    case "pending":
        Console.WriteLine("待处理");
        break;
    case "approved":
        Console.WriteLine("已批准");
        break;
    case "rejected":
        Console.WriteLine("已驳回");
        break;
    default:
        Console.WriteLine("未知状态");
        break;
}

switch 表达式(C# 8+)

// 更简洁的写法
string status = "done";
string text = status switch
{
    "pending" => "待处理",
    "done" => "已完成",
    "failed" => "失败",
    _ => "未知"          // default
};
Console.WriteLine(text);   // 已完成

模式匹配

// 类型模式
object obj = 42;
if (obj is int number)
{
    Console.WriteLine($"是整数: {number}");
}

// 属性模式
var user = new { Name = "张三", Age = 25 };
if (user is { Age: >= 18 and < 60 })
{
    Console.WriteLine("成年且未退休");
}

// 关系模式
int score = 85;
string grade = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    _ => "D"
};
✅ 建议
分支多用 switch 表达式(简洁、无 break);条件复杂用「合并 + 提前 return」减少嵌套层级。

C# 循环

C# 循环:for、foreach、while、do-while 以及 break/continue。

for 循环

// 打印 1~5
for (int i = 1; i <= 5; i++)
{
    Console.WriteLine(i);
}

// 递减
for (int i = 10; i > 0; i -= 2)
{
    Console.WriteLine(i);   // 10 8 6 4 2
}

// 循环数组
int[] nums = { 10, 20, 30 };
for (int i = 0; i < nums.Length; i++)
{
    Console.WriteLine(nums[i]);
}

foreach 循环(遍历集合)

// 数组
foreach (int n in nums)
{
    Console.WriteLine(n);
}

// 字符串(字符遍历)
foreach (char c in "hello")
{
    Console.WriteLine(c);
}

// 字典
var dict = new Dictionary
{
    ["apple"] = 5,
    ["banana"] = 3
};
foreach (var kv in dict)
{
    Console.WriteLine($"{kv.Key}: {kv.Value}");
}

while 循环

int n = 0;
while (n < 5)
{
    Console.WriteLine(n);
    n++;
}

// 读取输入直到 q
string input = "";
while (input != "q")
{
    input = Console.ReadLine();
    Console.WriteLine($"输入了: {input}");
}

do-while(至少执行一次)

int guess;
do
{
    Console.Write("猜数字(1-10): ");
    guess = int.Parse(Console.ReadLine());
} while (guess != 7);
Console.WriteLine("猜对了!");

break / continue

// break: 跳出整个循环
for (int i = 1; i <= 10; i++)
{
    if (i == 5) break;
    Console.WriteLine(i);       // 1 2 3 4
}

// continue: 跳过本次
for (int i = 1; i <= 10; i++)
{
    if (i % 2 == 0) continue;   // 跳过偶数
    Console.WriteLine(i);       // 1 3 5 7 9
}

// 嵌套循环 break 只跳出内层
for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3; j++)
    {
        if (j == 1) break;      // 只跳出 j 循环
        Console.Write($"{i}{j} ");
    }
}

循环场景速查

场景推荐
知道次数for
遍历集合元素foreach
不确定次数while
至少执行一次do-while
✅ 注意
foreach 中不能修改集合(Add/Remove 会抛异常);需要索引或修改时用 for。

C# 封装

封装(Encapsulation):把数据与操作数据的方法捆绑在类中,并用访问修饰符控制外部可见性——面向对象三大特性之一。

访问修饰符

修饰符可见范围
public任何地方
private仅本类
protected本类 + 派生类
internal同一程序集
protected internal本程序集或派生类

封装示例

public class BankAccount
{
    // 私有字段: 外部不能直接访问
    private decimal _balance;
    private readonly string _accountNo;

    // 构造
    public BankAccount(string accountNo)
    {
        _accountNo = accountNo;
        _balance = 0;
    }

    // 公开方法: 受控的修改入口
    public void Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("金额必须大于0");
        _balance += amount;
    }

    public bool Withdraw(decimal amount)
    {
        if (amount > _balance)
            return false;          // 余额不足
        _balance -= amount;
        return true;
    }

    // 只读属性
    public decimal Balance => _balance;
}

// 使用
var acc = new BankAccount("6222-0001");
acc.Deposit(1000);
bool ok = acc.Withdraw(300);
Console.WriteLine(acc.Balance);   // 700
// acc._balance = 9999;           // ❌ 私有不可访问

属性(Property)封装

public class Person
{
    private string _name = "";

    // 属性: 读写的封装
    public string Name
    {
        get { return _name; }
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("姓名不能为空");
            _name = value;
        }
    }

    // 自动属性(简化)
    public int Age { get; set; }

    // 只读计算属性
    public bool IsAdult => Age >= 18;
}

为什么要封装

  • 保护数据:防止非法状态(如负余额)
  • 隐藏实现:内部改了,外部调用不变
  • 控制入口:加校验、日志、权限
✅ 原则
字段默认 private,需要对外暴露时才加属性;get 尽量保留(读取更安全),写操作谨慎开放。

C# 方法

C# 方法:定义、参数传递、重载、可选参数。

方法定义

// 返回类型 方法名(参数) { 主体 }
public int Add(int a, int b)
{
    return a + b;
}

// 无返回值
public void Print(string msg)
{
    Console.WriteLine(msg);
}

// 调用
int sum = Add(3, 4);
Print($"和是 {sum}");

参数传递

// 值传递(默认): 修改不影响原变量
void Change(int x) { x = 100; }
int a = 5;
Change(a);
Console.WriteLine(a);    // 5 (不变)

// ref: 引用传递, 可修改原变量
void ChangeRef(ref int x) { x = 100; }
int b = 5;
ChangeRef(ref b);
Console.WriteLine(b);    // 100 (已改)

// out: 输出参数, 必须赋值
bool TryDivide(int a, int b, out int result)
{
    if (b == 0) { result = 0; return false; }
    result = a / b;
    return true;
}
if (TryDivide(10, 3, out int r))
    Console.WriteLine(r);   // 3

方法重载

// 同名方法, 不同参数列表
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
public int Add(int a, int b, int c) => a + b + c;

Console.WriteLine(Add(1, 2));        // 3
Console.WriteLine(Add(1.5, 2.5));    // 4.0
Console.WriteLine(Add(1, 2, 3));     // 6

可选参数与命名参数

// 可选参数: 必须放在末尾
public void SendEmail(string to, string subject = "通知",
                      bool html = true)
{
    // ...
}

SendEmail("a@b.com");                       // 用默认值
SendEmail("a@b.com", "重要通知");           // 指定subject
SendEmail("a@b.com", html: false);          // 命名参数

// params: 可变参数
public int Sum(params int[] numbers)
{
    return numbers.Sum();
}
Sum(1, 2, 3, 4);        // 10
Sum();                  // 0

表达式体方法(简洁)

public int Square(int x) => x * x;
public void Greet() => Console.WriteLine("你好");
public string FullName => $"{FirstName} {LastName}";
✅ 最佳实践
方法职责单一(一个方法做一件事);参数不超过 3-4 个(多则封装成类);用 out 表示「可能失败的尝试」。

C# 可空类型

C# 可空类型:Nullable<T>、? 后缀、空值处理。

可空值类型

// 值类型默认不能为 null, 加 ? 后可以
int? age = null;          // 可空 int
double? price = 99.5;     // 可空 double
bool? isActive = null;    // 可空 bool(三态: true/false/null)

// Nullable 等价写法
Nullable n = 5;

常用操作

int? score = 85;

// 判断是否有值
if (score.HasValue)
{
    Console.WriteLine(score.Value);    // 85
}

// 简化
if (score is int v)
{
    Console.WriteLine(v);
}

// 取值: 有值取之, 无值取默认
int result = score.GetValueOrDefault();     // 85
int result2 = score.GetValueOrDefault(0);   // 无值则 0

// ?? 空合并
int final = score ?? 0;    // 无值用 0

可空引用类型(C# 8+)

// 在 csproj 启用: enable
// 之后编译器会警告可能为 null 的使用

string name = null;        // 警告: 非空声明赋了 null
string? maybe = null;      // ✅ 明确可空

// 使用可空引用
string? userInput = Console.ReadLine();
int len = userInput!.Length;   // !: 告诉编译器"我确定非空"
// 或安全处理:
if (userInput is not null)
{
    Console.WriteLine(userInput.Length);
}

可空与数据库/表单

// 数据库字段 NULL → 可空类型
public class User
{
    public int Id { get; set; }          // NOT NULL
    public string? Nickname { get; set; } // 可为 NULL
    public DateTime? LastLogin { get; set; } // 可为 NULL
}

// 使用
User u = GetUser();
if (u.LastLogin.HasValue)
{
    Console.WriteLine($"上次登录: {u.LastLogin.Value:yyyy-MM-dd}");
}

注意事项

// 不要对 null 调用 .Value (会抛异常)
int? x = null;
// int v = x.Value;      // ❌ InvalidOperationException
// 先判 HasValue 或用 ??

// 可空类型参与运算
int? a = 10, b = null;
int? sum = a + b;          // null (任一为 null 结果 null)
✅ 核心
「可空」= 明确表达「这里可能没有值」。配合 ??、?.、GetValueOrDefault 优雅处理空值,避免 NRE。

C# 数组(Array)

C# 数组:一维、多维、交错数组以及常用方法。

一维数组

// 声明方式
int[] arr1 = new int[5];          // 长度5, 默认0
int[] arr2 = { 1, 2, 3 };         // 初始化
int[] arr3 = new int[] { 4, 5, 6 };
string[] names = { "张三", "李四" };

// 访问
Console.WriteLine(arr2[0]);       // 1
arr2[2] = 30;                     // 修改
Console.WriteLine(arr2.Length);   // 3

// 遍历
foreach (int n in arr2) { Console.WriteLine(n); }
for (int i = 0; i < arr2.Length; i++) { }

常用方法

using System.Linq;

int[] nums = { 5, 3, 8, 1, 9 };

Array.Sort(nums);        // 排序 [1,3,5,8,9]
Array.Reverse(nums);     // 反转
Array.IndexOf(nums, 8);  // 找下标(找不到 -1)

nums.Sum();              // 26   (LINQ)
nums.Max();              // 9
nums.Min();              // 1
nums.Average();          // 5.2
nums.Where(n => n > 3);  // [5,8,9] (筛选)
nums.OrderBy(n => n);    // 排序(不修改原数组)

多维数组

// 矩形多维数组
int[,] matrix = new int[2, 3]
{
    { 1, 2, 3 },
    { 4, 5, 6 }
};
Console.WriteLine(matrix[1, 2]);   // 6
Console.WriteLine(matrix.GetLength(0));  // 2 (行)
Console.WriteLine(matrix.GetLength(1));  // 3 (列)

// 遍历
for (int i = 0; i < matrix.GetLength(0); i++)
    for (int j = 0; j < matrix.GetLength(1); j++)
        Console.Write(matrix[i, j] + " ");

交错数组(数组的数组)

int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 3, 4, 5, 6 };
jagged[2] = new int[] { 7 };

Console.WriteLine(jagged[1][3]);   // 6 (行可不同长度)

foreach (var row in jagged)
{
    foreach (var v in row)
        Console.Write(v + " ");
    Console.WriteLine();
}

复制与克隆

int[] original = { 1, 2, 3 };

int[] copy = (int[])original.Clone();   // 值复制
copy[0] = 99;
Console.WriteLine(original[0]);         // 1 (不受影响)

// 部分复制
int[] part = new int[2];
Array.Copy(original, 1, part, 0, 2);    // [2,3]
✅ 注意
数组长度固定;需要动态增删用 List<T>。数组是引用类型:直接赋值是共享引用,要复制数据用 Clone()。

C# 字符串(String)

C# 字符串:不可变、常用方法、格式化、StringBuilder。

字符串基础

string s1 = "Hello";
string s2 = @"C:\path	oile";    // 原义字符串(不转义)
string s3 = $"{s1} World";          // 字符串插值

// 字符串不可变: 每次修改都生成新对象
string a = "Hello";
string b = a + " World";    // 新字符串
string c = a.ToUpper();     // 新字符串
Console.WriteLine(a);       // Hello (原样)

常用方法

string text = "Hello, World";

text.Length;                    // 12
text.ToUpper();                 // "HELLO, WORLD"
text.ToLower();                 // "hello, world"
text.Trim();                    // 去首尾空格
text.StartsWith("Hello");       // true
text.EndsWith("d");             // true
text.Contains("World");         // true
text.IndexOf("World");          // 7
text.Substring(7);              // "World"
text.Substring(0, 5);           // "Hello"
text.Replace("World", "C#");    // "Hello, C#"
text.Split(',');                // ["Hello", " World"]
string.Join("-", "a", "b", "c");  // "a-b-c"

字符串比较与判空

string x = "abc";
string y = "ABC";

x == y;                        // false (区分大小写)
x.Equals(y, StringComparison.OrdinalIgnoreCase);  // true

// 判空(推荐)
string? s = null;
string.IsNullOrEmpty(s);       // true
string.IsNullOrWhiteSpace("  ");  // true

// 可空字符串使用
string? name = Console.ReadLine();
if (string.IsNullOrWhiteSpace(name))
{
    Console.WriteLine("输入为空");
}

字符串插值与格式化

string name = "张三";
int age = 25;
double score = 92.5;

// 插值
Console.WriteLine($"姓名: {name}, 年龄: {age}");

// 格式化
Console.WriteLine($"{score:F1}");        // 92.5
Console.WriteLine($"{score:P0}");        // 9,250%
Console.WriteLine($"{123456:N0}");       // 123,456
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm}");
Console.WriteLine($"{3.14159:F2}");      // 3.14

// 对齐
Console.WriteLine($"{"左对齐",-10}|");
Console.WriteLine($"{"右对齐",10}|");

StringBuilder(频繁拼接用)

using System.Text;

// 大量拼接用 StringBuilder(避免创建大量临时字符串)
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i).Append(", ");
}
string result = sb.ToString();
✅ 最佳实践
少量拼接用 + 或插值;循环/大量拼接用 StringBuilder;用户输入判空用 IsNullOrWhiteSpace。

C# 结构体(Struct)

结构体(struct):轻量级的值类型,适合表示小型的、不可变的数据片段。

struct 定义

public struct Point
{
    public int X;
    public int Y;

    // 构造函数(C# 10+ 支持无参构造)
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public double Distance()
    {
        return Math.Sqrt(X * X + Y * Y);
    }
}

// 使用
Point p = new Point(3, 4);
Console.WriteLine(p.Distance());   // 5
p.X = 10;                          // 可修改(除非 readonly)

readonly struct

// 只读结构体: 所有成员只读, 更安全高效
public readonly struct Money
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public Money Add(Money other) => new Money(Amount + other.Amount, Currency);
}

struct vs class

维度structclass
类型值类型(栈)引用类型(堆)
赋值复制值共享引用
继承不支持支持
默认值字段默认0/falsenull
性能小对象快(无GC)有GC开销
场景Point、Color、坐标业务实体

赋值行为差异

// struct: 复制
var p1 = new Point(1, 2);
var p2 = p1;
p2.X = 99;
Console.WriteLine(p1.X);    // 1 (p1 不受影响)

// class: 共享
var r1 = new Rectangle();
var r2 = r1;
r2.Width = 50;
Console.WriteLine(r1.Width);    // 50 (共享同一对象)

record struct(C# 10+)

// 值语义 + 值相等比较
public readonly record struct Temperature(double Value, string Unit);

var t1 = new Temperature(36.5, "C");
var t2 = new Temperature(36.5, "C");
Console.WriteLine(t1 == t2);    // true (按值比较)
✅ 选择标准
数据量小、值语义、复制成本低 → struct;有继承/多态/业务逻辑 → class。拿不准默认用 class。

C# 枚举(Enum)

枚举(enum):定义一组具名的常量,提高代码可读性与安全性。

枚举定义

public enum OrderStatus
{
    Pending = 0,     // 待处理
    Paid = 1,        // 已支付
    Shipped = 2,     // 已发货
    Completed = 3,   // 已完成
    Cancelled = 4    // 已取消
}

// 不指定值时从 0 开始递增
public enum Color { Red, Green, Blue }   // 0,1,2

// 指定基础类型(默认 int)
public enum Level : byte { Low = 1, Mid = 2, High = 3 }

使用枚举

OrderStatus status = OrderStatus.Paid;

// 比较
if (status == OrderStatus.Paid)
{
    Console.WriteLine("已支付");
}

// switch 处理
string text = status switch
{
    OrderStatus.Pending => "待处理",
    OrderStatus.Paid => "已支付",
    OrderStatus.Shipped => "已发货",
    _ => "其他"
};

// 转字符串/数字
string name = status.ToString();        // "Paid"
int value = (int)status;                // 1

// 字符串/数字 → 枚举
OrderStatus s2 = (OrderStatus)1;                // Paid
bool ok = Enum.TryParse("Shipped", out OrderStatus s3);  // true

枚举与显示名称

public enum Priority
{
    [Description("低优先级")]
    Low,
    [Description("中优先级")]
    Mid,
    [Description("高优先级")]
    High
}

// 读取描述
using System.ComponentModel;

static string GetDescription(Enum value)
{
    var field = value.GetType().GetField(value.ToString());
    var attr = (DescriptionAttribute?)Attribute.GetCustomAttribute(
        field!, typeof(DescriptionAttribute));
    return attr?.Description ?? value.ToString();
}

Console.WriteLine(GetDescription(Priority.High));   // 高优先级

[Flags] 位标志枚举

[Flags]
public enum Permissions
{
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
    All = Read | Write | Execute
}

var perm = Permissions.Read | Permissions.Write;    // 组合
perm.HasFlag(Permissions.Read);                     // true
perm.HasFlag(Permissions.Execute);                  // false

// 添加/移除
perm |= Permissions.Execute;    // 加
perm &= ~Permissions.Write;     // 移除 Write
✅ 最佳实践
用枚举代替魔法数字(状态、类型、等级);[Flags] 用 2 的幂定义组合权限;展示用 Description 属性配合中文。

C# 类(Class)

类(class)是 C# 面向对象的核心:封装数据(字段/属性)与行为(方法)。

类定义

public class Car
{
    // 字段
    private string _brand;

    // 属性
    public string Brand
    {
        get => _brand;
        set => _brand = value ?? "未知";
    }

    public int Speed { get; private set; }   // 外部只读

    // 构造函数
    public Car(string brand)
    {
        _brand = brand;
        Speed = 0;
    }

    // 方法
    public void Accelerate(int delta)
    {
        Speed += delta;
    }

    public void Brake()
    {
        Speed = Math.Max(0, Speed - 10);
    }
}

创建与使用对象

// new 创建对象
Car myCar = new Car("比亚迪");
myCar.Accelerate(50);
myCar.Brake();
Console.WriteLine($"{myCar.Brand} 速度 {myCar.Speed}");

// 对象初始化器
var user = new User
{
    Name = "张三",
    Age = 25
};

构造函数的几种形态

public class Product
{
    public string Name { get; }
    public decimal Price { get; }

    // 默认/主构造函数
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    // 重载构造函数(链式调用)
    public Product(string name) : this(name, 0m) { }

    // 静态工厂
    public static Product CreateDefault() => new("未命名", 0m);
}

var p = Product.CreateDefault();

静态成员

public class MathUtils
{
    public const double Pi = 3.14159;      // 常量

    public static int Count = 0;           // 静态字段

    public static int Square(int x) => x * x;   // 静态方法

    static MathUtils() { Count = 0; }      // 静态构造
}

// 通过类名访问(无需实例)
int sq = MathUtils.Square(5);
MathUtils.Count++;

this 与对象引用

public class Person
{
    private string _name;

    public Person(string name)
    {
        this._name = name;    // this 区分字段与参数
    }

    public Person Clone()
    {
        return new Person(this._name);   // this 指当前对象
    }
}
✅ 设计建议
属性用于对外接口,字段私有;构造函数保证「对象创建即合法」;静态成员适合工具方法与全局配置。

C# 继承

继承(Inheritance):派生类复用并扩展基类的代码,实现「is-a」关系——面向对象三大特性之二。

基础继承

public class Animal
{
    public string Name { get; set; } = "";

    public Animal(string name) { Name = name; }

    public virtual void Speak()      // virtual: 可重写
    {
        Console.WriteLine($"{Name} 发出声音");
    }
}

public class Dog : Animal            // : 表示继承
{
    public Dog(string name) : base(name) { }   // base: 调基类构造

    public override void Speak()     // override: 重写
    {
        Console.WriteLine($"{Name} 汪汪叫");
    }

    public void Fetch() { Console.WriteLine("叼回飞盘"); }
}

// 使用
Dog dog = new Dog("旺财");
dog.Speak();        // 旺财 汪汪叫
dog.Fetch();        // 叼回飞盘 (子类特有)

继承层级与多态引用

Animal a = new Dog("小黑");   // 基类引用指向子类对象
a.Speak();                    // 小黑 汪汪叫 (多态: 调用实际类型的方法)

Animal[] animals = {
    new Dog("A"),
    new Cat("B"),
    new Animal("C")
};
foreach (var animal in animals)
{
    animal.Speak();   // 各自实现
}

sealed 与 abstract

// abstract: 抽象类, 不能实例化, 子类必须实现抽象成员
public abstract class Shape
{
    public abstract double Area();    // 抽象方法(无实现)
    public void Describe()            // 普通方法
    {
        Console.WriteLine($"面积: {Area()}");
    }
}

public class Circle : Shape
{
    public double Radius { get; }
    public Circle(double r) { Radius = r; }

    public override double Area() => Math.PI * Radius * Radius;
}

// sealed: 密封类, 不能被继承
public sealed class FinalConfig { }
// public class Sub : FinalConfig { }   // ❌ 编译错误

base 关键字

public class BaseClass
{
    protected int Value = 10;

    public virtual void Show()
    {
        Console.WriteLine($"Base: {Value}");
    }
}

public class Derived : BaseClass
{
    public override void Show()
    {
        base.Show();          // 调用基类实现
        Console.WriteLine($"Derived: {Value}");   // 访问受保护成员
    }
}

is / as 类型判断

object obj = new Dog("阿黄");

if (obj is Dog dog1)          // 模式匹配
{
    dog1.Fetch();
}

Dog? d = obj as Dog;          // as: 转换失败返回 null
if (d != null) { d.Fetch(); }

Animal a2 = new Dog("小黄");
if (a2 is Dog) { Console.WriteLine("是 Dog"); }
✅ 原则
继承表达「is-a」(狗是动物);组合表达「has-a」(车有引擎)。优先组合,继承只用于真正的层级关系。

C# 多态性

多态(Polymorphism):同一接口、不同实现——「一个方法,多种形态」。核心机制是 virtual/override。

多态的三种形式

1. 方法重载(编译时多态): 同名方法不同参数
2. 继承重写(运行时多态): virtual + override
3. 接口实现: 不同类实现同一接口

运行时多态

public class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("绘制形状");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("绘制圆形");
    }
}

public class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("绘制矩形");
    }
}

// 多态: 运行时决定调用哪个 Draw
Shape[] shapes = { new Circle(), new Rectangle(), new Shape() };
foreach (var s in shapes)
{
    s.Draw();
}
// 输出:
// 绘制圆形
// 绘制矩形
// 绘制形状

virtual / override / new

public class Base
{
    public virtual void Method() => Console.WriteLine("Base");
}

public class Derived : Base
{
    // override: 真正重写(推荐)
    public override void Method() => Console.WriteLine("Derived");

    // new: 隐藏基类方法(不推荐, 破坏多态)
    public new void Method() => Console.WriteLine("隐藏");
}

// override 效果: 基类引用调子类实现
Base b = new Derived();
b.Method();    // Derived (多态生效)

// new 效果: 基类引用调基类实现
// (Base b = new Derived(); b.Method() → Base)

抽象类与多态

public abstract class Payment
{
    public abstract void Pay(decimal amount);   // 子类实现

    public void Confirm()
    {
        Console.WriteLine("支付确认");
    }
}

public class WeChatPay : Payment
{
    public override void Pay(decimal amount)
        => Console.WriteLine($"微信支付 {amount} 元");
}

public class Alipay : Payment
{
    public override void Pay(decimal amount)
        => Console.WriteLine($"支付宝支付 {amount} 元");
}

// 统一处理
void Checkout(Payment payment, decimal amount)
{
    payment.Pay(amount);       // 多态
    payment.Confirm();
}

Checkout(new WeChatPay(), 99);
Checkout(new Alipay(), 99);

多态的价值

  • 开闭原则:加新类型不改已有代码(加个新 Payment 子类即可)
  • 统一接口:调用方只需面向基类/接口编程
  • 可扩展:策略模式、工厂模式的根基
✅ 记忆
多态三要素:继承(或接口)、重写、基类引用。看到「一个方法在不同对象上表现不同」,就是多态。

C# 运算符重载

运算符重载:为自定义类型定义运算符的行为(如 +、-、==),让代码更自然。

定义运算符重载

public struct Vector
{
    public double X { get; }
    public double Y { get; }

    public Vector(double x, double y) { X = x; Y = y; }

    // 重载 +
    public static Vector operator +(Vector a, Vector b)
        => new Vector(a.X + b.X, a.Y + b.Y);

    // 重载 -
    public static Vector operator -(Vector a, Vector b)
        => new Vector(a.X - b.X, a.Y - b.Y);

    // 重载 * (向量乘标量)
    public static Vector operator *(Vector a, double s)
        => new Vector(a.X * s, a.Y * s);

    // 重载 == 和 != (应配套)
    public static bool operator ==(Vector a, Vector b)
        => a.X == b.X && a.Y == b.Y;

    public static bool operator !=(Vector a, Vector b)
        => !(a == b);

    // 同时应重写 Equals 和 GetHashCode
    public override bool Equals(object? obj)
        => obj is Vector v && this == v;

    public override int GetHashCode() => (X, Y).GetHashCode();

    public override string ToString() => $"({X}, {Y})";
}

// 使用
var v1 = new Vector(1, 2);
var v2 = new Vector(3, 4);
Vector sum = v1 + v2;      // (4, 6)
Vector scaled = v1 * 2.5;  // (2.5, 5)
Console.WriteLine(v1 == new Vector(1, 2));  // True
Console.WriteLine(sum);    // (4, 6)

可重载的运算符

类别运算符
算术+ - * / %
比较== != < > <= >=(成对)
一元! ++ -- - +
转换implicit / explicit

隐式/显式转换

public struct Celsius
{
    public double Value { get; }
    public Celsius(double v) { Value = v; }

    // 隐式: double → Celsius
    public static implicit operator Celsius(double v)
        => new Celsius(v);

    // 显式: Celsius → Fahrenheit
    public static explicit operator Fahrenheit(Celsius c)
        => new Fahrenheit(c.Value * 9 / 5 + 32);
}

public struct Fahrenheit
{
    public double Value { get; }
    public Fahrenheit(double v) { Value = v; }
}

Celsius c = 36.5;          // 隐式转换
Fahrenheit f = (Fahrenheit)c;   // 显式转换

注意事项

// 1. 重载 == 必须同时重写 Equals 和 GetHashCode
// 2. 重载 < 必须同时重载 >
// 3. 运算符重载不能改变原有语义(如 + 不能做减法)
// 4. 滥用会降低可读性
✅ 建议
运算符重载适合「数学/数值」语义明确的类型(Vector、Money、TimeSpan);普通业务类用方法代替更清晰。

C# 接口(Interface)

接口(interface)定义一组「能力契约」:只声明成员,不实现。任何实现接口的类都必须提供这些成员。

接口定义与实现

public interface ILogger
{
    void Log(string message);         // 方法签名
    string Name { get; }              // 属性
}

public class ConsoleLogger : ILogger
{
    public string Name => "Console";

    public void Log(string message)
    {
        Console.WriteLine($"[{Name}] {message}");
    }
}

public class FileLogger : ILogger
{
    public string Name => "File";

    public void Log(string message)
    {
        File.AppendAllText("log.txt", message + "\n");
    }
}

面向接口编程

// 依赖接口, 不依赖具体类
void WriteLog(ILogger logger, string msg)
{
    logger.Log(msg);      // 多态
}

var logger = new ConsoleLogger();
WriteLog(logger, "用户登录");       // [Console] 用户登录

// 轻松切换实现(可测试性)
WriteLog(new FileLogger(), "审计记录");

接口默认实现(C# 8+)

public interface IReport
{
    string Title { get; }
    string Render();

    // 默认实现: 实现类可不重写
    public void Print() => Console.WriteLine(Render());
}

public class SalesReport : IReport
{
    public string Title => "销售报表";
    public string Render() => "| 1月 | 2月 | 3月 |";
}

var report = new SalesReport();
report.Print();   // 用默认实现

多接口实现

public interface ISaveable { void Save(); }
public interface IPrintable { void Print(); }

public class Document : ISaveable, IPrintable
{
    public void Save() => Console.WriteLine("保存文档");
    public void Print() => Console.WriteLine("打印文档");
}

// 接口引用
ISaveable s = new Document();
s.Save();
IPrintable p = new Document();
p.Print();

接口 vs 抽象类

维度interfaceabstract class
成员纯契约(默认无实现)可有实现字段
继承数量可实现多个只能继承一个
字段不能有实例字段可以有
场景能力/契约(ILogger)家族层级(Shape)

接口设计原则

// 接口隔离: 小而专(不要大而全)
public interface IReadable { string Read(); }
public interface IWritable { void Write(string data); }

// 实现需要的能力
public class FileStore : IReadable, IWritable { ... }
public class ReadOnlyCache : IReadable { ... }   // 只读场景
✅ 核心价值
接口是「契约」:定义能力,不管实现。依赖接口 → 解耦、可测试、可替换。这是依赖注入与单元测试的基础。

C# 命名空间(Namespace)

命名空间(namespace)组织代码、避免命名冲突,是 C# 代码的「文件夹」。

定义与使用

namespace Company.Project.Core
{
    public class Config { }
}

namespace Company.Project.Api
{
    public class Program
    {
        static void Main()
        {
            // 用 using 引入
            // using Company.Project.Core;
            var cfg = new Config();
        }
    }
}

using 语句

// 文件顶部引入
using System;
using System.Collections.Generic;
using System.Linq;

// 别名(解决冲突)
using Console = System.Console;

// 静态引入
using static System.Math;
double x = Sqrt(16);      // 直接用方法名

// 全局 using(所有文件生效, 写在任意文件的顶部+global)
global using System;

嵌套命名空间与别名

// 嵌套
namespace Outer
{
    namespace Inner
    {
        public class Helper { }
    }
}
// 完整名: Outer.Inner.Helper

// 点语法等价
namespace Outer.Inner
{
    public class Helper2 { }
}

// 别名
using HelperAlias = Outer.Inner.Helper;
var h = new HelperAlias();

命名空间 vs 程序集

命名空间(逻辑组织) ≠ 程序集(物理文件 .dll)
- 一个程序集可含多个命名空间
- 一个命名空间可跨多个程序集

命名空间约定: 公司.项目.模块
如: MyCompany.Blog.Domain, MyCompany.Blog.Services

文件作用域命名空间(C# 10+)

// 新写法: 不用花括号
namespace MyApp.Models;

public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
✅ 最佳实践
命名空间按功能模块分层(Models/Services/Controllers);避免过深的嵌套;公共类型放根命名空间方便引入。

C# 预处理器指令

预处理器指令:编译前的条件编译指令(以 # 开头),用于按条件包含代码、定义符号、区域折叠。

条件编译

// 定义符号
#define DEBUG
// #undef DEBUG

class Program
{
    static void Main()
    {
#if DEBUG
        Console.WriteLine("调试模式");
#else
        Console.WriteLine("发布模式");
#endif
    }
}

常用指令

指令作用
#define / #undef定义/取消符号
#if / #elif / #else / #endif条件编译
#region / #endregion代码折叠区域
#warning / #error编译警告/错误
#nullable控制可空上下文
#pragma warning抑制警告

平台/框架条件编译

class PlatformHelper
{
    public static string GetPath()
    {
#if WINDOWS
        return @"C:\temp";
#elif LINUX || MACOS
        return "/tmp";
#else
        return ".";
#endif
    }
}

// 项目文件(.csproj)中定义符号:
// 
//   $(DefineConstants);WINDOWS
// 

#region 折叠

class BigService
{
#region 私有方法
    private void Helper1() { }
    private void Helper2() { }
#endregion

#region 公开 API
    public void DoWork() { }
#endregion
}

调试辅助

// 仅调试版执行的代码
#if DEBUG
    Console.WriteLine($"耗时: {sw.ElapsedMilliseconds}ms");
#endif

// 条件特性
[System.Diagnostics.Conditional("DEBUG")]
void LogDebug(string msg) { Console.WriteLine(msg); }

// 抑制警告
#pragma warning disable CS0219   // 未使用变量警告
int unused = 5;
#pragma warning restore CS0219
✅ 注意
预处理器指令在编译前处理,不是运行时判断;#if DEBUG 的代码不会出现在 Release 程序集中(更省体积)。

C# 正则表达式

正则表达式(Regex):用模式匹配文本,用于校验、提取、替换。

基础匹配

using System.Text.RegularExpressions;

string text = "我的邮箱是 test@example.com,电话 138-1234-5678";

// 匹配邮箱
Match m = Regex.Match(text, @"[\w.-]+@[\w.-]+\.\w+");
if (m.Success)
{
    Console.WriteLine(m.Value);    // test@example.com
}

// 判断是否匹配
bool isMatch = Regex.IsMatch("abc123", @"\d+");   // true

常用验证

// 邮箱
Regex.IsMatch(email, @"^[\w.-]+@[\w.-]+\.\w+$");

// 手机号(中国大陆)
Regex.IsMatch(phone, @"^1[3-9]\d{9}$");

// 身份证
Regex.IsMatch(id, @"^\d{17}[0-9Xx]$");

// URL
Regex.IsMatch(url, @"^https?://");

// 中文
Regex.IsMatch(text, @"[\u4e00-\u9fa5]");

提取与分组

string html = "

价格: 99.5 元

"; Match m = Regex.Match(html, @"(\d+\.?\d*)"); if (m.Success) { string price = m.Groups[1].Value; // 99.5 (分组1) Console.WriteLine(price); } // 多个匹配 string log = "2026-01-01 10:00 | 2026-02-02 11:30"; var dates = Regex.Matches(log, @"\d{4}-\d{2}-\d{2}"); foreach (Match d in dates) { Console.WriteLine(d.Value); // 2026-01-01, 2026-02-02 }

替换与分割

// 替换
string masked = Regex.Replace(
    "13812345678",
    @"(\d{3})\d{4}(\d{4})",
    "$1****$2");            // 138****5678

// 清理空白
string clean = Regex.Replace("a   b  c", @"\s+", " ");   // "a b c"

// 分割
var parts = Regex.Split("a,b;c|d", @"[,;|]");
// ["a", "b", "c", "d"]

性能与预编译

// 频繁使用: 缓存正则(避免每次编译)
private static readonly Regex EmailRegex =
    new(@"^[\w.-]+@[\w.-]+\.\w+$",
        RegexOptions.Compiled | RegexOptions.IgnoreCase);

bool IsValidEmail(string email) => EmailRegex.IsMatch(email);

// 超时保护(防 ReDoS)
new Regex(pattern, RegexOptions.None, TimeSpan.FromSeconds(1));
✅ 速记
\d数字、\w字母数字、.+任意、^$边界、()分组、{n,m}次数。复杂正则先在在线工具测试。

C# 异常处理

异常处理:用 try/catch/finally 捕获与处理运行时错误,保证程序健壮性。

基本结构

try
{
    int x = 10;
    int y = 0;
    int result = x / y;        // 抛 DivideByZeroException
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"除零错误: {ex.Message}");
}
catch (Exception ex)           // 兜底: 其他所有异常
{
    Console.WriteLine($"未知错误: {ex.Message}");
}
finally
{
    Console.WriteLine("总会执行(清理资源)");   // 可选
}

常见异常类型

异常场景
NullReferenceException访问 null 对象
ArgumentException参数非法
FormatException格式转换失败
IndexOutOfRangeException索引越界
FileNotFoundException文件不存在
InvalidOperationException状态非法
TimeoutException超时

抛出异常

public void Withdraw(decimal amount)
{
    if (amount <= 0)
        throw new ArgumentException("金额必须大于0", nameof(amount));

    if (amount > Balance)
        throw new InvalidOperationException("余额不足");
}

// 自定义异常
public class OrderException : Exception
{
    public string OrderId { get; }

    public OrderException(string orderId, string message)
        : base(message)
    {
        OrderId = orderId;
    }
}

using 资源管理

// 等价于 try/finally + Dispose
using (var reader = new StreamReader("file.txt"))
{
    string content = reader.ReadToEnd();
}   // 自动释放

// 简化写法(C# 8+)
using var writer = new StreamWriter("out.txt");
writer.WriteLine("hello");
// 作用域结束时自动释放

try-catch 实战

try
{
    string input = Console.ReadLine() ?? "";
    int age = int.Parse(input);

    if (age < 0 || age > 150)
        throw new ArgumentOutOfRangeException(nameof(age), "年龄超出范围");

    Console.WriteLine($"明年 {age + 1} 岁");
}
catch (FormatException)
{
    Console.WriteLine("请输入数字");
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine($"系统错误: {ex.Message}");
    // 记录日志...
}
✅ 最佳实践
能用 TryParse/if 避免的异常尽量避免(用逻辑代替异常);catch 后必须处理或记录,别空吞;异常用于「意外情况」,不用作流程控制。

C# 文件的输入与输出

C# 文件 IO:读写文本、读写二进制、目录操作。

读取文件

using System.IO;

// 简单方式
string content = File.ReadAllText("data.txt");
string[] lines = File.ReadAllLines("data.txt");
byte[] bytes = File.ReadAllBytes("image.png");

// 流式读取(大文件)
using var reader = new StreamReader("big.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine(line);
}

写入文件

// 简单方式
File.WriteAllText("out.txt", "hello");
File.AppendAllText("log.txt", $"{DateTime.Now}\n");   // 追加

// 流式写入
using var writer = new StreamWriter("report.txt");
writer.WriteLine("标题: 销售报告");
writer.WriteLine($"日期: {DateTime.Now:yyyy-MM-dd}");

// 编码指定
File.WriteAllText("utf8.txt", "中文内容",
    System.Text.Encoding.UTF8);

目录与文件操作

// 判断存在
File.Exists("a.txt");
Directory.Exists("folder");

// 目录操作
Directory.CreateDirectory("uploads/2026");
string[] files = Directory.GetFiles("folder");          // 所有文件
string[] txts = Directory.GetFiles("folder", "*.txt");  // 过滤
string[] dirs = Directory.GetDirectories("folder");     // 子目录

// 文件操作
File.Copy("a.txt", "b.txt", overwrite: true);
File.Move("a.txt", "archive/a.txt");
File.Delete("temp.txt");

// 信息
var info = new FileInfo("a.txt");
info.Length;       // 大小
info.LastWriteTime;  // 修改时间
info.Extension;    // 扩展名

路径处理

string dir = @"C:\data";
string file = "report.txt";

// 拼接路径(自动处理分隔符)
string full = Path.Combine(dir, file);

Path.GetFileName(full);       // "report.txt"
Path.GetExtension(full);      // ".txt"
Path.GetDirectoryName(full);  // "C:\data"
Path.GetFileNameWithoutExtension(full);  // "report"

// 应用目录
string appDir = AppDomain.CurrentDomain.BaseDirectory;
string tempDir = Path.GetTempPath();

二进制文件

// 写入二进制
using var bw = new BinaryWriter(File.OpenWrite("data.bin"));
bw.Write(42);            // int
bw.Write(3.14);          // double
bw.Write("hello");       // 字符串

// 读取二进制
using var br = new BinaryReader(File.OpenRead("data.bin"));
int i = br.ReadInt32();
double d = br.ReadDouble();
string s = br.ReadString();
✅ 最佳实践
始终用 using 管理文件句柄;路径用 Path.Combine;处理用户输入路径时注意路径穿越安全(防 ../ 攻击)。

C# 特性(Attribute)

特性(Attribute):给代码元素(类、方法、属性)附加元数据,运行时可通过反射读取。像「标签」一样描述代码。

内置特性

[Obsolete("用 NewMethod 代替")]     // 标记废弃
public void OldMethod() { }

[Serializable]                        // 可序列化标记
public class Data { }

[Conditional("DEBUG")]               // 条件编译
public void DebugLog() { }

[Flags]                              // 位标志枚举
[Enum]

自定义特性

// 定义特性类: 继承 Attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
    AllowMultiple = false)]
public class AuthorAttribute : Attribute
{
    public string Name { get; }
    public string? Email { get; set; }

    public AuthorAttribute(string name)
    {
        Name = name;
    }
}

// 使用特性
[Author("张三", Email = "zhangsan@example.com")]
public class OrderService
{
    [Author("李四")]
    public void CreateOrder() { }
}

读取特性(反射)

// 读取类上的特性
var attrs = typeof(OrderService)
    .GetCustomAttributes(typeof(AuthorAttribute), false);
foreach (AuthorAttribute a in attrs)
{
    Console.WriteLine($"作者: {a.Name}, 邮箱: {a.Email}");
}

// 读取方法上的特性
var method = typeof(OrderService).GetMethod("CreateOrder")!;
var ma = method.GetCustomAttribute();
Console.WriteLine(ma?.Name);   // 李四

特性应用场景

// 1. 数据校验(ASP.NET)
public class LoginModel
{
    [Required(ErrorMessage = "用户名必填")]
    [StringLength(20, MinimumLength = 3)]
    public string Username { get; set; } = "";

    [EmailAddress]
    public string? Email { get; set; }
}

// 2. 序列化控制
[JsonIgnore]
public string PasswordHash { get; set; } = "";   // JSON 忽略

[JsonPropertyName("display_name")]
public string DisplayName { get; set; } = "";    // 改名

// 3. 路由/API 元数据(Web)
[HttpGet("api/orders/{id}")]
public IActionResult GetOrder(int id) { ... }
✅ 理解
特性 = 声明式元数据:不改变代码行为,但框架(ASP.NET、EF、序列化)可以通过反射读取它来改变行为。

C# 反射(Reflection)

反射(Reflection):运行时检查并操作类型——获取类型信息、创建实例、调用方法、读写字段。是特性、序列化、依赖注入的底层机制。

获取类型

// 三种方式
Type t1 = typeof(DateTime);
Type t2 = new DateTime().GetType();
Type t3 = Type.GetType("System.DateTime");   // 按名称

// 类型信息
t1.Name;             // "DateTime"
t1.FullName;         // "System.DateTime"
t1.IsClass;          // true
t1.IsValueType;      // true(DateTime 是结构体)
t1.BaseType;         // System.ValueType

遍历成员

Type t = typeof(string);

foreach (var m in t.GetMethods())
{
    if (m.IsPublic && m.DeclaringType == t)
    {
        Console.WriteLine(m.Name);
    }
}

// 获取属性
foreach (var p in t.GetProperties())
{
    Console.WriteLine($"{p.Name}: {p.PropertyType.Name}");
}

动态创建与调用

// 创建实例
object obj = Activator.CreateInstance(typeof(List))!;

// 泛型类型创建
Type genType = typeof(List<>).MakeGenericType(typeof(string));
object list = Activator.CreateInstance(genType)!;

// 调用方法
Type t = typeof(Math);
MethodInfo? method = t.GetMethod("Pow");
object? result = method?.Invoke(null, new object[] { 2.0, 10.0 });
Console.WriteLine(result);   // 1024

// 反射调用实例方法
var sb = new StringBuilder();
Type sbType = sb.GetType();
sbType.GetMethod("Append")!
     .Invoke(sb, new object[] { "Hello" });
Console.WriteLine(sb);       // Hello

读取字段/属性(通用打印器)

public static string Dump(object obj)
{
    var sb = new StringBuilder();
    Type t = obj.GetType();
    sb.AppendLine($"{t.Name}:");
    foreach (var p in t.GetProperties())
    {
        var val = p.GetValue(obj);
        sb.AppendLine($"  {p.Name} = {val}");
    }
    return sb.ToString();
}

Console.WriteLine(Dump(new { Name = "张三", Age = 25 }));

反射的代价

优点: 动态、通用、插件化(ORM/框架的基础)
缺点: 性能低(慢于直接调用)、可读性差、易出错
建议: 框架代码用, 业务代码少用; 
     高频路径用表达式树/生成器代替反射
✅ 典型应用
对象映射(AutoMapper)、ORM(EF Core 模型映射)、单元测试框架、插件系统——都是反射的忠实用户。

C# 属性(Property)

属性(Property):类中对字段的安全访问接口——get/set 访问器,可加逻辑与校验。

基本属性

public class Employee
{
    private string _name = "";

    // 完整属性
    public string Name
    {
        get { return _name; }
        set { _name = value; }     // value 是隐式参数
    }

    // 自动属性(无逻辑时)
    public int Age { get; set; }

    // 只读自动属性(构造赋值)
    public string Id { get; } = Guid.NewGuid().ToString();

    // 私有 set
    public DateTime CreatedAt { get; private set; }

    // 计算属性(只有 get)
    public string Display => $"{Name} ({Age}岁)";
}

带校验的属性

private int _age;
public int Age
{
    get => _age;
    set
    {
        if (value < 0 || value > 150)
            throw new ArgumentOutOfRangeException(nameof(Age));
        _age = value;
    }
}

// 或静默修正
public string Name
{
    get => _name;
    set => _name = string.IsNullOrWhiteSpace(value) ? "未命名" : value.Trim();
}

表达式体属性

public class Order
{
    public List Items { get; set; } = new();

    // 计算属性(简洁)
    public decimal Total => Items.Sum(i => i.Price * i.Quantity);
    public bool IsEmpty => Items.Count == 0;
}

init 属性(C# 9+)

public class Product
{
    public string Name { get; init; } = "";    // 只能在初始化时设置
    public decimal Price { get; init; }
}

var p = new Product { Name = "手机", Price = 1999 };   // ✅
// p.Name = "其他";    // ❌ init 后不可改

// 配合 with 表达式做不可变更新
var p2 = p with { Price = 1499 };

属性 vs 字段

维度字段属性
访问控制通常 private公开接口
可加逻辑不能get/set 可加逻辑
校验不能可以
序列化默认不序列化默认序列化
✅ 最佳实践
对外暴露一律用属性(可加校验、可序列化、可绑定);字段保持 private。计算属性(只 get)是「派生数据」的优雅表达。

C# 索引器(Indexer)

索引器(Indexer):让对象可以像数组一样用 [] 访问,本质是带参数的属性。

基本索引器

public class WordList
{
    private readonly List _words = new();

    public void Add(string word) => _words.Add(word);

    // 索引器: this[int index]
    public string this[int index]
    {
        get => _words[index];
        set => _words[index] = value;
    }

    public int Count => _words.Count;
}

var words = new WordList();
words.Add("apple");
words.Add("banana");
Console.WriteLine(words[0]);      // apple
words[1] = "orange";              // 修改
Console.WriteLine(words[1]);      // orange

字符串键索引器(字典风格)

public class ConfigStore
{
    private readonly Dictionary _data = new();

    public string this[string key]
    {
        get => _data.TryGetValue(key, out var v) ? v : "";
        set => _data[key] = value;
    }
}

var config = new ConfigStore();
config["SiteName"] = "我的网站";
config["PageSize"] = "20";
Console.WriteLine(config["SiteName"]);   // 我的网站
Console.WriteLine(config["PageSize"]);   // 20

多维/多参索引器

public class Matrix
{
    private readonly int[,] _data;

    public Matrix(int rows, int cols)
    {
        _data = new int[rows, cols];
    }

    public int this[int row, int col]
    {
        get => _data[row, col];
        set => _data[row, col] = value;
    }
}

var m = new Matrix(2, 2);
m[0, 0] = 1;
m[1, 1] = 9;
Console.WriteLine(m[1, 1]);   // 9

索引器与 foreach

// 实现 IEnumerable 后可用 foreach
using System.Collections;

public class NumberList : IEnumerable
{
    private readonly int[] _nums;

    public NumberList(int[] nums) => _nums = nums;

    public int this[int i] => _nums[i];

    public IEnumerator GetEnumerator() => _nums.GetEnumerator();
}

var list = new NumberList(new[] { 10, 20, 30 });
foreach (int n in list) { Console.WriteLine(n); }

常见用法

// 内置索引器示例
string s = "hello";
char c = s[1];             // 'e'

List nums = new() { 1, 2, 3 };
int v = nums[2];           // 3

// Span/数组等也支持索引
int[] arr = { 5, 6, 7 };
arr[^1];                   // 7 (C# 8+ 从后索引)
✅ 建议
索引器适合「自然支持下标访问」的容器类(集合、矩阵、映射);普通业务类优先用方法。

C# 委托(Delegate)

委托(delegate):C# 中把方法当作参数/变量传递的类型——「方法的类型」。是事件、回调、LINQ 的基础。

委托基础

// 定义委托: 声明"方法的签名"
public delegate int MathOperation(int a, int b);

// 方法
static int Add(int a, int b) => a + b;
static int Multiply(int a, int b) => a * b;

// 委托变量指向方法
MathOperation op = Add;
int r1 = op(3, 4);          // 7

op = Multiply;              // 换方法
int r2 = op(3, 4);          // 12

// 方法作为参数(回调)
static void Execute(int a, int b, MathOperation op)
{
    Console.WriteLine(op(a, b));
}
Execute(5, 3, Add);         // 8

内置委托 Func / Action / Predicate

// Func: 有返回值(最后一个是返回类型)
Func add = (a, b) => a + b;
Func square = x => x * x;

// Action: 无返回值
Action print = msg => Console.WriteLine(msg);
Action log = () => Console.WriteLine("记录日志");

// Predicate: 返回 bool
Predicate isEven = n => n % 2 == 0;

// 使用
print("Hello");           // Hello
Console.WriteLine(square(5));   // 25

多播委托(+=)

Action notify = () => Console.WriteLine("发送短信");
notify += () => Console.WriteLine("发送邮件");
notify += () => Console.WriteLine("推送通知");

notify();
// 输出:
// 发送短信
// 发送邮件
// 推送通知

// 移除
notify -= () => Console.WriteLine("发送邮件");

委托实战:排序比较器

List nums = new() { 3, 1, 4, 1, 5 };

// 自定义比较
nums.Sort((a, b) => b.CompareTo(a));   // 降序
// [5,4,3,1,1]

// 按条件筛选
var evens = nums.Where(n => n % 2 == 0);   // LINQ 内部用委托

// 延迟执行示例
var sorted = nums.OrderBy(n => n);         // n => n 是委托

lambda 与匿名方法

// Lambda 表达式
Func doubleIt = x => x * 2;

// 多参数 lambda
Func sum = (a, b) => a + b;

// 语句体 lambda
Action greet = name =>
{
    string msg = $"你好, {name}";
    Console.WriteLine(msg);
};

// 匿名方法(老语法)
Func old = delegate (int x) { return x * 3; };
✅ 理解
委托 = 「方法的指针」。日常用 Func/Action 就够了;自定义 delegate 只在需要语义化命名时使用。

C# 事件(Event)

事件(event):基于委托的发布-订阅机制——对象状态变化时通知订阅者。是 GUI 按钮点击、异步通知的基石。

事件定义与订阅

public class Alarm
{
    // 定义事件: 基于委托
    public event Action? Ring;

    public void Trigger()
    {
        Console.WriteLine("警报响起!");
        Ring?.Invoke();        // 通知所有订阅者
    }
}

// 使用
var alarm = new Alarm();

// 订阅事件
alarm.Ring += () => Console.WriteLine("发送短信提醒");
alarm.Ring += () => Console.WriteLine("拨打紧急电话");

alarm.Trigger();
// 输出:
// 警报响起!
// 发送短信提醒
// 拨打紧急电话

带参数事件(EventArgs)

// 事件参数
public class OrderEventArgs : EventArgs
{
    public string OrderId { get; }
    public decimal Amount { get; }
    public OrderEventArgs(string id, decimal amount)
    {
        OrderId = id; Amount = amount;
    }
}

public class OrderService
{
    // 标准事件模式: EventHandler
    public event EventHandler? OrderCreated;

    public void Create(string id, decimal amount)
    {
        // 业务逻辑...
        OrderCreated?.Invoke(this, new OrderEventArgs(id, amount));
    }
}

// 订阅
var service = new OrderService();
service.OrderCreated += (sender, e) =>
    Console.WriteLine($"新订单 {e.OrderId}, 金额 {e.Amount}");
service.OrderCreated += (sender, e) => SendReceipt(e.OrderId);

service.Create("A001", 199.5m);

内置事件:按钮点击(模拟)

public class Button
{
    public event EventHandler? Click;

    public void OnClick()
    {
        Click?.Invoke(this, EventArgs.Empty);
    }
}

var btn = new Button();
btn.Click += (s, e) => Console.WriteLine("按钮被点击了");
btn.Click += (s, e) => SaveData();

btn.OnClick();

事件的限制(vs 委托)

// 事件只能在本类内部触发(Invoke)
// 外部只能 += / -=(订阅/退订)
// 委托可以直接调用与赋值

// 错误: 外部不能触发
// alarm.Ring?.Invoke();   // ❌ 编译错误(类外部)
// alarm.Ring = null;      // ❌ 编译错误

// 正确: 外部只能订阅
alarm.Ring += Handler;

事件常见模式

// 属性变化通知
public class Person : INotifyPropertyChanged
{
    private string _name = "";
    public event PropertyChangedEventHandler? PropertyChanged;

    public string Name
    {
        get => _name;
        set
        {
            if (_name != value)
            {
                _name = value;
                PropertyChanged?.Invoke(this,
                    new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }
}
✅ 记忆
事件 = 委托 + 封装限制(只能订阅,不能外部触发)。看到「A 通知 B 做某事」就是事件/回调的典型场景。

C# 集合(Collection)

C# 集合体系:List、Dictionary、HashSet、Queue、Stack——处理动态数据的主力。

List<T>(动态数组)

var list = new List();

list.Add(10);                // 添加
list.AddRange(new[] { 20, 30 });
list.Insert(1, 15);          // 插入到位置1

list.Remove(20);             // 按值移除
list.RemoveAt(0);            // 按下标移除
list.Contains(30);           // true
list.Count;                  // 元素个数
list[0];                     // 按下标访问

// 遍历
foreach (var n in list) { }
for (int i = 0; i < list.Count; i++) { }

// LINQ
list.Where(n => n > 10);
list.OrderBy(n => n);
list.Sum();

Dictionary<K,V>(键值对)

var dict = new Dictionary();

dict["apple"] = 5;
dict["banana"] = 3;

// 安全读取
if (dict.TryGetValue("apple", out int count))
{
    Console.WriteLine(count);   // 5
}

dict.ContainsKey("orange");     // false
dict.Remove("banana");
dict.Count;

// 遍历
foreach (var kv in dict)
{
    Console.WriteLine($"{kv.Key}: {kv.Value}");
}

// 不存在时给默认值
dict.GetValueOrDefault("grape");   // 0

HashSet<T>(唯一集合)

var set = new HashSet();
set.Add(1);
set.Add(1);       // 忽略重复
set.Add(2);

set.Count;        // 2
set.Contains(1);  // true

// 集合运算
var a = new HashSet { 1, 2, 3 };
var b = new HashSet { 2, 3, 4 };
a.UnionWith(b);           // {1,2,3,4} 并
a.IntersectWith(b);       // 交
a.ExceptWith(b);          // 差

// 去重
var unique = new HashSet(new[] { 1, 1, 2, 3, 3 });  // {1,2,3}

Queue / Stack

// 队列 FIFO
var queue = new Queue();
queue.Enqueue("任务1");
queue.Enqueue("任务2");
queue.Dequeue();        // "任务1"
queue.Peek();           // "任务2" (不取出)

// 栈 LIFO
var stack = new Stack();
stack.Push(1);
stack.Push(2);
stack.Pop();            // 2
stack.Peek();           // 1

集合选型

集合特性场景
List<T>有序、可重复、索引访问默认首选
Dictionary<K,V>键值、O(1) 查找按键查数据
HashSet<T>唯一、O(1) 查找去重、集合运算
Queue<T>FIFO任务队列、BFS
Stack<T>LIFO撤销、括号匹配

集合初始化与不可变

// 初始化语法
var nums = new List { 1, 2, 3 };
var map = new Dictionary
{
    ["a"] = 1,
    ["b"] = 2
};

// 不可变(线程安全/共享)
using System.Collections.Immutable;
var frozen = nums.ToImmutableList();
✅ 最佳实践
90% 的场景 List<T> + Dictionary<K,V> 就够了;需要去重用 HashSet;注意 Dictionary 的键必须唯一且不可变。

C# 泛型(Generic)

泛型(Generic):编写类型无关的代码——「用类型占位符 T 定义,使用时指定具体类型」。是集合、LINQ、异步的基础。

为什么需要泛型

// 非泛型: 每种类型写一遍(或装箱损失性能)
class IntBox { public int Value { get; set; } }
class StringBox { public string? Value { get; set; } }

// 泛型: 一份代码通用
class Box
{
    public T? Value { get; set; }
}

var intBox = new Box { Value = 42 };
var strBox = new Box { Value = "hello" };

泛型方法

// 泛型方法: 交换两个值
public static void Swap(ref T a, ref T b)
{
    T temp = a;
    a = b;
    b = temp;
}

int x = 1, y = 2;
Swap(ref x, ref y);       // T 推断为 int

string s1 = "a", s2 = "b";
Swap(ref s1, ref s2);     // T 推断为 string

// 泛型打印
public static void PrintAll(IEnumerable items)
{
    foreach (var item in items)
        Console.WriteLine(item);
}

泛型约束

// where T : 约束
public class Repository where T : class        // 引用类型
{ }

public class ValueRepo where T : struct        // 值类型
{ }

// 接口约束 + new()
public class Factory where T : IComparable, new()
{
    public T Create() => new T();
}

// 多约束
public void Process(T item)
    where T : class, IDisposable, new()
{
    using var obj = new T();
}

// 数值运算(现代 C# 支持静态抽象成员)
public static T Add(T a, T b) where T : INumber
    => a + b;

Console.WriteLine(Add(3, 4));        // 7
Console.WriteLine(Add(1.5, 2.5));    // 4.0

泛型类实战

public class Stack
{
    private readonly List _items = new();

    public void Push(T item) => _items.Add(item);
    public T Pop()
    {
        T item = _items[^1];
        _items.RemoveAt(_items.Count - 1);
        return item;
    }
    public T Peek() => _items[^1];
    public int Count => _items.Count;
}

var stack = new Stack();
stack.Push(10); stack.Push(20);
Console.WriteLine(stack.Pop());    // 20

泛型与性能

// 泛型避免装箱: List 直接存值
var list = new List();        // ✅ 无装箱

// 非泛型 ArrayList 会装箱
System.Collections.ArrayList old = new();
old.Add(42);                       // int → object 装箱 ❌

// 泛型是编译期生成具体类型 → 类型安全 + 零开销
✅ 记忆
泛型 = 「类型参数化」:T 是占位符,使用时替换成具体类型。集合类、算法类、仓储类通用写法。

C# 匿名方法

匿名方法 / Lambda:不声明名字的方法,内联定义,配合委托使用。

Lambda 基础

// Lambda: 参数 => 表达式
Func square = x => x * x;
Func add = (a, b) => a + b;

// 无参
Action hello = () => Console.WriteLine("Hello");

// 语句体(多行)
Action show = name =>
{
    Console.WriteLine($"开始处理 {name}");
    Console.WriteLine("完成");
};

// 使用
Console.WriteLine(square(5));      // 25
Console.WriteLine(add(2, 3));      // 5
show("订单");

匿名方法(旧语法)

// delegate 关键字匿名方法
Func doubleIt = delegate (int x)
{
    return x * 2;
};
Console.WriteLine(doubleIt(4));    // 8

闭包(捕获外部变量)

int factor = 3;

// Lambda 捕获外部变量 factor
Func multiply = x => x * factor;

Console.WriteLine(multiply(10));   // 30

factor = 5;                        // 捕获的是引用!
Console.WriteLine(multiply(10));   // 50 (值已变)

Lambda 在 LINQ 中的应用

var numbers = new List { 1, 2, 3, 4, 5, 6 };

// 筛选
var evens = numbers.Where(n => n % 2 == 0);      // 2,4,6
// 映射
var squared = numbers.Select(n => n * n);        // 1,4,9,...
// 排序
var desc = numbers.OrderByDescending(n => n);
// 聚合
int sum = numbers.Aggregate((a, b) => a + b);    // 21
// 分组
var groups = numbers.GroupBy(n => n % 3);        // 按余数分组
// 是否存在
bool any = numbers.Any(n => n > 5);              // true

Lambda 与排序

var people = new List<(string Name, int Age)>
{
    ("张三", 30),
    ("李四", 22),
    ("王五", 35)
};

// 按年龄升序
var byAge = people.OrderBy(p => p.Age);

// 自定义比较
people.Sort((a, b) => a.Age.CompareTo(b.Age));

// 多级排序
var multi = people
    .OrderByDescending(p => p.Age)
    .ThenBy(p => p.Name);
✅ 注意
Lambda 捕获外部变量是按引用(闭包);在循环中捕获循环变量要注意(老版本坑,现代 C# 已修复)。

C# 不安全代码

不安全代码(unsafe):绕过托管环境直接操作指针,用于高性能场景或互操作。默认不推荐,需显式开启。

启用 unsafe

<!-- csproj 中 -->
<PropertyGroup>
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

基本指针操作

unsafe
{
    int value = 42;
    int* p = &value;          // 取地址

    Console.WriteLine(*p);    // 42 解引用
    *p = 100;                 // 通过指针修改
    Console.WriteLine(value); // 100
}

fixed 固定内存

unsafe
{
    int[] arr = { 10, 20, 30, 40 };

    // fixed: 防止 GC 移动数组, 才能取指针
    fixed (int* p = arr)
    {
        for (int i = 0; i < arr.Length; i++)
        {
            Console.Write(*(p + i) + " ");   // 指针算术
        }
        // 10 20 30 40
    }
}

指针与数组

unsafe
{
    int[] nums = { 1, 2, 3, 4, 5 };
    fixed (int* start = nums)
    {
        int* p = start;
        Console.WriteLine(*p);       // 1
        Console.WriteLine(*(p + 2)); // 3 (指针偏移)
        Console.WriteLine(p[4]);     // 5 (下标语法)

        // 遍历
        for (int i = 0; i < 5; i++)
            Console.Write(*(start + i));
    }
}

String 指针互操作

unsafe
{
    string s = "hello";
    fixed (char* p = s)
    {
        for (int i = 0; i < s.Length; i++)
            Console.Write(*(p + i));   // h e l l o
    }
}

为什么要用 unsafe

✅ 高性能场景(图像处理、数值计算、游戏引擎)
✅ 调用原生 C/C++ 库(互操作) 
✅ 需要内存布局控制(序列化、协议)

❌ 增加风险: 指针错误 = 崩溃/内存损坏
❌ 失去 GC 保护, 需手动管理
❌ 不是所有平台支持(如浏览器 WASM 部分限制)

stackalloc(栈上分配)

unsafe
{
    // 栈上分配数组(避免 GC)
    Span buffer = stackalloc int[100];
    for (int i = 0; i < 100; i++)
        buffer[i] = i * 2;

    Console.WriteLine(buffer[50]);   // 100
}

// 现代 C# 用 Span 更安全, 不需要 unsafe
⚠️ 警告
unsafe 是双刃剑:90% 的场景用 Span<T>、Marshal 等安全替代即可。仅在确需极致性能或互操作时使用,并做好边界检查。

C# 多线程

C# 并发编程:Task / async-await / Parallel / lock——让程序同时做多件事。

Task 基础

// 创建任务
Task task = Task.Run(() =>
{
    Thread.Sleep(1000);
    Console.WriteLine("后台任务完成");
});

// 等待
task.Wait();   // 阻塞直到完成

// 有返回值
Task calc = Task.Run(() => 3 * 4);
int result = calc.Result;      // 12

// 多个任务
Task t1 = Task.Run(Work1);
Task t2 = Task.Run(Work2);
Task.WaitAll(t1, t2);          // 全部完成

async / await(推荐)

using System.Net.Http;

public static async Task Main()
{
    await RunAsync();
}

static async Task RunAsync()
{
    Console.WriteLine("开始下载");
    using var client = new HttpClient();

    // await: 不阻塞线程, 完成后继续
    string html = await client.GetStringAsync(
        "https://example.com");

    Console.WriteLine($"下载完成, {html.Length} 字符");
}

// async 方法规范:
// - 返回 Task(无值) / Task(有值)
// - 内部必须有 await(否则警告)
// - 命名以 Async 结尾

Parallel(数据并行)

// 并行处理集合
Parallel.For(0, 100, i =>
{
    Console.WriteLine($"处理 {i}");
});

// 并行 LINQ
var results = Enumerable.Range(1, 1000)
    .AsParallel()
    .Where(n => n % 2 == 0)
    .Select(n => n * n)
    .ToList();

// 控制并发度
var options = new ParallelOptions { MaxDegreeOfParallelism = 4 };
Parallel.For(0, 100, options, i => Work(i));

线程安全与锁

class Counter
{
    private int _count;
    private readonly object _lock = new();

    public void Increment()
    {
        lock (_lock)          // 互斥锁
        {
            _count++;
        }
    }

    public int Value => _count;
}

// 并发安全集合
using System.Collections.Concurrent;
var dict = new ConcurrentDictionary();
var bag = new ConcurrentBag();

// Interlocked(原子操作)
Interlocked.Increment(ref _count);

Thread 直接使用(低层)

Thread t = new(() =>
{
    for (int i = 0; i < 5; i++)
        Console.WriteLine($"线程: {i}");
});

t.IsBackground = true;    // 后台线程
t.Start();
t.Join();                 // 等待

Thread.Sleep(100);        // 当前线程休眠

选型建议

场景方案
IO 操作(网络/文件)async/await ✅
CPU 密集计算Parallel / Task.Run
共享数据修改lock / Concurrent 集合
定时/后台任务Timer / BackgroundService
✅ 要点
IO 密集用 async/await(不占线程);CPU 密集用 Parallel;共享状态要加锁;优先无共享的「数据并行」。

C# 语言测验

通过自测题检验本教程学习成果,覆盖基础语法、面向对象、高级特性。

一、选择题

1. 下列哪个是 C# 入口方法的正确签名?
   A. static void Main()      ✅
   B. public Main()
   C. static Main(int)
   D. void main()

2. 值类型与引用类型的区别?
   A. 值类型存值, 引用类型存地址   ✅

3. string 是否可变?
   A. 可变  B. 不可变(每次生成新对象)  ✅

4. 接口与抽象类的区别?
   A. 接口纯契约可多实现; 抽象类可有实现单继承  ✅

5. 委托的用途?
   A. 把方法当参数传递  ✅

6. List 与数组的区别?
   A. List 长度可变, 数组固定  ✅

7. async/await 的好处?
   A. IO 操作不阻塞线程  ✅

8. 哪个集合去重且 O(1) 查找?
   A. HashSet  ✅

二、补全代码

// 1. 声明一个只读属性
public string Name { ______; }        // get;

// 2. 泛型方法: 交换
public static void Swap(ref T a, ref T b) { ______ }

// 3. Lambda 筛选偶数
var evens = nums.______(n => n % 2 == 0);   // Where

// 4. 事件订阅
btn.Click ______ (s, e) => Save();          // +=

// 5. 空值兜底
string display = name ______ "默认";        // ??

三、判断题

1. C# 区分大小写                       ✅ 对
2. struct 是引用类型                   ❌ 错(值类型)
3. 接口可以包含字段                     ❌ 错(不能)
4. const 字段编译期确定                 ✅ 对
5. 泛型可以避免装箱                     ✅ 对
6. 事件外部可以直接触发 Invoke         ❌ 错
7. foreach 中可以修改集合               ❌ 错
8. ref 参数可以修改原变量               ✅ 对
9. lambda 捕获外部变量按引用            ✅ 对
10. unsafe 代码默认允许                 ❌ 错(需开启)

四、编程题

// 1. 实现一个简单 BankAccount: 存款/取款/余额(带校验)
// 2. 用 async/await 模拟一次网络请求
// 3. 用 LINQ 从集合筛选 + 排序 + 求和
// 4. 定义接口 IShape 并实现 Circle/Rectangle
// 5. 用 event 实现"订单完成通知"

// 参考答案(思路):
// 1. private decimal _balance + 方法校验
// 2. await Task.Delay(1000) 模拟
// 3. list.Where(...).OrderBy(...).Sum()
// 4. interface IShape { double Area(); }
// 5. event EventHandler Completed

成绩对照

得分评价
90-100🎉 掌握扎实,可以实战项目了
70-89👍 基础良好,复习薄弱章节
50-69📖 建议回看对应章节
<50🔁 重新过一遍教程,动手敲代码
✅ 结语
学编程没有捷径——多写、多调试、多读优秀代码。C# 生态成熟,动手做一个管理后台或小工具项目,会让知识真正内化。
第 1 / 42 节