4.1 泛型编程与类型约束
泛型是一种通过类型参数化编程的方法。C# 的泛型允许在编写类、接口和方法时,不指定具体类型,而是使用占位符,增强代码的灵活性和类型安全。例如,泛型类:
public class GenericList<T>
{
private List<T> items = new List<T>();
public void Add(T item)
{
items.Add(item);
}
public T Get(int index)
{
return items[index];
}
}
类型约束用于限制泛型类型参数的范围,指定类型参数必须满足的条件。例如,where T : IComparable表明类型参数T必须实现IComparable接口,这样在泛型类或方法中就可以使用IComparable接口定义的方法。
4.2 委托、事件与回调机制
委托是一种类型,它定义了方法的类型,使得可以将方法作为参数传递。例如:
public delegate void MyDelegate(string message);
public class Program
{
public static void PrintMessage(string message)
{
Console.WriteLine(message);
}
public static void Main()
{
MyDelegate del = PrintMessage;
del("Hello, Delegate!");
}
}
事件是一种基于委托的机制,用于处理异步操作和对象间的交互。一个对象可以定义事件,当特定事件发生时,会通知注册了该事件的其他对象。例如:
public class Button
{
public event EventHandler Click;
public void OnClick()
{
Click?.Invoke(this, EventArgs.Empty);
}
}
public class Program
{
public static void Button_Click(object sender, EventArgs e)
{
Console.WriteLine("按钮被点击了");
}
public static void Main()
{
Button button = new Button();
button.Click += Button_Click;
button.OnClick();
}
}
回调机制是指在某个操作完成后,调用预先注册的方法,委托和事件常常用于实现回调机制。
4.3 Lambda 表达式与匿名方法
Lambda 表达式是定义匿名函数的一种方式,语法简洁且易于使用。例如:
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// 输出: 25
匿名方法是一种没有名称的方法,在 C# 2.0 中引入,它可以作为参数传递给委托。例如:
MyDelegate del = delegate (string message)
{
Console.WriteLine(message);
};
del("Hello, Anonymous Method!");
Lambda 表达式是匿名方法的一种更简洁的语法形式,在许多场景下,Lambda 表达式使代码更加简洁、易读,常用于 LINQ 查询、事件处理等场景。
4.4 扩展方法与 LINQ 基础
扩展方法与 LINQ(Language Integrated Query,语言集成查询)是 C# 中简化数据处理的核心技术:扩展方法 允许在不修改原类代码、不继承原类的前提下为类型添加新功能,是 LINQ 的底层技术支撑;LINQ 则基于扩展方法,提供了一套统一、简洁的语法,用于查询和操作各种数据源(内存集合、数据库、XML 等),让数据处理代码更具可读性和可维护性。
4.4.1 扩展方法:无侵入式扩展类型功能
1. 为什么需要扩展方法?
在开发中,我们常遇到 “需要为现有类型(如 string、int[]、自定义类)添加新方法,但无法修改原类型代码” 的场景(例如:.NET 框架自带的 string 类、第三方库的类型)。
传统解决方案的局限:
- 继承原类型:值类型无法继承,部分类被 sealed 修饰禁止继承;
- 静态工具类:调用时需传入参数,语法繁琐(如 StringHelper.IsEmail(input))。
扩展方法的核心优势:无侵入式扩展(不修改原类、不继承)、调用方式自然(像调用类型自带方法一样)。
2. 扩展方法的语法规则(必满足 4 个条件)
|
条件 |
说明 |
|
容器要求 |
必须定义在 静态类 中(类名提议为 “目标类型 + Extensions”,如 StringExtensions) |
|
方法修饰 |
方法本身必须是 静态方法 |
|
首个参数 |
第一个参数前必须加 this 关键字,指定扩展的目标类型(如 this string input 表明扩展 string 类型) |
|
参数限制 |
第一个参数不能有默认值,且不能用 ref/out 修饰 |
语法模板:
// 静态扩展类(必须)
public static class 目标类型Extensions
{
// 静态方法 + this 关键字(必须)
public static 返回值类型 扩展方法名(this 目标类型 变量名, [其他参数...])
{
// 方法逻辑(通过第一个参数访问目标类型的公共成员)
}
}
3. 实战示例
示例 1:为 string 扩展 “邮箱格式校验” 方法
using System.Text.RegularExpressions;
// 扩展类:专门用于扩展 string 类型
public static class StringExtensions
{
/// <summary>
/// 扩展方法:判断字符串是否为合法邮箱格式
/// </summary>
/// <param>要校验的字符串(this 指定扩展 string 类型)</param>
/// <returns>是否合法</returns>
public static bool IsEmail(this string input)
{
// 空值安全处理
if (string.IsNullOrWhiteSpace(input))
return false;
// 邮箱正则表达式
string pattern = @"^[w-]+(.[w-]+)*@([w-]+.)+[a-zA-Z]{2,7}$";
return Regex.IsMatch(input, pattern);
}
}
// 调用方式(与 string 原生方法一致)
string validEmail = "user@example.com";
string invalidEmail = "invalid-email";
Console.WriteLine(validEmail.IsEmail()); // True
Console.WriteLine(invalidEmail.IsEmail()); // False
示例 2:为 int[] 扩展 “求和”“求平均值” 方法
public static class IntArrayExtensions
{
// 扩展方法1:数组求和
public static int Sum(this int[] array)
{
if (array == null || array.Length == 0)
return 0;
int total = 0;
foreach (int num in array)
total += num;
return total;
}
// 扩展方法2:数组求平均值(返回 float 避免整数截断)
public static float Average(this int[] array)
{
if (array == null || array.Length == 0)
return 0;
return (float)array.Sum() / array.Length; // 调用自身扩展的 Sum 方法
}
}
// 调用示例
int[] numbers = { 1, 2, 3, 4, 5 };
Console.WriteLine(numbers.Sum()); // 输出:15
Console.WriteLine(numbers.Average()); // 输出:3.0
4. 关键注意事项
- 优先级:扩展方法与目标类型的 “原生方法” 同名同参时,原生方法优先执行(扩展方法不会覆盖原生方法);
- 访问权限:仅能访问目标类型的 公共成员(无法访问私有 / 保护成员);
- 命名空间:使用扩展方法前,必须引入扩展类所在的命名空间(否则编译器无法识别);
- 空值安全:即使目标对象为 null,扩展方法也能被调用(需在方法内处理空值):
string nullStr = null;
Console.WriteLine(nullStr.IsEmail()); // 输出:False(无空指针异常)
使用原则:仅在 “无法修改原类” 时使用,避免过度扩展导致代码分散。
4.4.2 LINQ 基础:声明式数据处理
1.LINQ 的核心价值
LINQ 是 C# 3.0 引入的语言特性,核心是 “将查询能力集成到 C# 语言中”,解决传统数据处理的痛点:
- 传统方式:用 for/foreach 循环手动筛选、排序(代码冗长,可读性差);
- LINQ 方式:用 “声明式语法” 描述 “要什么数据”,而非 “如何获取数据”(代码简洁,逻辑清晰)。
LINQ 核心优势:
- 统一语法:对所有数据源(内存集合、数据库、XML 等)使用一样查询语法;
- 类型安全:编译时检查语法错误和类型匹配;
- 可组合性:多个操作链式调用,灵活组合;
- 延迟执行:大部分操作按需计算,提升性能。
2. LINQ 的底层依赖
LINQ 的核心功能(如 Where、Select)本质是 .NET 为 IEnumerable<T> 接口(所有泛型集合的基接口,如 List<T>、数组、HashSet<T>)提供的 扩展方法,定义在 System.Linq 命名空间下。
结论:LINQ 是扩展方法的 “超级应用”,通过扩展
IEnumerable<T>
实现对所有集合的统一查询。
使用前提:必须引入命名空间:
using System.Linq; // 关键:启用 LINQ 扩展方法
3. LINQ 常用操作符(方法语法)
LINQ 有两种语法形式:
- 方法语法:直接调用 IEnumerable<T> 的扩展方法(链式调用,推荐开发使用);
- 查询表达式语法:类似 SQL 的语法(编译器最终转换为方法语法)。
以下结合 “学生信息查询” 场景,讲解最常用的 LINQ 操作符(方法语法)。
准备工作:定义实体类与测试数据
// 学生实体类
public class Student
{
public int Id { get; set; } // 学号
public string Name { get; set; } // 姓名
public int Age { get; set; } // 年龄
public string Class { get; set; } // 班级
public int Score { get; set; } // 分数
}
// 测试数据:学生列表
List<Student> students = new List<Student>
{
new Student { Id = 1, Name = "张三", Age = 18, Class = "高一(1)班", Score = 85 },
new Student { Id = 2, Name = "李四", Age = 17, Class = "高一(2)班", Score = 92 },
new Student { Id = 3, Name = "王五", Age = 18, Class = "高一(1)班", Score = 78 },
new Student { Id = 4, Name = "赵六", Age = 19, Class = "高一(3)班", Score = 95 },
new Student { Id = 5, Name = "孙七", Age = 17, Class = "高一(2)班", Score = 88 }
};
(1)筛选:Where(条件过滤)
- 作用:筛选满足条件的元素,返回 IEnumerable<T>;
- 语法:IEnumerable<T> Where(Func<T, bool> 条件表达式)。
示例:筛选 “高一 (1) 班” 且分数≥80 的学生:
var result = students.Where(s => s.Class == "高一(1)班" && s.Score >= 80);
// 遍历结果(延迟执行:此时才计算)
foreach (var student in result)
{
Console.WriteLine($"学号:{student.Id},姓名:{student.Name}");
}
// 输出:学号:1,姓名:张三
(2)投影:Select(数据转换)
- 作用:将元素转换为另一种类型(提取字段、格式转换),返回 IEnumerable<U>;
- 语法:IEnumerable<U> Select(Func<T, U> 转换表达式)。
示例 1:提取 “姓名 + 分数”,转换为匿名类型:
var studentInfo = students.Select(s => new
{
学生姓名 = s.Name,
分数 = s.Score
});
foreach (var info in studentInfo)
{
Console.WriteLine($"{info.学生姓名}:{info.分数}分");
}
// 输出:张三:85分、李四:92分...
示例 2:将分数转换为等级(A:90+,B:80-89,C:70-79):
var scoreLevels = students.Select(s => new
{
s.Name,
等级 = s.Score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
_ => "D"
}
});
(3)排序:OrderBy/OrderByDescending(升序 / 降序)
- 作用:按指定字段排序,返回 IOrderedEnumerable<T>(支持多字段排序);
- 语法:
- 升序:OrderBy(Func<T, TKey> 排序字段);
- 降序:OrderByDescending(Func<T, TKey> 排序字段);
- 多字段排序:ThenBy/ThenByDescending(追加排序条件)。
示例:先按班级升序,再按分数降序:
var sortedStudents = students
.OrderBy(s => s.Class) // 主排序:班级升序
.ThenByDescending(s => s.Score); // 次排序:分数降序
(4)聚合:Count/Sum/Average/Max/Min(统计计算)
- 作用:对集合进行聚合计算,返回单个结果(立即执行);
- 常用场景:统计总数、总和、平均值、最值。
示例:学生数据统计:
int 总人数 = students.Count(); // 5人
float 平均分 = students.Average(s => s.Score); // 87.6分
int 最高分 = students.Max(s => s.Score); // 95分
int 高一2班人数 = students.Count(s => s.Class == "高一(2)班"); // 2人
(5)分页:Take/Skip(取前 N 条 / 跳前 N 条)
- 作用:实现分页功能;
- 语法:
- Take(n):取前 n 个元素;
- Skip(n):跳前 n 个元素。
示例:分页查询(每页 2 条,查询第 2 页):
int 页大小 = 2;
int 页码 = 2;
var 第2页数据 = students
.OrderBy(s => s.Id)
.Skip((页码 - 1) * 页大小) // 跳前2条(第1页数据)
.Take(页大小); // 取2条(第2页数据)
(6)连接:Join(多集合关联)
- 作用:类似 SQL 的 JOIN,根据共同字段关联两个集合(内连接);
- 语法:Join(内集合, 外集合关联字段, 内集合关联字段, 结果转换)。
示例:关联 “学生表” 和 “班级表”,查询班主任:
// 班级表
List<Class> classes = new List<Class>
{
new Class { ClassName = "高一(1)班", 班主任 = "李老师" },
new Class { ClassName = "高一(2)班", 班主任 = "王老师" }
};
// 关联查询
var studentWithTeacher = students.Join(
inner: classes,
outerKeySelector: s => s.Class, // 学生表关联字段
innerKeySelector: c => c.ClassName, // 班级表关联字段
resultSelector: (s, c) => new // 关联结果
{
s.Name,
s.Class,
班主任 = c.班主任
});
















暂无评论内容