实现类的扩展方法

已完成

通常,可通过两种方法将方法添加到现有类型:

  • 修改该类型的源代码。 如果还添加任何专用数据字段以支持该方法,则修改源会创建中断性变更。
  • 在派生类中定义新方法。 不能以这种方式为其他类型的继承添加方法,例如结构和枚举。 也不能用于向密封类“添加”方法。

扩展方法允许向现有类型“添加”方法,而无需修改类型本身或在继承类型中实现新方法。 扩展方法也不必与扩展的类型位于同一程序集中。 调用扩展方法,就好像它是类型的已定义成员一样。

扩展方法

扩展方法使你可以“添加”方法到现有类型,而无需创建新的派生类型、重新编译或其他修改原始类型。 扩展方法是静态方法,但它们被调用,就像它们是扩展类型的实例方法一样。 对于用 C#、F# 和 Visual Basic 编写的客户端代码,调用扩展方法和类型中定义的方法之间没有明显的区别。

编译时绑定扩展方法

可以使用扩展方法扩展类或接口,但不能重写它们。 从不调用与类方法同名和签名的扩展方法。 在编译时,扩展方法的优先级始终低于类型本身中定义的实例方法。 换句话说,如果类型具有名为 Process(int i)的方法,并且具有具有相同签名的扩展方法,编译器将始终绑定到实例方法。 当编译器遇到方法调用时,它会在类型的实例方法中查找匹配项。 如果未找到匹配的实例方法,编译器将搜索为类型定义的任何扩展方法。 编译器绑定到它找到的第一个扩展方法。

以下示例演示 C# 编译器在确定是将方法调用绑定到类型上的实例方法还是绑定到扩展方法时所遵循的规则。


namespace ExtensionMethodDemo.PersonNamespace
{
    // Define a simple class with properties and methods
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }

        public void Introduce()
        {
            Console.WriteLine($"Hi, I'm {FirstName} {LastName}, and I'm {Age} years old.");
        }
    }
}

namespace ExtensionMethodDemo.PersonExtensionsNamespace
{
    using ExtensionMethodDemo.PersonNamespace;

    // Define an extension method for the Person class
    public static class PersonExtensions
    {
        public static void DisplayFullName(this Person person)
        {
            Console.WriteLine($"Full Name: {person.FirstName} {person.LastName}");
        }

        public static bool IsAdult(this Person person)
        {
            return person.Age >= 18;
        }

        public static void Introduce()
        {
            Console.WriteLine($"Extension - Hi, it's nice to meet you.");
        }

        // Extension method attempting to override Introduce
        public static void Introduce(this Person person, string greeting)
        {
            Console.WriteLine($"{greeting}, I'm {person.FirstName} {person.LastName}, and I'm {person.Age} years old.");
        }
    }
}

namespace ExtensionMethodDemo
{
    using ExtensionMethodDemo.PersonNamespace;
    using ExtensionMethodDemo.PersonExtensionsNamespace;

    class Program
    {
        static void Main(string[] args)
        {
            // Create instances of the Person class
            Person person1 = new Person { FirstName = "FName1", LastName = "LName1", Age = 25 };
            Person person2 = new Person { FirstName = "FName2", LastName = "LName2", Age = 16 };

            // Use the methods of the Person class
            person1.Introduce();
            person2.Introduce();

            // Use the extension methods
            person1.DisplayFullName();
            Console.WriteLine($"Is {person1.FirstName} an adult? {person1.IsAdult()}");

            person2.DisplayFullName();
            Console.WriteLine($"Is {person2.FirstName} an adult? {person2.IsAdult()}");

            // Use the extension method that attempts to override Introduce
            person1.Introduce("Hello");
            person2.Introduce("Greetings");
        }
    }
}
// Output:
//     Hi, I'm FName1 LName1, and I'm 25 years old.
//     Hi, I'm FName2 LName2, and I'm 16 years old.
//     Full Name: FName1 LName1
//     Is FName1 an adult? True
//     Full Name: FName2 LName2
//     Is FName2 an adult? False
//     Hello, I'm FName1 LName1, and I'm 25 years old.
//     Greetings, I'm FName2 LName2, and I'm 16 years old.

在此示例中,Person 类具有三个属性:FirstName、LastName和 Age。 Person 类还具有一个实例方法,Introduce 向控制台写入消息。

PersonExtensions 类定义 Person 类的扩展方法。 PersonExtensions 类包含以下扩展方法:

  • DisplayFullName:将人员的全名写入控制台。
  • IsAdult:返回一个布尔值,该值指示此人是否为成人。
  • Introduce:将消息写入控制台。 此方法有两个重载:一个没有参数,一个具有字符串参数。
  • Introduce(this Person person, string greeting):使用自定义问候语将消息写入控制台。
  • Introduce():将默认消息写入控制台。

Main 方法创建 Person 类的两个实例,并在每个实例上调用 Introduce 方法。 执行 Introduce 类中定义的 Person 方法。 不会调用 Introduce 类中没有参数的 PersonExtensions 方法,因为 Person 类已具有具有相同签名的实例方法 Introduce。 编译器遵循编译时绑定扩展方法的规则,并优先于类型本身中定义的实例方法。 Main 方法还使用自定义问候语调用扩展方法 DisplayFullName、IsAdult和重载 Introduce 方法。 扩展方法按预期执行。

一般准则

扩展方法是在整个 .NET 生态系统中创建可重用功能的重要选项。 但是,只要合理且可能这样做,修改对象的代码或派生新类型,仍被视为可取。 当原始源不在你的控制之下、当派生对象不合适或不可能时,或者不应在其适用范围之外公开功能时,扩展方法是一个不错的选择。

使用扩展方法扩展未控制其源代码的类型时,将面临类型实现更改会导致扩展方法中断的风险。 如果确实为给定类型实现扩展方法,请记住以下几点:

  • 如果扩展方法与类型中定义的方法具有相同的签名,则不会调用扩展方法。
  • 扩展方法在命名空间级别进入范围。 例如,如果多个静态类包含名为 Extensions的单个命名空间中的扩展方法,它们都将由 using Extensions; 指令引入范围。

对于实现的类库,不应使用扩展方法来避免递增程序集的版本号。 如果要向拥有源代码的库添加重要功能,请遵循程序集版本控制的 .NET 准则。