C# 学习文档(零基础入门)

适用版本:.NET 8 / C# 12(文中会注明新语法与传统写法的区别)

阅读方式:按章节顺序阅读,每章的代码都可直接复制运行


目录

  1. 认识 C# 与 .NET
  2. 基础语法:变量与数据类型
  3. 运算符与类型转换
  4. 流程控制
  5. 数组与字符串
  6. 方法(函数)
  7. 面向对象基础:类与对象
  8. 面向对象进阶:继承、多态与接口
  9. 常用集合:List 与 Dictionary
  10. 异常处理
  11. 现代 C# 特性一览
  12. 实战小项目
  13. 学习路线与资源

1. 认识 C# 与 .NET

1.1 C# 是什么?

C#(读作 "C Sharp")是微软设计的一门现代编程语言。用它你可以开发:

应用类型 说明
桌面软件 Windows 上的 WPF、WinForms 应用
网站 / 后端服务 ASP.NET Core(很多大公司的网站后端)
游戏 Unity 引擎的首选语言(大量手机游戏用它开发)
手机 App .NET MAUI(一套代码同时出 Android / iOS)
人工智能 / 云 ML.NET、Azure 云服务

1.2 几个基本概念(大白话版)

  • .NET:一个"平台",包含运行环境和一大堆现成的工具库。C# 代码要在 .NET 上运行,就像鱼要在水里游。
  • CLR(公共语言运行时):.NET 的"引擎",负责运行你的代码、管理内存(所以 C# 不需要像 C++ 那样手动释放内存)。
  • .NET SDK :开发工具包,包含编译器、运行时和命令行工具。写 C# 第一步就是装它
  • IDE(开发工具) :写代码的软件。推荐 Visual Studio (功能全,新手友好)或 VS Code + C# 扩展(轻量)。

1.3 搭建环境

  1. 下载 .NET SDK:https://dotnet.microsoft.com/download(选择 .NET 8.0 或更高)
  2. 安装后打开终端(PowerShell 或 CMD),验证:
bash 复制代码
dotnet --version   # 输出版本号说明安装成功
  1. 创建并运行第一个项目:
bash 复制代码
dotnet new console -o MyFirstApp   # 创建一个控制台项目
cd MyFirstApp
dotnet run                          # 编译并运行

你会看到输出:Hello, World!

1.4 第一个程序解读

Program.cs 的内容(.NET 6+ 的极简写法):

csharp 复制代码
Console.WriteLine("Hello, World!");

就这么一行!早期版本(.NET 5 及以前)必须写完整的类和 Main 方法:

csharp 复制代码
using System;

namespace MyFirstApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

两种写法效果完全一样。新版本编译器自动帮你把顶层语句放进 Main 方法。本教程使用简洁写法,但在第 7 章你也会学到传统结构------它是理解 C# 的必修课。


2. 基础语法:变量与数据类型

2.1 变量:存放数据的"盒子"

csharp 复制代码
int age = 25;              // 整数
double height = 1.75;      // 小数(双精度)
string name = "小明";       // 字符串(一段文字)
bool isStudent = true;     // 布尔值(真 / 假)
Console.WriteLine($"我叫{name},{age}岁,身高{height}米");

规则:

  • 变量必须先声明再使用 ,格式:类型 变量名 = 值;
  • 语句以分号 ; 结尾
  • C# 区分大小写:ageAge 是两个不同的变量
  • 变量名只能由字母、数字、下划线组成,且不能以数字开头 ,不能用关键字(如 intclass

2.2 常用数据类型

类型 说明 示例 备注
int 整数 100, -5 约 ±21 亿
long 大整数 10000000000L 非常大
double 小数 3.14 15~16 位有效数字
decimal 精确小数 3.14m 算钱用它,28 位精度
bool 真 / 假 true, false ---
char 单个字符 'A' 单引号,只能一个字符
string 字符串 "你好" 双引号

⚠️ 涉及金额计算不要用 double(会有精度误差),要用 decimal(值后面加 m)。

2.3 常量与 var

csharp 复制代码
const double Pi = 3.14159;  // 常量:一旦赋值不能再改,名字习惯全大写
var count = 10;             // var:编译器自动推断类型,count 就是 int
var msg = "你好";            // msg 就是 string

var 只是让你少打字,C# 仍是强类型语言 ------var count = 10; 之后不能 count = "abc";

2.4 控制台输入输出

csharp 复制代码
Console.Write("请输入你的名字:");          // 不换行
string name = Console.ReadLine();          // 读取用户输入的一行(返回字符串)
Console.WriteLine($"你好,{name}!");        // 输出并换行

// 输入数字需要转换(见第 3 章)
Console.Write("请输入年龄:");
int age = int.Parse(Console.ReadLine());   // 把字符串转成 int

💡 $"..."字符串插值 :在字符串里用 {变量名} 嵌入变量,非常好用。


3. 运算符与类型转换

3.1 算术运算符

csharp 复制代码
int a = 10, b = 3;
Console.WriteLine(a + b);    // 13  加
Console.WriteLine(a - b);    // 7   减
Console.WriteLine(a * b);    // 30  乘
Console.WriteLine(a / b);    // 3   除(整数相除会丢掉小数!)
Console.WriteLine(a % b);    // 1   取余数
Console.WriteLine(10.0 / 3); // 3.333...(有小数参与才是小数除法)

⚠️ 新手最常踩的坑:10 / 3 结果是 3 不是 3.33。想得到小数,把其中一个写成小数:10 / 3.0

自增与复合赋值:

csharp 复制代码
int i = 5;
i++;    // i 变成 6,等价于 i = i + 1
i--;    // i 变成 5
i += 3; // i = i + 3 → 8
i *= 2; // i = i * 2 → 16

3.2 比较与逻辑运算符

csharp 复制代码
int x = 5;
bool r1 = x > 3;           // true
bool r2 = x == 5;          // true(判断相等用 ==,不是 =!)
bool r3 = x != 5;          // false(不等于)
bool r4 = x > 3 && x < 10; // true(并且:两边都真才真)
bool r5 = x < 3 || x > 4;  // true(或者:一边真就真)
bool r6 = !(x > 3);        // false(取反)

⚠️ = 是赋值,== 是比较。把 if (x = 5) 写出来在 C# 里会直接报错。

3.3 类型转换

自动转换(小 → 大,不丢数据):

csharp 复制代码
int i = 100;
double d = i;    // OK,int 自动变 double

强制转换(大 → 小,可能丢数据):

csharp 复制代码
double d = 3.99;
int i = (int)d;                  // 3(直接砍掉小数,不四舍五入)
int rounded = (int)Math.Round(d); // 4(想四舍五入用 Math.Round)

字符串 ↔ 数值:

csharp 复制代码
string s = "123";
int n1 = int.Parse(s);         // 字符串 → int
int n2 = Convert.ToInt32(s);   // 效果类似

int n = 456;
string s2 = n.ToString();      // 数字 → 字符串

// 安全转换:用户输入可能不是数字,用 TryParse 避免崩溃
if (int.TryParse(Console.ReadLine(), out int result))
    Console.WriteLine($"你输入的数字是 {result}");
else
    Console.WriteLine("这不是一个合法的数字!");

💡 处理用户输入时优先用 TryParse,它不会因输入非法而报错崩溃。


4. 流程控制

4.1 if / else if / else

csharp 复制代码
int score = 85;

if (score >= 90)
    Console.WriteLine("优秀");
else if (score >= 80)
    Console.WriteLine("良好");     // 会输出这句
else if (score >= 60)
    Console.WriteLine("及格");
else
    Console.WriteLine("不及格");

如果分支里有多条语句,要用大括号包起来:

csharp 复制代码
if (score >= 60)
{
    Console.WriteLine("及格");
    Console.WriteLine("继续努力");
}

4.2 switch

适合"一个变量对多个固定值"的判断:

csharp 复制代码
int day = 3;
switch (day)
{
    case 1:
        Console.WriteLine("星期一");
        break;                      // 每个 case 结尾必须 break
    case 6:
    case 7:                         // 多个值共用一段逻辑
        Console.WriteLine("周末");
        break;
    default:                        // 都不匹配时执行
        Console.WriteLine("工作日");
        break;
}

4.3 for 循环

适合已知次数的重复:

csharp 复制代码
// 输出 1~5
for (int i = 1; i <= 5; i++)
{
    Console.WriteLine($"第 {i} 次循环");
}

// 计算 1+2+...+100
int sum = 0;
for (int i = 1; i <= 100; i++)
{
    sum += i;
}
Console.WriteLine(sum);   // 5050

结构解读:for (初始化; 循环条件; 每轮结束后执行)

4.4 while 与 do-while

适合不知道循环多少次的场景:

csharp 复制代码
// while:先判断再执行(可能一次都不执行)
int n = 1;
while (n <= 5)
{
    Console.WriteLine(n);
    n++;
}

// do-while:先执行一次再判断(至少执行一次)
int input;
do
{
    Console.WriteLine("输入 0 退出:");
    input = int.Parse(Console.ReadLine());
} while (input != 0);

4.5 break 与 continue

csharp 复制代码
for (int i = 1; i <= 10; i++)
{
    if (i == 3) continue;  // 跳过本轮,进入下一轮(3 不输出)
    if (i == 6) break;     // 直接结束整个循环
    Console.WriteLine(i);  // 输出 1 2 4 5
}

4.6 随机数(配合循环很好玩)

csharp 复制代码
Random random = new Random();
int dice = random.Next(1, 7);   // 1~6 的随机数(含1不含7)
Console.WriteLine($"骰子点数:{dice}");

5. 数组与字符串

5.1 一维数组

数组是固定长度、装同一种类型数据的容器:

csharp 复制代码
// 三种写法
int[] scores = { 90, 85, 78, 92, 66 };       // 直接给值(最常用)
int[] nums = new int[5];                     // 长度为5,默认全是 0
string[] names = new string[] { "张三", "李四" };

// 通过下标访问(下标从 0 开始!)
Console.WriteLine(scores[0]);        // 90(第一个元素)
Console.WriteLine(scores[4]);        // 66(最后一个元素 = 长度-1)
Console.WriteLine(scores.Length);    // 5(数组长度)

scores[1] = 88;                      // 修改元素

// 遍历方式一:下标循环(需要下标时用)
for (int i = 0; i < scores.Length; i++)
    Console.WriteLine($"第{i+1}个成绩:{scores[i]}");

// 遍历方式二:foreach(更简洁,但拿不到下标)
foreach (int s in scores)
    Console.WriteLine(s);

⚠️ 下标越界会崩溃:长度为 5 的数组访问 scores[5] 会抛出异常。

5.2 二维数组

csharp 复制代码
int[,] matrix = { { 1, 2, 3 }, { 4, 5, 6 } };  // 2行3列
Console.WriteLine(matrix[0, 1]);                // 2(第0行第1列)
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] + " ");      // 1 2 3 4 5 6

