🧭 编程学习站

C# 入门 + 进阶训练

公网版 · 手机/电脑自适应

🐣 纯新手先看这一框(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# 最容易被忽视的分水岭

  • 值类型intdoublestructbool——存在栈上,赋值=复制。
  • 引用类型classstring、数组、委托——存堆上,变量持有引用,赋值=复制引用(指向同一对象)。
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 塞进 ArrayListobject 会装箱(堆分配),大量循环里极慢。用泛型集合 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 enablestring? 显式可空,编译器逼你判空,从根上砍掉 NullReferenceException。
9. record 不可变record 一行定义值对象,自动生成相等/哈希/ToString,且 with 表达式生成副本,适合 DTO。
10. 模式匹配switch 支持类型/属性模式(x is int iswitch{>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# 规范。
🛠 在线练习