刚入门C#后,光会写单项目控制台程序可不够,实际开发中不仅要会管理多项目、抽离公用代码,还得吃透委托、Lambda、扩展方法、事件这些核心语法------它们不仅是日常开发的高频考点,更是Linq、异步编程的基础,也是面试中常被问到的重点。
今天这篇文章,把C#进阶最核心的知识点一次性梳理清楚,从多项目开发的实战技巧,到索引器、密封类等基础进阶语法,再到委托-Lambda-事件的完整体系,每部分都配了可直接复制运行的代码示例,新手也能一步步吃透,建议收藏起来慢慢看、慢慢敲!
一、多项目开发:实战必备的项目管理技巧
实际开发中基本都是多项目协作,而非单项目,合理的项目结构能让代码更易维护、复用性更高。这部分重点讲项目创建、公用代码抽离、引用规则,都是落地即用的技巧。
1.1 项目创建的两种核心方式
Visual Studio中创建C#项目,核心有两种方式,按需选择即可:
- 项目和解决方案一起建立:新建项目时直接创建,适合简单的多项目场景,一步到位;
- 先建解决方案,再添加项目:先创建空解决方案,再右键「添加→新建项目」,适合复杂的多项目架构(比如业务项目、类库项目、测试项目分离),更灵活。
1.2 公用代码抽离:类库的创建与引用(核心)
多个项目需要共用的类(比如工具类、实体类),不要重复写 !抽离到类库项目(Class Library) 中,其他项目通过「添加引用」复用,这是解耦和提高复用性的关键,步骤如下:
- 添加类库:右键解决方案→添加→新建项目→选择「类库(.NET Framework/.NET Core)」,写入公用类;
- 添加引用:在需要使用公用类的项目上右键→添加→引用→勾选创建好的类库项目;
- 引用类方法 :在代码中通过
using 类库命名空间;引入,之后直接使用类库中的类和方法即可。
1.3 多项目的配置文件读取规则
重要规则 :多项目中,只有主项目(当前运行的项目) 的app.config/web.config配置文件读取才会生效,其他项目的配置文件不会被加载。
如果类库项目需要读取配置,建议把配置项写在主项目的配置文件中。
1.4 第三方类库的引用与反编译
开发中用到第三方类库(比如NuGet包、dll文件),直接右键项目→添加→引用→勾选对应的dll即可;
如果需要查看第三方类库的具体代码,可通过反编译工具(比如ILSpy、dnSpy)查看,方便调试和理解用法。
二、基础语法进阶:索引器、密封类&静态类
这部分是C#的基础进阶语法,看似简单,实则是理解后续核心语法的铺垫,重点记本质和使用规则。
2.1 索引器:无名称的"数组式属性"
索引器可以让我们像操作数组一样操作自定义类的对象,核心特点:
- 索引器没有名字 ,本质是
this[参数] {get; set;}; - 不仅支持数字索引 ,还支持字符串索引,甚至允许多个索引器参数;
- 用法和属性类似,分为get(读取)和set(赋值),适合需要按"索引"访问对象内部数据的场景。
2.2 密封类&静态类:继承与实例化的限制
两者都是对类的继承/实例化做了限制,是面试高频考点,对比记忆更清晰:
| 类型 | 修饰符 | 核心规则 |
|---|---|---|
| 密封类 | sealed | 不能被继承,防止类的方法被子类重写,适合确定无需扩展的类 |
| 静态类 | static | 1. 不能被继承;2. 不能创建实例;3. 内部成员必须是static类型;4. 直接通过类名调用 |
补充 :static成员不仅能在静态类中定义,也能在非静态类 中定义,调用方式统一为类名.方法名/属性名,无需创建类的实例。
2.3 小补充:string类的扩展方法
string类是密封类(不能被继承),如果想给string扩展自定义方法,通过扩展方法实现(后文会详细讲),比如给string加一个"判断是否为数字"的方法,无需修改string原类。
三、C#核心:委托(Delegate)
委托(Delegate) 是C#的类型安全的函数指针 ,简单说:委托是用来封装方法 的类型,能把方法当作参数传递,是Lambda、事件、异步编程的基础。
核心记住:日常开发基本不用自定义委托,用.NET内置泛型委托就够了!
3.1 自定义委托:基础语法
先了解自定义委托的写法,理解委托的本质,语法如下:
csharp
// 定义委托:delegate + 返回值 + 委托名 + 参数列表
delegate 返回值 委托名(参数列表);
// 实例化委托:传入匹配的方法(方法的返回值、参数列表需和委托一致)
委托名 委托实例 = new 委托名(方法名);
// 简化写法:直接赋值方法名
委托名 委托实例 = 方法名;
// 调用委托
委托实例(参数);
代码示例:
csharp
namespace ConsoleApp12
{
class Program
{
static void Main(string[] args)
{
// 实例化委托,两种写法都可
MyDel myDel = new MyDel(GetName);
// MyDel myDel = GetName;
myDel("chen", 1); // 调用委托,输出chen1
}
// 匹配委托的方法
static string GetName(string name,int num)
{
return name + num;
}
}
// 定义自定义委托
delegate string MyDel(string name, int num);
}
3.2 实战优选:内置泛型委托Func & Action
.NET框架已经为我们封装了泛型委托Func和Action ,满足99%的开发场景,无需自定义委托,核心区别:
- Action :无返回值的委托,支持0~16个参数,比如
Action、Action<string>、Action<int, string>; - Func :有返回值的委托,支持0~16个参数,最后一个参数固定为返回值类型 ,比如
Func<int>、Func<string, int>、Func<int, string, bool>。
代码示例:
csharp
static void Main(string[] args)
{
Action a1 = F1; // 无参数无返回值
Action<string> a2 = F2;// 1个参数无返回值
Func<string,int> f1 = F3;// 1个参数,返回int
a1(); // 调用F1
a2("测试"); // 调用F2
int res = f1("chen"); // 调用F3,获取返回值
}
static void F1() => Console.WriteLine("F1");
static void F2(string s1) => Console.WriteLine("F2:"+s1);
static int F3(string name) => 1;
3.3 匿名方法:无名称的临时方法
如果某个方法只需要用一次,没必要单独定义,用匿名方法 即可------本质是没有名字的方法 ,直接赋值给委托,语法:委托实例 = delegate(参数) { 方法体; }。
csharp
// 示例:匿名方法赋值给委托
Action<int> a = delegate (int s) { Console.WriteLine(s); };
a(10); // 输出10
注意:匿名方法是Lambda表达式的"前身",实际开发中已经被更简洁的Lambda替代,了解即可。
四、值传递进阶:ref 和 out
ref和out都是用来实现方法的按引用传递,解决C#中值类型参数默认按值传递、方法内修改不影响外部的问题,核心区别:
- ref :要求参数传入前必须初始化,方法内可读写;
- out :要求参数方法内必须赋值 ,传入前可未初始化,方法内写完后外部才能用。
两者都是实战中优化参数传递的小技巧,比如方法需要返回多个值时,可结合out使用。
五、Lambda表达式:简化委托的"语法糖"(核心)
Lambda表达式是C#中最常用的语法糖 ,本质是匿名方法的简化写法 ,能极大减少代码量,也是Linq的核心语法,重点记简化规则,配练习食用效果更佳!
5.1 Lambda的核心简化规则
Lambda的核心符号是=>(读作"goes to"),结合委托使用,从匿名方法到Lambda的简化步骤是由繁到简,核心规则:
- 可省略参数的类型声明(编译器会自动推断);
- 只有一个参数时,可省略参数的圆括号
(); - 方法体只有一行代码时,可省略大括号
{}; - 有返回值且方法体只有一行时,可省略
return关键字。
5.2 5个经典练习,吃透Lambda
这5个练习由浅入深,覆盖Lambda的所有使用场景,代码可直接复制到控制台运行,敲一遍就懂了!
练习1:基础简化(无返回值+有返回值)
csharp
static void Main(string[] args)
{
// 无返回值:一步步简化
Action<int> a1 = delegate (int i) { Console.WriteLine(i); };
Action<int> a2 = (int i)=>{ Console.WriteLine(i);};
Action<int> a3 = i => { Console.WriteLine(i); }; // 单参数省略括号+类型
a1(3333); a2(4444); a3(5555);
// 有返回值:极致简化
Func<int, string, bool> f1 = delegate (int i, string name) { return true; };
Func<int, string, bool> f2 = (int i, string name)=> { return true; };
Func<int, string, bool> f3 = (i, name) => { return true; }; // 省略类型
Func<int, string, bool> f4 = (i, name) => true; // 一行代码省略return+大括号
f1(1, "chen"); f2(1, "chen"); f3(1, "chen"); f4(1, "chen");
Console.ReadKey();
}
练习2:泛型结合Lambda,实现通用找最大值
自定义泛型方法,通过Lambda传递比较规则,让方法支持任意类型的数组找最大值,适配int、string、自定义实体等:
csharp
static void Main(string[] args)
{
int[] nums = new int[] { 3, 88, 6, 9 };
// Lambda直接作为方法参数,传递比较规则
int m = GetMax(nums, (i1, i2) => i1 > i2 );
Console.WriteLine(m); // 输出88
Console.ReadKey();
}
// 自定义比较方法(可被Lambda替代)
static bool compareInt(int i1,int i2) => i1 > i2;
// 泛型找最大值方法
static T GetMax<T>(T[] objs,Func<T,T,bool> compareFunc)
{
T max = objs[0];
for (int i = 0; i < objs.Length; i++)
{
if(compareFunc(objs[i],max)) max = objs[i];
}
return max;
}
练习3:自定义Where扩展方法(Linq底层原理)
实现Linq中Where的核心逻辑,通过扩展方法+Lambda实现集合过滤,理解Linq的底层本质:
csharp
// 静态类:扩展方法的容器
static class JiHeExt
{
// 扩展方法:this关键字+静态方法
public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> data,Func<T,bool> func)
{
List<T> resultList = new List<T>();
foreach(T item in data)
{
if (func(item)) resultList.Add(item); // Lambda作为过滤规则
}
return resultList;
}
}
class Program
{
static void Main(string[] args)
{
int[] nums = new int[] { 3, 88, 6, 9 };
IEnumerable<int> r1 = nums.MyWhere(i => i > 10); // 过滤大于10的数
foreach (int item in r1) Console.WriteLine(item); // 输出88
Console.ReadKey();
}
}
练习4:Select方法:集合数据转换
Linq中Select的核心作用是数据处理/转换,通过Lambda对集合中每个元素做处理,生成新集合:
csharp
static void Main(string[] args)
{
List<int> list1 = new List<int> { 1, 2,3,8,16,99 };
// Lambda将int转换为string,生成新集合
IEnumerable<string> data = list1.Select(i => i +"你好");
foreach (var item in data) Console.WriteLine(item);
// 输出:1你好、2你好、3你好、8你好、16你好、99你好
Console.ReadLine();
}
练习5:Linq高频方法:Sum/ToList/ToArray
结合Lambda操作自定义实体集合,实战中高频使用,比如求和、集合类型转换:
csharp
class Program
{
static void Main(string[] args)
{
// 自定义实体数组
Person[] p = new Person[]
{
new Person{Name="chen",Age=12},
new Person{Name="chen2",Age=24},
new Person{Name="chen3",Age=12}
};
int totalAge = p.Sum(i => i.Age); // 求和:12+24+12=48
List<Person> psList = p.ToList(); // 转List集合
Person[] psArray = p.ToArray(); // 转Array数组
Console.WriteLine(totalAge); // 输出48
Console.ReadKey();
}
}
// 自定义实体类
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
六、扩展方法:不修改原类,实现功能扩展
扩展方法是C#的黑科技 之一,核心作用是:在不修改原有类的代码、不继承原有类的前提下,为类添加新的方法 ,符合面向对象的开闭原则(对扩展开放,对修改关闭)。
扩展方法的三个必须条件(缺一不可)
- 扩展方法必须定义在静态类中;
- 扩展方法本身必须是静态方法;
- 方法的第一个参数必须加this关键字,且this后紧跟要扩展的类(称为"扩展目标类")。
核心应用:Linq的所有方法(Where、Select、Sum等)都是通过扩展方法实现的,也是我们日常开发中给第三方类、系统类(如string、int)扩展方法的核心方式。
七、委托组合:多个委托的"批量执行"
C#支持委托的组合 ,通过+号将多个同类型的委托实例组合成一个委托链 ,调用组合后的委托时,会依次执行 委托链中的所有方法;也可通过-号从委托链中移除某个委托。
代码示例:
csharp
class Program
{
static void Main(string[] args)
{
Mydel d1 = F1;
Mydel d2 = F2;
Mydel d3 = F3;
Mydel d4 = d1 + d2 + d3; // 委托组合,形成委托链
d4(8); // 依次执行F1、F2、F3,输出对应内容
Console.ReadKey();
}
static void F1(int i) => Console.WriteLine("我是F1:"+i);
static void F2(int i) => Console.WriteLine("我是F2:" + i);
static void F3(int i) => Console.WriteLine("我是F3:" + i);
}
// 定义委托
delegate void Mydel(int i);
八、事件:委托的安全封装(实战核心)
事件(Event)是委托的封装 ,在委托的基础上加了event关键字,让委托的使用更安全 ------这是WinForm、WPF、ASP.NET中事件驱动编程 的核心,比如按钮的点击事件Click。
8.1 事件的基础实现(实战示例)
以"本命年提醒"为例,当Person的Age被赋值为12的倍数时,触发"本命年"事件,执行对应的方法:
csharp
// 定义实体类,包含事件
class Person
{
private int age;
public int Age {
get => this.age;
set
{
this.age = value;
// 当年龄是12的倍数时,触发事件
if(value%12==0) OnBenMingNian?.Invoke(); // 空判断简化写法
}
}
// 定义事件:event + 委托类型 + 事件名(常用OnXXX命名)
public event Action OnBenMingNian;
}
// 主程序调用
class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.OnBenMingNian += BMN; // 订阅事件:+= 添加事件处理方法
p1.Age = 5; // 不触发事件,输出5
Console.WriteLine(p1.Age);
p1.Age = 24; // 触发事件,输出"本命年到了"+24
Console.WriteLine(p1.Age);
p1.Age = 55; // 不触发事件,输出55
Console.WriteLine(p1.Age);
Console.ReadKey();
}
// 事件处理方法:参数和返回值必须和事件的委托类型一致
static void BMN() => Console.WriteLine("本命年到了");
}
8.2 事件vs委托:核心区别
很多小伙伴会混淆事件和委托,核心区别就在于event关键字,加了之后会有访问限制:
- 委托:可在外部直接赋值(
=)、调用,灵活性高但不安全; - 事件:在外部只能通过
+=订阅、-=取消订阅,不能直接赋值、不能直接调用,只能在定义事件的类内部触发,更安全。
本质 :反编译后会发现,事件是由一个私有的委托变量 + add方法 + remove方法 组成的,+=对应调用add方法,-=对应调用remove方法。
8.3 经典面试题:接口中可以定义什么?
答案 :接口中可以定义方法、事件、属性、索引器 。
原因 :因为这四个元素的本质都是方法------属性是get/set方法,索引器是this的get/set方法,事件是add/remove方法,所以接口中可以定义它们(接口只能定义成员签名,不能实现)。
九、文末总结&敲代码建议
今天的内容覆盖了C#从项目实战 到核心语法的关键知识点,核心重点总结为3点:
- 多项目开发:核心是抽离类库、正确添加引用,记住"只有主项目的配置文件生效";
- 委托-Lambda-扩展方法:三者是一套体系,委托是基础,Lambda是简化委托的语法糖,扩展方法结合Lambda实现了Linq的核心功能;
- 事件 :是委托的安全封装,核心是
event关键字,外部只能订阅/取消订阅,是事件驱动编程的基础。
最重要的建议 :一定要敲代码!文中的所有代码示例都能直接复制到控制台运行,先跑通,再逐行修改、调试(比如改Lambda的写法、改事件的触发条件),只有动手,才能把知识点从"看懂"变成"会用"。
这些知识点是C#进阶的基础,后续的Linq、异步编程(async/await)、设计模式(比如观察者模式)都离不开它们,收藏起来,反复看、反复敲,一定能吃透!
最后,想问大家:你在C#开发中,对委托、Lambda、事件的使用有哪些疑问?或者遇到过哪些坑?评论区聊聊~
关注我,后续继续分享C#实战干货、面试考点,从入门到进阶,一起加油~