5.3 常用数组操作

csharp 复制代码
int[] arr = { 5, 2, 8, 1, 9 };
Array.Sort(arr);                      // 排序:1 2 5 8 9
Array.Reverse(arr);                   // 反转:9 8 5 2 1
Console.WriteLine(arr.Max());         // 9(最大值,需 System.Linq)
Console.WriteLine(arr.Min());         // 1(最小值)
Console.WriteLine(Array.IndexOf(arr, 8)); // 找下标,找不到返回 -1

5.4 字符串常用操作

csharp 复制代码
string s = "Hello, C#";

Console.WriteLine(s.Length);              // 9(长度)
Console.WriteLine(s[0]);                  // H(按下标取字符)
Console.WriteLine(s.ToUpper());           // HELLO, C#
Console.WriteLine(s.ToLower());           // hello, c#
Console.WriteLine(s.Contains("C#"));      // True(是否包含)
Console.WriteLine(s.Substring(7));        // C#(从第7位截到末尾)
Console.WriteLine(s.Substring(0, 5));     // Hello(从0位截5个字符)
Console.WriteLine(s.Replace("C#", "World")); // Hello, World
Console.WriteLine("  hi  ".Trim());       // hi(去掉首尾空格)
Console.WriteLine(string.Join("-", "a", "b", "c")); // a-b-c

