第 4 章 C#高级语言特性

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;

var2页数据 = 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.班主任
   });
© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
u又改主意咯o3o的头像 - 鹿快
评论 共1条

请登录后发表评论

    暂无评论内容