天天看點

C#擴充方法原理及其使用

1、寫在前面

今天群裡一個小夥伴問了這樣一個問題,擴充方法與執行個體方法的執行順序是什麼樣子的,誰先誰後(這個問題會在文章結尾回答)。是以寫了這邊文章,力圖從原理角度解釋擴充方法及其使用。

以下為主要内容:

  • 什麼是擴充方法
  • 擴充方法原理及自定義擴充方法
  • 擴充方法的使用及其注意事項

2、什麼是擴充方法

      一般而言,擴充方法為現有類型添加新的方法(從面向對象的角度來說,是為現有對象添加新的行為)而無需修改原有類型,這是一種無侵入而且非常安全的方式。擴充方法是靜态的,它的使用和其他執行個體方法幾乎沒有什麼差別。常見的擴充方法有Linq擴充、有IEnumerable擴充等。

     先讓我們來感受一下.NET中自帶的擴充方法,其中OrderBy和Aggregate都是系統自帶的擴充方法

using System;
using System.Collections.Generic;
using System.Linq;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> lst = new List<int> { 2, 1, 4, 3 };

            string result = lst.OrderBy(p => p).Aggregate(string.Empty, (next, p) => next += p + ",");

            Console.WriteLine(result);

            Console.ReadLine();
        }
    }
}      

輸出結果:

C#擴充方法原理及其使用

是不是感覺擴充方法很優美,使用起來和執行個體方法幾乎沒有差別。不得不說.NET在這方面做得很精緻,很讓人欽佩,那麼接下來我們來看看擴充方法的原理

3、擴充方法原理及自定義擴充方法

首先我們,先看看如何自定義擴充方法

using System;
using TestExtension;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("2".ToInt32());

            Console.ReadLine();
        }
    }
}

namespace TestExtension
{
    public static class StringExtension
    {
        public static int ToInt32(this string str)
        {
            if (int.TryParse(str, out int result))
            {
                return result;
            }

            throw new ArgumentException("無法轉換為Int32類型");
        }
    }
}      

通過以上執行個體,我們可以知道自定義擴充方法需要做到:

  • 必須是靜态類,擴充方法也為靜态方法
  • 此方法的第一個參數指定方法所操作的類型;此參數前面必須加上 this 修飾符
  • 在調用代碼中,如何不再同一個命名空間,需要添加 using 指令,導入需要調用的擴充方法所在的命名空間
  • 需要注意的是,第一個this标記的參數并非是實參,而是辨別該擴充所指定的類型,調用的時候隻需要提供this後的形參即可

接下來我們來探究一下擴充方法反編譯後的效果:

這是StringExtension編譯後的代碼,可以看到擴充方法在編譯後被标記了ExtensionAttribute這個特性,也就是說擴充方法在編譯期就已經被綁定成擴充方法了

.class public auto ansi abstract sealed beforefieldinit TestExtension.StringExtension
    extends [System.Runtime]System.Object
{
    .custom instance void       

[System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute

::.ctor() = (
        01 00 00 00
    )
    // Methods
    .method public hidebysig static 
        int32 ToInt32 (
            string str
        ) cil managed 
    {
        .custom instance void       
::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x2050
        // Code size 31 (0x1f)
        .maxstack 2
        .locals init (
            [0] int32,
            [1] bool,
            [2] int32
        )

        IL_0000: nop
        IL_0001: ldarg.0
        IL_0002: ldloca.s 0
        IL_0004: call bool [System.Runtime]System.Int32::TryParse(string, int32&)
        IL_0009: stloc.1
        IL_000a: ldloc.1
        IL_000b: brfalse.s IL_0012

        IL_000d: nop
        IL_000e: ldloc.0
        IL_000f: stloc.2
        IL_0010: br.s IL_001d

        IL_0012: ldstr "無法轉換為Int32類型"
        IL_0017: newobj instance void [System.Runtime]System.ArgumentException::.ctor(string)
        IL_001c: throw

        IL_001d: ldloc.2
        IL_001e: ret
    } // end of method StringExtension::ToInt32

} // end of class TestExtension.StringExtension      

我們看一下調用後的效果,和直接調用靜态方法一樣TestExtension.StringExtension::ToInt32(string) ,至此,我們已經知道了擴充方法的使用了,編譯器綁定,底層調用和靜态調用一直,這也解釋了一個問題,就是當類型為空的時候,為什麼調用擴充方法了

.namespace Test
{
    .class private auto ansi beforefieldinit Test.Program
        extends [System.Runtime]System.Object
    {
        // Methods
        .method private hidebysig static 
            void Main (
                string[] args
            ) cil managed 
        {
            // Method begins at RVA 0x207b
            // Code size 24 (0x18)
            .maxstack 8
            .entrypoint

            IL_0000: nop
            IL_0001: ldstr "2"
            IL_0006: call int32 TestExtension.StringExtension::ToInt32(string)
            IL_000b: call void [System.Console]System.Console::WriteLine(int32)
            IL_0010: nop
            IL_0011: call string [System.Console]System.Console::ReadLine()
            IL_0016: pop
            IL_0017: ret
        } // end of method Program::Main

        .method public hidebysig specialname rtspecialname 
            instance void .ctor () cil managed 
        {
            // Method begins at RVA 0x2094
            // Code size 8 (0x8)
            .maxstack 8

            IL_0000: ldarg.0
            IL_0001: call instance void [System.Runtime]System.Object::.ctor()
            IL_0006: nop
            IL_0007: ret
        } // end of method Program::.ctor

    } // end of class Test.Program

}      

4、擴充方法的使用及其注意事項

擴充方法雖然很好用,但是如果我們擴充的對象發生了版本疊代,則會增加擴充方法失效的風險。

一下是在使用擴充方法時需要注意的地方

  • 擴充方法與該類型中定義的方法具有相同的簽名,編譯器總是綁定到該執行個體方法,也就是擴充方法永遠不會被調用,這也就回答了題目剛開始所說的問題。同時這個地方應該是考慮到了程式安全的問題,不然很容易出現代碼注入問題。
  • 當出現命名空間不同時,則需要使用

    using導入命名空間

  • 同時擴充方法可以被修飾為internal,public,但需要類和擴充方法保持同樣的修飾辨別
  • 以上為本篇文章的主要内容,希望大家多提意見,如果喜歡記得點個推薦哦

    作者:

    艾心

    出處:

    https://www.cnblogs.com/edison0621/

    本文版權歸作者和部落格園共有,歡迎轉載,轉載時保留原作者和文章位址即可。