string[] parts = "张三,李四,王五".Split(',');  // 按逗号拆成数组
foreach (string p in parts)
    Console.WriteLine(p);

Console.WriteLine("abc" == "abc");        // True(字符串比较内容用 ==)

⚠️ 字符串是不可变 的:s.ToUpper() 不会修改 s,而是返回一个新字符串,要用变量接住返回值。


6. 方法(函数)

方法 = 把一段代码打包命名,随时调用,避免重复。

6.1 定义与调用

csharp 复制代码
// 格式:返回类型 方法名(参数列表) { ... return 值; }
int Add(int a, int b)
{
    return a + b;
}

// 没有返回值用 void
void PrintHello(string name)
{
    Console.WriteLine($"你好,{name}!");
}

// 调用
int result = Add(3, 5);
Console.WriteLine(result);      // 8
PrintHello("小明");

6.2 参数的几种形式

csharp 复制代码
// 默认参数值
void Greet(string name, string greeting = "你好")
{
    Console.WriteLine($"{greeting},{name}!");
}
Greet("张三");            // 你好,张三!
Greet("李四", "早上好");   // 早上好,李四!

// params:参数个数不限
int Sum(params int[] numbers)
{
    int total = 0;
    foreach (int n in numbers) total += n;
    return total;
}
Console.WriteLine(Sum(1, 2, 3, 4));   // 10

// 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, 2, out int q))
    Console.WriteLine($"商是 {q}");   // 商是 5

6.3 方法重载与简写

同名方法、参数不同,编译器根据传入参数自动选择:

csharp 复制代码
int Add2(int a, int b) => a + b;
double Add2(double a, double b) => a + b;    // 参数类型不同
int Add2(int a, int b, int c) => a + b + c;  // 参数个数不同

Console.WriteLine(Add2(1, 2));        // 3
Console.WriteLine(Add2(1.5, 2.5));    // 4
Console.WriteLine(Add2(1, 2, 3));     // 6

💡 => a + b; 是"表达式方法"简写,等价于 { return a + b; },单行方法的常用写法。

6.4 递归(进阶,看不懂可跳过)

方法调用自己:

csharp 复制代码
long Factorial(int n)
{
    if (n <= 1) return 1;          // 递归出口,必须有,否则无限递归
    return n * Factorial(n - 1);
}
Console.WriteLine(Factorial(5));   // 120(5×4×3×2×1)

7. 面向对象基础:类与对象

