🐣 纯新手先看这一框(30 秒秒懂)
1
C# 适合做什么?Windows 桌面、Unity 游戏、ASP.NET 后端、企业系统、跨平台(.NET 6+ 一处编写多端跑)。语法和 Java 神似,但工具链更现代。
2
为什么要学它?强类型、生态强大、Visual Studio 体验顶级。想做游戏(Unity)或微软系后端,C# 是首选。
3
怎么跑起来?本站「动手实验室」选 C# 点运行即可;本地装 .NET SDK,用 dotnet run。
4
新手最常踩的坑?① 所有代码必须在类里;② == 比较字符串是内容(比 Java 友好),但 string 仍是引用类型;③ 值类型(int/struct)和引用类型(class)赋值行为不同;④ 忘记 using 引命名空间。
1. Hello World 与 .NET入口是 Main 方法
using System;
class Program {
static void Main() {
Console.WriteLine("你好,C#!");
Console.WriteLine("1 + 2 = " + (1 + 2));
}
}
秒懂现代 C#(9+)支持「顶级语句」省掉 class/Main 样板,但本站运行器兼容性起见,示例都用经典写法。
2. 值类型与引用类型C# 最容易被忽视的分水岭
- 值类型:
int、double、struct、bool——存在栈上,赋值=复制。 - 引用类型:
class、string、数组、委托——存堆上,变量持有引用,赋值=复制引用(指向同一对象)。
using System;
struct Point { public int x, y; } // 值类型
class Person { public string name; } // 引用类型
class Program {
static void Main() {
Point a = new Point { x = 1 };
Point b = a; b.x = 9; // b 是副本,a 不变
Person p1 = new Person { name = "A" };
Person p2 = p1; p2.name = "B"; // 同一对象,p1 也变
Console.WriteLine(a.x + " " + p1.name);
}
}
核心struct 改字段不影响原值,class 改字段两边一起变——这是「值 vs 引用」最直观的区别,弄错会出隐蔽 bug。
3. 字符串与 StringBuilderstring 不可变,拼接要小心
string 是不可变(immutable)的:每次拼接都会生成新对象。循环里大量拼接要用 StringBuilder。
using System;
using System.Text;
class Program {
static void Main() {
string s = "a";
s += "b"; s += "c"; // 每次都新建字符串
StringBuilder sb = new StringBuilder();
sb.Append("a").Append("b").Append("c");
Console.WriteLine(s + " | " + sb.ToString());
}
}
易错点在循环里用
+= 拼接上千次字符串会严重拖慢——这是 C# 经典性能坑。4. 数组与集合 List<T>日常用 List 而非原生数组
using System;
using System.Collections.Generic;
class Program {
static void Main() {
int[] arr = { 1, 2, 3 }; // 定长数组
List<int> list = new List<int>(); // 动态列表
list.Add(1); list.Add(2); list.Add(3);
list.RemoveAt(0);
Console.WriteLine("Count=" + list.Count + " 首项=" + list[0]);
}
}
5. 类与对象封装数据和行为
using System;
class Dog {
public string name;
public void Bark() { Console.WriteLine(name + " 汪汪"); }
}
class Program {
static void Main() {
Dog d = new Dog { name = "旺财" };
d.Bark();
}
}
6. 继承与多态子类重写父类方法
using System;
class Animal { public virtual void Speak() { Console.WriteLine("..."); } }
class Cat : Animal { public override void Speak() { Console.WriteLine("喵"); } }
class Program {
static void Main() {
Animal a = new Cat(); // 父类引用指向子类
a.Speak(); // 输出 喵(多态)
}
}
规范想被重写的方法父类加
virtual,子类加 override,缺一不可。7. 接口约定能力,不关心实现
using System;
interface IShape { double Area(); }
class Circle : IShape {
public double r;
public double Area() { return 3.14 * r * r; }
}
class Program {
static void Main() {
IShape s = new Circle { r = 2 };
Console.WriteLine("面积 " + s.Area());
}
}
8. 异常处理try/catch/finally
using System;
class Program {
static void Main() {
try {
int x = 0;
Console.WriteLine(1 / x);
} catch (DivideByZeroException e) {
Console.WriteLine("除以零: " + e.Message);
} finally {
Console.WriteLine("无论是否出错都执行");
}
}
}
9. 泛型入门一套代码支持多种类型
using System;
class Box<T> {
public T value;
public Box(T v) { value = v; }
}
class Program {
static void Main() {
Box<int> b = new Box<int>(42);
Console.WriteLine(b.value);
}
}
10. 属性与索引器C# 对字段的封装语法糖
using System;
class Student {
private int age;
public int Age {
get { return age; }
set { if (value >= 0) age = value; } // value 是关键字
}
}
class Program {
static void Main() {
Student s = new Student();
s.Age = 20;
Console.WriteLine(s.Age);
}
}
干货现代 C# 用自动属性
public int Age { get; set; },连私有字段都省了。11. 委托 / 事件 / Lambda把方法当变量传
using System;
class Program {
static void Main() {
Func<int, int, int> add = (a, b) => a + b; // Lambda
Console.WriteLine(add(3, 4));
Action<string> shout = s => Console.WriteLine(s.ToUpper());
shout("hi");
}
}
秒懂
Func 有返回值、Action 没有;=> 就是 Lambda。事件是委托的「只许外部订阅、不许直接触发」安全封装。12. async / await 异步不阻塞线程等 IO
using System;
using System.Threading.Tasks;
class Program {
static async Task Main() {
Console.WriteLine("开始");
await Task.Delay(500); // 模拟异步等待,不卡线程
Console.WriteLine("500ms 后");
}
}
核心坑不要在异步方法里用
.Result 或 .Wait() 去「同步等异步」,容易死锁(sync over async)。一路 await 到底。13. LINQ用声明式语法查集合
using System;
using System.Linq;
using System.Collections.Generic;
class Program {
static void Main() {
var nums = new List<int> { 1, 2, 3, 4, 5 };
var sum = nums.Where(n => n % 2 == 1).Select(n => n * n).Sum();
Console.WriteLine("奇数平方和=" + sum); // 1+9+25=35
}
}
易错点LINQ 是延迟执行的——定义查询不立即跑,直到
Sum/ToList/foreach 才真正遍历。多次枚举会跑多次。14. record 与可空引用类型现代 C# 的不可变与空安全
// 现代写法(运行器若支持 C#9 可用;record 自动生成相等/哈希/ToString)
// record Person(string Name, int Age);
// 可空引用类型:string? 显式允许为 null
string? maybe = null;
Console.WriteLine(maybe == null ? "空" : maybe);
干货传统类要手写一堆样板才能比较相等,
record 一行搞定且不可变;#nullable enable 让编译器逼你处理 null,少写一堆 NullReferenceException。15. 特性与反射给代码贴元数据,运行时读
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Method)]
class TagAttribute : Attribute { public string Name; public TagAttribute(string n){Name=n;} }
class Program {
[Tag("入口")]
static void Run() { Console.WriteLine("运行中"); }
static void Main() {
var m = typeof(Program).GetMethod("Run");
var tag = (TagAttribute)Attribute.GetCustomAttribute(m, typeof(TagAttribute));
Console.WriteLine("方法标签=" + tag.Name);
}
}
应用场景ASP.NET 的
[HttpGet]、单元测试的 [Test]、序列化标记都是特性 + 反射驱动,这是框架的通用魔法。16. Span<T> 与性能零分配切分内存
using System;
class Program {
static int Sum(ReadOnlySpan<int> s) {
int t = 0;
foreach (var x in s) t += x;
return t;
}
static void Main() {
int[] arr = { 1, 2, 3, 4 };
Console.WriteLine(Sum(arr.AsSpan(1, 2))); // 只取 [2,3]
}
}
坑与秘
Span<T> 在栈上描述一段连续内存,切分不复制、零 GC 压力,是写高性能解析/网络代码的利器(但它不能存进堆、不能跨 async)。🧪 动手实验室(点 ▶ 直接看结果)
代码已预填,打开自动跑一次;点 ▶ 运行 看结果,框内可直接改。
① Hello World
② 值类型 vs 引用类型
③ List 排序与求和
④ 类与继承
⑤ LINQ 过滤求和
💡 连老师都不会教你的干货(工业级技巧)
1. 值类型 vs 引用类型struct 赋值复制值、class 赋值复制引用。需频繁传参且小且不可变优先 struct,减少堆分配与 GC。
2. 装箱拆箱性能坑
int 塞进 ArrayList 或 object 会装箱(堆分配),大量循环里极慢。用泛型集合 List<int> 避免。3. GC 分代0/1/2 三代,短命对象在 0 代快速回收;大对象(>85KB)进 LOH。理解它才能定位卡顿。
4. using + IDisposable文件/连接用完必须释放,写
using(var x = ...){} 编译器自动调 Dispose(),比手写 try/finally 稳。5. sync over async 死锁异步方法里别用
.Result/.Wait() 同步等,UI/ASP 上下文下必死锁。一路 await,或必要时 ConfigureAwait(false)。6. LINQ 延迟执行查询定义不执行,枚举才执行。多次
foreach 同查询会跑多次——要固化就 .ToList()。7. 委托/事件事件是委托的封装,外部只能
+= 订阅不能触发,防止类外乱发通知。8. 可空引用类型 NRT
#nullable enable 后 string? 显式可空,编译器逼你判空,从根上砍掉 NullReferenceException。9. record 不可变
record 一行定义值对象,自动生成相等/哈希/ToString,且 with 表达式生成副本,适合 DTO。10. 模式匹配
switch 支持类型/属性模式(x is int i、switch{>0:..}),比一堆 if 清爽且编译器可检查穷尽。11. 特性与反射框架靠特性+反射发现方法(路由、测试、序列化)。但反射慢,热路径避免频繁用。
12. Span<T>/Memory<T>零分配切分连续内存,Span 栈上、Memory 可存堆/跨 async。高性能解析首选,但要避开「跨异步、进字段」禁忌。
自测题(点击展开答案)
- Q1:
struct Point p2 = p1; p2.x=9;后 p1.x 会变吗?A:不会。struct 是值类型,赋值是复制,p2 是独立副本。 - Q2:循环里拼接上万次字符串,用什么性能最好?A:
StringBuilder。string 不可变,+=每次新建对象,会大量分配与 GC。 - Q3:子类要重写父类方法,两边分别用什么关键字?A:父类
virtual,子类override。缺virtual是隐藏(new)而非重写。 - Q4:为什么异步方法里不建议用
.Result?A:sync over async 在 UI/ASP 上下文下会死锁。应一路 await。 - Q5:LINQ 查询什么时候真正执行?A:延迟执行——定义时不跑,直到
Sum/ToList/foreach等枚举才遍历,多次枚举会跑多次。 - Q6:
using语句对实现了什么接口的类型自动释放资源?A:IDisposable。离开using块自动调Dispose()。 - Q7:
Span<T>有什么使用禁忌?A:不能存为类的字段、不能跨await异步边界(它活在栈上)。需要跨异步用Memory<T>。
本 C# 课程遵循 C# 8/9 常用子集(运行器基于 Mono 6.12,个别最新特性可能受限),手机与电脑端自适应,配套本站运行器练习效果最佳。
公网参考:learn.microsoft.com/dotnet · dotnet 官方文档 · C# 规范。