← 全部文章 C# 语言

C# 扩展方法

2022 年 4 月 21 日 · 1 分钟读完

目录
  1. 三个要素
  2. 例子
  3. 规则总结
  4. 延伸

微软文档的说法:「扩展方法使你能够向现有类型添加方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。」也就是说,可以给 string、int、DataRow、DataTable 这些改不了源码的类型加方法。

三个要素

  1. 方法必须是静态方法;
  2. 方法必须放在静态类(非嵌套、非泛型)里;
  3. 第一个参数以 this 开头,this 后面的类型就是要扩展的类型。

例子

一个学生类和一个学生列表类:

class Student
{
    public string Name { get; set; }
    public int Age { get; set; }
}

class StudentList
{
    List<Student> mStudentList = new List<Student>(); // 私有

    public int StudentsCount => mStudentList.Count;

    public void AddStudent(string name, int age)
    {
        mStudentList.Add(new Student { Name = name, Age = age });
    }
}

给 StudentList 写扩展:

static class StudentListEx
{
    // 扩展方法拿到的只是一个普通实例,访问不到私有成员
    // public static int SumOfStudentsAge(this StudentList studentList)
    // {
    //     foreach (var item in studentList.mStudentList) // 编译错误
    // }

    public static void WriteLineStudentsCount(this StudentList studentList)
    {
        Console.WriteLine("学生个数:" + studentList.StudentsCount);
    }

    // 和实例方法签名完全相同:永远不会被调用
    public static void AddStudent(this StudentList studentList, string name, int age)
    {
        Console.WriteLine("调用扩展方法");
    }

    // 参数不同:相当于新增了一个重载
    public static void AddStudent(this StudentList studentList, Student student)
    {
        Console.WriteLine("调用扩展方法2");
        studentList.AddStudent(student.Name, student.Age);
    }
}

调用:

var studentList = new StudentList();
studentList.AddStudent("小明", 10);                                    // 实例方法
studentList.AddStudent(new Student { Name = "小胡椒", Age = 11 });      // 扩展方法 2
studentList.WriteLineStudentsCount();                                  // 学生个数:2

规则总结

  1. 扩展方法只能访问类型的公开成员,访问不到 private。
  2. 和实例方法签名相同时,实例方法优先,扩展方法不会被调用(编译器也不报错,要小心)。
  3. 只有被扩展类型(及其子类、实现类)的变量能用。扩展父类,子类对象也能用;扩展接口,所有实现类都能用。
  4. 使用前要 using 扩展类所在的命名空间。
  5. 本质是语法糖:studentList.WriteLineStudentsCount() 会被编译成 StudentListEx.WriteLineStudentsCount(studentList),所以对 null 调用扩展方法不会立刻抛异常,异常发生在方法内部访问成员时。

延伸

  • LINQ 就是一大堆扩展方法:Where、Select、OrderBy 都定义在 System.Linq.Enumerable 这个静态类里,扩展的是 IEnumerable<T>。这就是为什么不写 using System.Linq; 就点不出它们。
  • 扩展接口最有用:给 IEnumerable<T> 写一个 ForEach,给 Control 写一个线程安全的 InvokeIfRequired,所有实现类都能直接用。
public static class ControlExtensions
{
    // WinForm 跨线程更新界面的常用写法
    public static void InvokeIfRequired(this Control control, Action action)
    {
        if (control.InvokeRequired) control.Invoke(action);
        else action();
    }
}

label1.InvokeIfRequired(() => label1.Text = "完成");
  • ASP.NET Core 的 services.AddXxx() 也是扩展方法,各个库通过扩展 IServiceCollection 来提供注册入口。
  • C# 14 的扩展成员:新语法 extension(StudentList list) { ... } 块里除了方法,还能写扩展属性和静态扩展成员。旧写法继续可用。

# C# # 扩展方法 # 语法糖

原文首发于 blog.csdn.net