7.1 为什么需要"类"?

前面的变量都是"散装"的:string name; int age;------如果要描述 100 个学生,就得管理几百个变量。把这些相关的数据和行为打包到一起。

  • 类(class):图纸 / 模板,描述"学生有哪些属性、能做什么"
  • 对象(object):按图纸造出来的具体东西,"张三这个学生"

7.2 定义第一个类

csharp 复制代码
public class Student
{
    // 字段(类的内部数据,一般设为 private)
    private string name;

    // 属性:外部访问数据的"窗口"
    public string Name
    {
        get { return name; }       // 读
        set { name = value; }      // 写(value 是隐含的"传入值")
    }

    // 自动属性(最常用简写,编译器自动生成隐藏字段)
    public int Age { get; set; }

    // 只读属性:只能在构造函数里赋值
    public string StudentId { get; }

    // 构造函数:创建对象时自动调用,用于初始化
    public Student(string name, int age, string id)
    {
        this.name = name;      // this. 表示"当前对象的"
        Age = age;
        StudentId = id;
    }

    // 无参构造函数(一旦写了有参构造,默认的无参构造就没了,需要手动补)
    public Student() { }

    // 方法(类的行为)
    public void Introduce()
    {
        Console.WriteLine($"我是{Name},{Age}岁,学号{StudentId}");
    }
}

使用:

csharp 复制代码
Student s1 = new Student("张三", 20, "2024001");   // new 创建对象
Student s2 = new Student("李四", 22, "2024002");

s1.Introduce();      // 我是张三,20岁,学号2024001
s2.Introduce();
Console.WriteLine(s1.Name);   // 张三

💡 C# 9+ 还支持更简的目标类型 new:Student s1 = new("张三", 20, "2024001");

7.3 访问修饰符

控制"谁能访问这个成员":

修饰符 谁能访问
public 任何地方
private 只有类自己内部(默认
protected 类自己 + 子类
internal 同一个项目内

习惯:字段用 private,对外暴露用属性 public

7.4 静态成员(static)

属于类本身而不是某个对象,直接用类名调用:

csharp 复制代码
public class Calculator
{
    public static double Pi = 3.14159;        // 静态字段

    public static double CircleArea(double r) // 静态方法
        => Pi * r * r;
}

// 不需要 new,直接用类名调用
Console.WriteLine(Calculator.Pi);
Console.WriteLine(Calculator.CircleArea(2));  // 12.566...

⚠️ Console.WriteLine 里的 Console 就是一个静态类,WriteLine 是静态方法。你已经用过很多次静态成员了!

7.5 结构体 struct 与值类型/引用类型(了解即可)

csharp 复制代码
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

核心区别:

csharp 复制代码
// struct 是值类型:赋值时复制整个内容,互不影响
Point p1 = new Point { X = 1, Y = 2 };
Point p2 = p1;
p2.X = 99;
Console.WriteLine(p1.X);   // 1(没被影响)

// class 是引用类型:赋值复制的是"地址",两个变量指向同一对象
Student a = new Student("张三", 20, "001");
Student b = a;
b.Age = 99;
Console.WriteLine(a.Age);  // 99(被影响了!)

简单的小数据结构(如坐标、颜色)用 struct,其余一律用 class。

7.6 命名空间 namespace

避免类名冲突的"文件夹":

csharp 复制代码
namespace MyShop.Models     // 逻辑上的分组路径
{
    public class Product { }
}

// 使用时在文件顶部引入
// using MyShop.Models;
// var p = new Product();

// 或者写全名
MyShop.Models.Product p2 = new MyShop.Models.Product();

8. 面向对象进阶:继承、多态与接口

8.1 继承

子类自动获得父类的属性和方法,实现代码复用。C# 只有单继承(一个类只能有一个父类):

csharp 复制代码
public class Animal
{
    public string Name { get; set; }

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

    public void Eat() => Console.WriteLine($"{Name} 在吃东西");
}

public class Dog : Animal            // Dog 继承 Animal
{
    public Dog(string name) : base(name) { }   // base 调用父类构造函数

    public void Bark() => Console.WriteLine($"{Name} 汪汪叫");
}

// 使用
Dog dog = new Dog("旺财");
dog.Eat();    // 继承来的方法:旺财 在吃东西
dog.Bark();   // 自己的方法:旺财 汪汪叫

8.2 虚方法与多态

多态:同一个调用,不同对象有不同的行为。

csharp 复制代码
public class Animal
{
    public string Name { get; set; }
    public Animal(string name) { Name = name; }

    // virtual:允许子类重写
    public virtual void MakeSound()
        => Console.WriteLine($"{Name} 发出了声音");
}

public class Dog : Animal
{
    public Dog(string name) : base(name) { }

    // override:重写父类方法
    public override void MakeSound()
        => Console.WriteLine($"{Name}:汪汪!");
}

public class Cat : Animal
{
    public Cat(string name) : base(name) { }

    public override void MakeSound()
        => Console.WriteLine($"{Name}:喵~");
}

// 多态的威力:父类变量可以装子类对象
List<Animal> animals = new List<Animal>
{
    new Dog("旺财"),
    new Cat("咪咪"),
    new Dog("小黑")
};

foreach (Animal a in animals)
    a.MakeSound();     // 实际执行的是各自重写后的版本!
// 输出:旺财:汪汪! / 咪咪:喵~ / 小黑:汪汪!

8.3 抽象类 abstract

当父类"不知道具体怎么实现"时,声明为抽象方法,强制子类去实现

csharp 复制代码
public abstract class Shape
{
    public abstract double GetArea();     // 抽象方法:没有方法体

    public void Print()                    // 抽象类也可以有普通方法
        => Console.WriteLine($"面积是 {GetArea():F2}");
}

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

    public override double GetArea()       // 必须实现,否则编译报错
        => Math.PI * Radius * Radius;
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public Rectangle(double w, double h) { Width = w; Height = h; }

    public override double GetArea() => Width * Height;
}

