扩展方法 - 概述

扩展方法在 C#3.0 中引入。扩展方法扩展并向现有类型添加行为,而不创建新的派生类型,重新编译或以其他方式修改原始类型。*当你无法修改要增强的类型的源时,它们尤其有用。*可以为系统类型,由第三方定义的类型以及你自己定义的类型创建扩展方法。可以调用扩展方法,就好像它是原始类型的成员方法一样。这允许方法链用于实现 Fluent 接口

通过向静态类添加静态方法来创建扩展方法,该静态类与要扩展的原始类型不同。持有扩展方法的静态类通常是为了保存扩展方法而创建的。

扩展方法采用特殊的第一个参数来指定要扩展的原始类型。第一个参数用关键字 this(它构成了在 C#中的 this 的特殊和独特用途 - 应理解为与使用 this 不同,后者允许引用当前对象实例的成员)。

在以下示例中,要扩展的原始类型是类 stringString 已通过 Shorten() 方法进行了扩展,该方法提供了缩短的附加功能。已创建静态类 StringExtensions 以保存扩展方法。扩展方法 Shorten() 通过特别标记的第一个参数显示它是 string 的扩展。为了表明 Shorten() 方法扩展 string,第一个参数用 this 标记。因此,第一个参数的完整签名是 this string text,其中 string 是扩展的原始类型,text 是所选择的参数名称。

static class StringExtensions
{
    public static string Shorten(this string text, int length) 
    {
        return text.Substring(0, length);
    }
}

class Program
{
    static void Main()
    {
        // This calls method String.ToUpper()
        var myString = "Hello World!".ToUpper();

        // This calls the extension method StringExtensions.Shorten()
        var newString = myString.Shorten(5); 

        // It is worth noting that the above call is purely syntactic sugar
        // and the assignment below is functionally equivalent
        var newString2 = StringExtensions.Shorten(myString, 5);
    }
}

.NET 小提琴现场演示

作为扩展方法第一个参数传递的对象 (伴随着 this 关键字)是调用扩展方法的实例。

例如,执行此代码时:

"some string".Shorten(5);

参数的值如下:

text: "some string"
length: 5

请注意,扩展方法仅在与其定义位于同一名称空间中时才可用,如果使用扩展方法由代码显式导入名称空间,或者扩展类是无名称空间。 .NET 框架指南建议将扩展类放在它们自己的命名空间中。但是,这可能会导致发现问题。

这导致扩展方法和正在使用的库之间没有冲突,除非明确地引入可能冲突的名称空间。例如 LINQ Extensions

using System.Linq; // Allows use of extension methods from the System.Linq namespace

class Program
{
    static void Main()
    {
        var ints = new int[] {1, 2, 3, 4};

        // Call Where() extension method from the System.Linq namespace
        var even = ints.Where(x => x % 2 == 0); 
    }
}

.NET 小提琴现场演示

从 C#6.0 开始,也可以将 using static 指令放到包含扩展方法的中。例如,using static System.Linq.Enumerable;。这使得来自该特定类的扩展方法可用,而无需将来自同一名称空间的其他类型引入范围。

当具有相同签名的类方法可用时,编译器将其优先于扩展方法调用。例如:

class Test
{
   public void Hello()
   {
       Console.WriteLine("From Test");
   }
}

static class TestExtensions
{
    public static void Hello(this Test test)
    {
        Console.WriteLine("From extension method");
    }
}

class Program
{
    static void Main()
    {
        Test t = new Test();
        t.Hello(); // Prints "From Test"
    }
}

.NET Fiddle 上的现场演示

请注意,如果有两个具有相同签名的扩展函数,并且其中一个在同一名称空间中,那么将优先考虑该名称。另一方面,如果 using 访问了它们,那么编译时错误将随之发生:

以下方法或属性之间的调用不明确

请注意,通过 originalTypeInstance.ExtensionMethod() 调用扩展方法的语法方便是一个可选的便利。该方法也可以以传统方式调用,以便将特殊的第一参数用作该方法的参数。

即,以下两项工作:

//Calling as though method belongs to string--it seamlessly extends string
String s = "Hello World";
s.Shorten(5);  

//Calling as a traditional static method with two parameters
StringExtensions.Shorten(s, 5);