现在的位置: 首页 > 综合 > 正文

扩展方法(C# 编程指南)

2018年08月05日 ⁄ 综合 ⁄ 共 5425字 ⁄ 字号 评论关闭

 扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。

最常见的扩展方法是 LINQ 标准查询运算符,这些运算符在现有 System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable(OfT)
类型中添加了查询功能。若要使用这些标准查询运算符,请先使用 using System.Linq 指令将它们纳入范围中。然后,任何实现了IEnumerable(OfT) 的类型看起来都具有GroupByOrderByAverage
等实例方法。在 IEnumerable(OfT) 类型的实例(如List(OfT)
Array)后键入“点”时,可以在 IntelliSense 语句结束中看到这些附加方法。

下面的示例演示如何对一个整数数组调用标准查询运算符 OrderBy 方法。括号里面的表达式是一个 lambda 表达式。很多标准查询运算符采用 lambda 表达式作为参数,但这不是扩展方法的必要条件。有关更多信息,请参见Lambda 表达式(C# 编程指南)

定义和调用扩展方法

  1. 定义一个静态以包含扩展方法。

    该类必须对客户端代码可见。有关可访问性规则的更多信息,请参见访问修饰符(C# 编程指南)

  2. 将该扩展方法实现为静态方法,并使其至少具有与包含类相同的可见性。

  3. 该方法的第一个参数指定方法所操作的类型;该参数必须以
    this
    修饰符开头。

  4. 在调用代码中,添加一条 using 指令以指定包含扩展方法类的命名空间

  5. 按照与调用类型上的实例方法一样的方式调用扩展方法。

    请注意,第一个参数不是由调用代码指定的,因为它表示正应用运算符的类型,并且编译器已经知道对象的类型。您只需通过
    n
    为这两个形参提供实参。

 

class ExtensionMethods2   
{

    static void Main()
    {           
        int[] ints = { 10, 45, 15, 39, 21, 26 };
        var result = ints.OrderBy(g => g);
        foreach (var iin result)
        {
            System.Console.Write(i + " ");
        }          
    }       
}
//Output: 10 15 21 26 39 45

扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。它们的第一个参数指定该方法作用于哪个类型,并且该参数以
this
修饰符为前缀。仅当您使用 using 指令将命名空间显式导入到源代码中之后,扩展方法才位于范围中。

下面的示例演示为 System.String 类定义的一个扩展方法。请注意,它是在非嵌套、非泛型静态类内部定义的:

namespace ExtensionMethods
{
    public staticclass MyExtensions
    {
        public staticint WordCount(this String str)
        {
            return str.Split(newchar[] {
' ', '.',
'?'
}, StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }  
}

可使用以下 using 指令将
WordCount
扩展方法放入范围中:

using ExtensionMethods;

而且,可以在应用程序中使用以下语法对该扩展方法进行调用:

string s = "Hello Extension Methods";
int i = s.WordCount();

在代码中,可以使用实例方法语法调用该扩展方法。但是,编译器生成的中间语言 (IL) 会将代码转换为对静态方法的调用。因此,并未真正违反封装原则。实际上,扩展方法无法访问它们所扩展的类型中的私有变量。

有关更多信息,请参见如何:实现和调用自定义扩展方法(C# 编程指南)

通常,您更多时候是调用扩展方法而不是实现您自己的扩展方法。由于扩展方法是使用实例方法语法调用的,因此不需要任何特殊知识即可从客户端代码中使用它们。若要为特定类型启用扩展方法,只需为在其中定义这些方法的命名空间添加using 指令。例如,若要使用标准查询运算符,请将以下using 指令添加到代码中:

using System.Linq;

(您可能还必须添加对 System.Core.dll 的引用。)您将注意到,标准查询运算符现在作为可供大多数 IEnumerable(OfT) 类型使用的附加方法显示在 IntelliSense 中。

说明:

尽管标准查询运算符没有显示在 String 的 IntelliSense 中,但它们仍然可用。

可以使用扩展方法来扩展类或接口,但不能重写扩展方法。与接口或类方法具有相同名称和签名的扩展方法永远不会被调用。编译时,扩展方法的优先级总是比类型本身中定义的实例方法低。换句话说,如果某个类型具有一个名为Process(int i) 的方法,而您有一个具有相同签名的扩展方法,则编译器总是绑定到该实例方法。当编译器遇到方法调用时,它首先在该类型的实例方法中寻找匹配的方法。如果未找到任何匹配方法,编译器将搜索为该类型定义的任何扩展方法,并且绑定到它找到的第一个扩展方法。下面的示例演示编译器如何确定要绑定到哪个扩展方法或实例方法。

下面的示例演示 C# 编译器在确定是将方法调用绑定到类型上的实例方法还是绑定到扩展方法时所遵循的规则。静态类 Extensions 包含为任何实现了IMyInterface 的类型定义的扩展方法。类ABC 都实现了该接口。

MethodB 方法永远不会被调用,因为它的名称和签名与这些类已经实现的方法完全匹配。

如果编译器找不到具有匹配签名的实例方法,它会绑定到匹配的扩展方法(如果存在这样的方法)。

namespace Extensions
{
  using System;
  using ExtensionMethodsDemo1;

     // Define extension methods for any type that implements IMyInterface.
     public staticclass Extension
     {
        public staticvoid MethodA(this IMyInterface myInterface,int i)
        {
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public staticvoid MethodA(this IMyInterface myInterface,string s)

        {
            Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        // This method is never called, because the three classes implement MethodB.
        public staticvoid MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;

    public interface IMyInterface
    {
        void MethodB();
    }

    class A : IMyInterface
    {
        public void MethodB(){Console.WriteLine("A.MethodB()");}
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj) { Console.WriteLine("C.MethodA(object obj)"); }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {
            A a = new A();
            B b = new B();
            C c = new C();
            TestMethodBinding(a,b,c);
        }

        static void TestMethodBinding(A a, B b, C c)
        {
            // A has no methods, so each call resolves to
            // the extension methods whose signatures match.
            a.MethodA(1);           // Extension.MethodA(object, int)
            a.MethodA("hello");    
// Extension.MethodA(object, string)

            a.MethodB();            // A.MethodB()

            // B itself has a method with this signature.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()

            // B has no matching method, but Extension does.
            b.MethodA("hello");    
// Extension.MethodA(object, string)

            // In each case C has a matching instance method.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");    
// C.MethodA(object)

            c.MethodB();            // C.MethodB()
        }
    }
}
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
*/

通用准则


通常,建议您只在不得已的情况下才实现扩展方法,并谨慎地实现。只要有可能,必须扩展现有类型的客户端代码都应该通过创建从现有类型派生的新类型来达到这一目的。在使用扩展方法来扩展您无法更改其源代码的类型时,您需要承受该类型实现中的更改会导致扩展方法失效的风险。

如果您确实为给定类型实现了扩展方法,请记住以下两点:

  • 如果扩展方法与该类型中定义的方法具有相同的签名,则扩展方法永远不会被调用。

  • 扩展方法被在命名空间级别放入范围中。例如,如果您在同一个名为 Extensions 的命名空间中具有多个包含扩展方法的静态类,则这些扩展方法将全部由using Extensions; 指令放入范围中。

类库的实施者不应使用扩展方法来避免创建程序集的新版本。如果您要向库中添加重要的新功能,并且您拥有源代码,则应该遵循标准 .NET Framework 程序集版本控制准则。

抱歉!评论已关闭.