// Shape s = new Shape();   // ❌ 抽象类不能实例化
Shape shape = new Circle(3);
shape.Print();              // 面积是 28.27

virtual vs abstractvirtual 是"我有默认实现,子类可以改";abstract 是"我没实现,子类必须实现"。

8.4 接口 interface

接口是一份"合同":规定类必须具备哪些能力,不管它是什么。

csharp 复制代码
public interface ISwimmable           // 接口命名习惯以 I 开头
{
    void Swim();                      // 接口成员默认是抽象的
}

public interface IFlyable
{
    void Fly();
}

// 一个类只能继承一个父类,但可以实现多个接口
public class Duck : Animal, ISwimmable, IFlyable
{
    public Duck(string name) : base(name) { }

    public void Swim() => Console.WriteLine($"{Name} 在游泳");
    public void Fly()  => Console.WriteLine($"{Name} 在飞");
}

Duck duck = new Duck("唐老鸭");
duck.Swim();   // 唐老鸭 在游泳
duck.Fly();    // 唐老鸭 在飞

抽象类 vs 接口怎么选?

  • 抽象类:一组密切相关的类,有共享的代码和数据 → "是什么"(is-a)
  • 接口:不相关的类拥有同种能力 → "能做什么"(can-do)

8.5 记录类型 record(C# 9+,现代写法)

适合表示"纯数据"的不可变对象,一行顶几十行:

csharp 复制代码
public record Person(string Name, int Age);

var p = new Person("张三", 20);
Console.WriteLine(p);                    // Person { Name = 张三, Age = 20 }
var p2 = p with { Age = 21 };            // 复制一份并修改(原对象不变)
Console.WriteLine(p2.Age);               // 21

9. 常用集合:List 与 Dictionary

数组长度固定,实际开发中更常用可变长度的集合

9.1 List:可变长度的数组

csharp 复制代码
using System.Collections.Generic;

List<string> fruits = new List<string>();
// 简写:var fruits = new List<string>();

fruits.Add("苹果");                // 添加到末尾
fruits.Add("香蕉");
fruits.Add("橘子");
fruits.Insert(1, "葡萄");          // 插入到指定位置
fruits.Remove("香蕉");             // 按值删除(删除成功返回 true)
fruits.RemoveAt(0);                // 按下标删除
Console.WriteLine(fruits.Count);   // 2(当前元素个数)
Console.WriteLine(fruits[0]);      // 葡萄
Console.WriteLine(fruits.Contains("橘子"));   // True
Console.WriteLine(fruits.IndexOf("橘子"));    // 1

foreach (string f in fruits)
    Console.WriteLine(f);

List、List、List......尖括号里的类型表示"这个列表装什么"。

9.2 Dictionary<K,V>:字典 / 键值对

像真正的字典:通过"键"快速查"值"。

csharp 复制代码
Dictionary<string, int> ages = new Dictionary<string, int>();
ages.Add("张三", 20);
ages["李四"] = 22;                     // 更常用的添加/修改方式
ages["张三"] = 21;                     // 键已存在则是修改

Console.WriteLine(ages["李四"]);        // 22
Console.WriteLine(ages.ContainsKey("王五"));  // False(先判断再访问,避免报错)

if (ages.TryGetValue("张三", out int age))
    Console.WriteLine($"张三 {age} 岁");  // 张三 21 岁

ages.Remove("李四");
Console.WriteLine(ages.Count);          // 1

// 遍历(KeyValuePair 包含 Key 和 Value)
foreach (var kv in ages)
    Console.WriteLine($"{kv.Key}:{kv.Value} 岁");

9.3 其他常用集合(了解)

集合 特点 适用场景
List<T> 有序、可变长、按下标访问 最常用,80% 场景
Dictionary<K,V> 键值对、按键查找极快 按名字/编号查数据
HashSet<T> 不重复 去重
Queue<T> 先进先出(排队) 任务队列
Stack<T> 后进先出(叠盘子) 撤销操作、递归模拟
SortedList<K,V>/SortedDictionary 自动按键排序 需要有序键值对

10. 异常处理

程序运行出错(比如用户输入了 "abc" 你却当数字解析)会"抛异常",不处理就直接崩溃。try/catch 用来兜住错误:

csharp 复制代码
try
{
    // 可能出错的代码放这里
    Console.Write("输入一个数字:");
    int n = int.Parse(Console.ReadLine());
    Console.WriteLine($"你输入的是 {n}");
}
catch (FormatException)                  // 捕获特定类型的异常:格式错误
{
    Console.WriteLine("输入的不是数字!");
}
catch (Exception ex)                     // Exception 是所有异常的基类,兜底
{
    Console.WriteLine($"出错了:{ex.Message}");
}
finally
{
    // 无论是否出错都会执行(常用于清理资源,如关闭文件)
    Console.WriteLine("程序结束");
}

10.1 主动抛出异常

csharp 复制代码
int Divide(int a, int b)
{
    if (b == 0)
        throw new ArgumentException("除数不能为 0");   // throw 主动抛出
    return a / b;
}

try
{
    Console.WriteLine(Divide(10, 0));
}
catch (ArgumentException ex)
{
    Console.WriteLine(ex.Message);   // 除数不能为 0
}

10.2 常见异常类型

异常 什么时候出现
FormatException int.Parse("abc") 转换失败
DivideByZeroException 整数除以 0
IndexOutOfRangeException 数组下标越界
NullReferenceException 使用了 null 对象(新手最常见的崩溃
KeyNotFoundException 访问字典中不存在的键

💡 原则:能提前判断的就用 if/TryParse 预防;无法预判的(文件被删、网络断开)才用 try/catch。不要用 try/catch 包住所有代码来"掩盖"错误。

10.3 可空类型与 null(进阶)

csharp 复制代码
string? maybe = null;                 // ? 表示"可以为 null"
Console.WriteLine(maybe?.Length);     // ?. 空条件运算符:maybe 为 null 时不报错,返回 null
Console.WriteLine(maybe ?? "默认值");  // ?? 空合并:为 null 时用右边的值

string name = null;
// Console.WriteLine(name.Length);    // ❌ NullReferenceException 崩溃
Console.WriteLine(name?.Length ?? 0); // ✅ 安全,输出 0

11. 现代 C# 特性一览

这一章是"预告片",让你看懂现代 C# 代码长什么样,不必现在精通。

11.1 Lambda 表达式

"匿名小函数",常作为参数传给方法:

csharp 复制代码
// 完整写法 vs lambda 简写
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 5));   // 8

