适用版本:.NET 8 / C# 12(文中会注明新语法与传统写法的区别)
阅读方式:按章节顺序阅读,每章的代码都可直接复制运行
目录
- 认识 C# 与 .NET
- 基础语法:变量与数据类型
- 运算符与类型转换
- 流程控制
- 数组与字符串
- 方法(函数)
- 面向对象基础:类与对象
- 面向对象进阶:继承、多态与接口
- 常用集合:List 与 Dictionary
- 异常处理
- 现代 C# 特性一览
- 实战小项目
- 学习路线与资源
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 搭建环境
- 下载 .NET SDK:https://dotnet.microsoft.com/download(选择 .NET 8.0 或更高)
- 安装后打开终端(PowerShell 或 CMD),验证:
bash
dotnet --version # 输出版本号说明安装成功
- 创建并运行第一个项目:
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# 区分大小写:
age和Age是两个不同的变量 - 变量名只能由字母、数字、下划线组成,且不能以数字开头 ,不能用关键字(如
int、class)
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 abstract :virtual 是"我有默认实现,子类可以改";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(是否存在)、Count、Sum、Average、Take(取前 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 新手常见坑(避雷清单)
- 整数除法 :
10 / 3 == 3,要小数就写10 / 3.0 - 下标从 0 开始 :长度为 5 的数组,最后一个是
arr[4] - 字符串不可变 :
s.Replace(...)要接返回值才生效 - 金额用 decimal,不要用 double
- null 崩溃 :用
?.和??防御 - 用户输入不可信 :一律用
TryParse - 字段默认 private :对外暴露用属性
{ get; set; } - 写了有参构造后:默认无参构造消失,需要时手动补一个
13.4 学习心法
- 每天写代码,哪怕 20 分钟,比周末突击 5 小时有效
- 看懂 ≠ 会写:每个例子都亲手敲一遍,再改改参数看结果
- 报错是朋友:认真读错误信息(通常含行号和原因),这是提升最快的时刻
- 学会查文档:遇到"这个类有什么方法",直接翻官方 API 或用 VS Code 智能提示
文档完 · 建议配合 .NET 8 SDK 实际运行所有示例 · 祝学习顺利!