Action<string> say = msg => Console.WriteLine(msg);
say("你好");                     // 你好

11.2 LINQ:像写 SQL 一样查询集合(超好用)

csharp 复制代码
int[] numbers = { 5, 2, 8, 1, 9, 3 };

// 找出所有偶数并排序
var evens = numbers.Where(n => n % 2 == 0).OrderBy(n => n);
Console.WriteLine(string.Join(", ", evens));   // 2, 8

List<Student> students = new List<Student>
{
    new Student("张三", 20, "001"),
    new Student("李四", 22, "002"),
    new Student("王五", 19, "003"),
};

// 查询语法(像 SQL)和方法语法(链式调用)等价
var adults = from s in students where s.Age >= 20 select s.Name;
var adults2 = students.Where(s => s.Age >= 20).Select(s => s.Name);
Console.WriteLine(string.Join(", ", adults));   // 张三, 李四

var oldest = students.OrderByDescending(s => s.Age).First();
Console.WriteLine($"最年长的是 {oldest.Name}");   // 李四

Console.WriteLine(students.Average(s => s.Age));  // 平均年龄 20.33...

常用 LINQ 方法:Where(筛选)、Select(投影)、OrderBy(排序)、First/FirstOrDefault(第一个)、Any(是否存在)、CountSumAverageTake(取前 N 个)。

11.3 async/await 异步编程(初识)

耗时操作(网络请求、读大文件)用异步,避免界面卡死:

csharp 复制代码
using System.Net.Http;

// 模拟下载网页(方法用 async 修饰,返回 Task)
async Task DownloadAsync()
{
    using HttpClient client = new HttpClient();
    string html = await client.GetStringAsync("https://example.com");
    Console.WriteLine($"下载完成,共 {html.Length} 个字符");
}

await DownloadAsync();

现阶段只需记住:看到 async / await / Task 知道这是"异步"即可,学 ASP.NET Core 或桌面开发时再深入。

11.4 模式匹配与 switch 表达式(C# 8+)

csharp 复制代码
// switch 表达式(比传统 switch 简洁得多)
string Classify(int score) => score switch
{
    >= 90 => "优秀",
    >= 80 => "良好",
    >= 60 => "及格",
    _     => "不及格"
};

Console.WriteLine(Classify(85));   // 良好

// is 模式匹配
object obj = "hello";
if (obj is string s && s.Length > 3)
    Console.WriteLine($"是个长度为 {s.Length} 的字符串");

11.5 文件读写(初识)

csharp 复制代码
using System.IO;

// 一行写入 / 读取整个文件
File.WriteAllText("test.txt", "第一行\n第二行");
string content = File.ReadAllText("test.txt");
Console.WriteLine(content);

// 逐行读取
foreach (string line in File.ReadAllLines("test.txt"))
    Console.WriteLine(line);

// 判断、删除、复制
Console.WriteLine(File.Exists("test.txt"));   // True
File.Copy("test.txt", "backup.txt");
File.Delete("backup.txt");

12. 实战小项目

建议每个项目都用 dotnet new console -o 项目名 新建,把代码写进 Program.cs 运行。

12.1 项目一:猜数字游戏

知识点:Random、while、if、TryParse。程序随机生成 1~100 的数,用户猜,提示"大了/小了",猜中显示次数。

csharp 复制代码
// Program.cs(.NET 6+ 顶层语句写法)
Random random = new Random();
int target = random.Next(1, 101);    // 1~100 的随机数
int guessCount = 0;

Console.WriteLine("=== 猜数字游戏(1~100)===");

while (true)
{
    Console.Write("请输入你猜的数字:");
    if (!int.TryParse(Console.ReadLine(), out int guess))
    {
        Console.WriteLine("请输入有效的数字!");
        continue;
    }

    guessCount++;

    if (guess < target)
        Console.WriteLine("小了,再猜大一点!");
    else if (guess > target)
        Console.WriteLine("大了,再猜小一点!");
    else
    {
        Console.WriteLine($"恭喜猜中!答案是 {target},你共猜了 {guessCount} 次。");
        break;
    }
}

12.2 项目二:简易通讯录

知识点:类、List、LINQ 初步、循环菜单、字符串处理。

csharp 复制代码
// Contact.cs ------ 联系人类(和 Program.cs 放同一项目文件夹)
public class Contact
{
    public string Name { get; set; }
    public string Phone { get; set; }

    public Contact(string name, string phone)
    {
        Name = name;
        Phone = phone;
    }

    public override string ToString() => $"{Name,-8} {Phone}";
}
csharp 复制代码
// Program.cs ------ 主程序
List<Contact> contacts = new List<Contact>();

while (true)
{
    Console.WriteLine("\n===== 通讯录 =====");
    Console.WriteLine("1. 添加联系人");
    Console.WriteLine("2. 查看所有联系人");
    Console.WriteLine("3. 按姓名查找");
    Console.WriteLine("4. 删除联系人");
    Console.WriteLine("0. 退出");
    Console.Write("请选择:");

    string choice = Console.ReadLine();
    if (choice == "0") break;

    switch (choice)
    {
        case "1":
            Console.Write("姓名:");
            string name = Console.ReadLine();
            Console.Write("电话:");
            string phone = Console.ReadLine();
            contacts.Add(new Contact(name, phone));
            Console.WriteLine("添加成功!");
            break;

        case "2":
            if (contacts.Count == 0)
                Console.WriteLine("通讯录为空。");
            else
                foreach (Contact c in contacts)
                    Console.WriteLine(c);
            break;

        case "3":
            Console.Write("要找的姓名:");
            string keyword = Console.ReadLine();
            var found = contacts.Where(c => c.Name.Contains(keyword)).ToList();
            if (found.Count == 0)
                Console.WriteLine("没找到。");
            else
                found.ForEach(Console.WriteLine);
            break;

        case "4":
            Console.Write("要删除的姓名:");
            string delName = Console.ReadLine();
            var target = contacts.FirstOrDefault(c => c.Name == delName);
            if (target != null)
            {
                contacts.Remove(target);
                Console.WriteLine("删除成功!");
            }
            else
                Console.WriteLine("没找到这个人。");
            break;

        default:
            Console.WriteLine("无效选项,请重新输入。");
            break;
    }
}

12.3 项目三:记账本(带文件保存)

知识点:record、List、LINQ、文件读写。退出时保存到文件,启动时加载。

csharp 复制代码
// 记录类型:一笔账(放在单独的 Bill.cs 文件)
public record Bill(string Item, decimal Amount, DateTime Date);
csharp 复制代码
// Program.cs
string filePath = "bills.txt";
List<Bill> bills = new List<Bill>();

// 启动时加载历史账单
if (File.Exists(filePath))
{
    foreach (string line in File.ReadAllLines(filePath))
    {
        string[] parts = line.Split('|');
        if (parts.Length == 3)
            bills.Add(new Bill(parts[0], decimal.Parse(parts[1]),
                               DateTime.Parse(parts[2])));
    }
    Console.WriteLine($"已加载 {bills.Count} 条历史账单。");
}

while (true)
{
    Console.WriteLine("\n===== 记账本 =====");
    Console.WriteLine("1. 记一笔");
    Console.WriteLine("2. 查看明细和统计");
    Console.WriteLine("0. 保存并退出");
    Console.Write("请选择:");
    string choice = Console.ReadLine();

    if (choice == "0")
    {
        File.WriteAllLines(filePath,
            bills.Select(b => $"{b.Item}|{b.Amount}|{b.Date:yyyy-MM-dd HH:mm}"));
        Console.WriteLine("已保存,再见!");
        break;
    }
    else if (choice == "1")
    {
        Console.Write("项目(如:午饭):");
        string item = Console.ReadLine();
        Console.Write("金额:");
        if (decimal.TryParse(Console.ReadLine(), out decimal amount))
        {
            bills.Add(new Bill(item, amount, DateTime.Now));
            Console.WriteLine("记账成功!");
        }
        else
            Console.WriteLine("金额无效。");
    }
    else if (choice == "2")
    {
        if (bills.Count == 0)
        {
            Console.WriteLine("还没有账单。");
            continue;
        }
        foreach (var b in bills)
            Console.WriteLine($"{b.Date:MM-dd HH:mm}  {b.Item,-10} {b.Amount,10:C}");
        Console.WriteLine($"共 {bills.Count} 笔,总支出:{bills.Sum(b => b.Amount):C}");
        Console.WriteLine($"最大一笔:{bills.MaxBy(b => b.Amount).Item}");
    }
}

练习建议:先自己敲一遍(不要复制粘贴),再尝试扩展功能,比如按月份统计、按金额排序------每个扩展都会逼你用到新知识。


13. 学习路线与资源

13.1 推荐学习顺序

复制代码
第 1~6 章(基础语法)  → 大量做小练习:九九乘法表、斐波那契数列、冒泡排序
第 7~8 章(面向对象)  → 第一道坎,多画图理解"类是图纸,对象是产品"
第 9~10 章(集合/异常)→ 重写前面的练习,用 List 代替数组
第 11 章(现代特性)   → 看懂即可,写代码时逐步采用
第 12 章(小项目)     → 一定要亲手完成,编程是练出来的不是看出来的
之后选方向:
  - Web 后端   → ASP.NET Core(官方文档极佳)
  - 桌面应用   → WPF / WinUI 3 / Avalonia
  - 游戏开发   → Unity(C# 是它的官方语言)
  - 深入语言   → 泛型、委托与事件、反射、垃圾回收机制

13.2 官方资源(免费且质量高)

资源 地址 说明
C# 官方文档(中文) https://learn.microsoft.com/dotnet/csharp/ 最权威,有交互式教程
.NET 官方教程 https://learn.microsoft.com/dotnet/fundamentals/ 平台概念
浏览器里写 C# https://dotnet.microsoft.com/zh-cn/platform/try-dotnet 不装环境也能练
.NET API 浏览器 https://learn.microsoft.com/dotnet/api/ 查类和方法用法

13.3 新手常见坑(避雷清单)

  1. 整数除法10 / 3 == 3,要小数就写 10 / 3.0
  2. 下标从 0 开始 :长度为 5 的数组,最后一个是 arr[4]
  3. 字符串不可变s.Replace(...) 要接返回值才生效
  4. 金额用 decimal,不要用 double
  5. null 崩溃 :用 ?.?? 防御
  6. 用户输入不可信 :一律用 TryParse
  7. 字段默认 private :对外暴露用属性 { get; set; }
  8. 写了有参构造后:默认无参构造消失,需要时手动补一个

13.4 学习心法

  • 每天写代码,哪怕 20 分钟,比周末突击 5 小时有效
  • 看懂 ≠ 会写:每个例子都亲手敲一遍,再改改参数看结果
  • 报错是朋友:认真读错误信息(通常含行号和原因),这是提升最快的时刻
  • 学会查文档:遇到"这个类有什么方法",直接翻官方 API 或用 VS Code 智能提示

文档完 · 建议配合 .NET 8 SDK 实际运行所有示例 · 祝学习顺利!

相关推荐
软件黑马王子2 小时前
14.事件中心:主要作用和原理
开发语言·前端框架·c#
君顾12 小时前
酒吧点餐小程序系统开发实战:从架构设计到上线全流程指南
java·开发语言·酒吧
lifallen2 小时前
短暂 Agent 与持久化工程:让理解脱离对话
人工智能·学习·ai·重构·ai编程
知识分享小能手2 小时前
深度学习学习教程,从入门到精通,深度模型中的优化 — 完整知识点与代码案例(8)
人工智能·深度学习·学习
似水এ᭄往昔2 小时前
【Qt】--常用控件(显示类控件)
开发语言·qt
互联网叫兽2 小时前
redis深入学习二
数据库·redis·学习
PHP实战开发录2 小时前
PHP文件上传成功为什么页面却找不到
开发语言·nginx·php·开发
是隼人3 小时前
buuctf-pwn mrctf2020_easy_equation(64位fmt)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门