[C#] 泛型 = 樣板?

Eric Lippert 的部落格看到一段挺有意思的 C# code,以下是我稍微修改過的版本:

class Program
{
    static void Main(string[] args)
    {
        C.DoIt<string>("Mike");
    }
}

public class C
{
    public static void DoIt<T>(T t)
    {
        ReallyDoIt(t);
    }
    
    public static void ReallyDoIt(string s)
    {
        System.Console.WriteLine("Natural version: " + s);
    }
    
    public static void ReallyDoIt<T>(T t)
    {
        System.Console.WriteLine("Generic version: " + t);
    }
}

當主程式呼叫 C.DoIt<string>("Mike") 時,C.DoIt 方法所呼叫的 ReallyDoIt 是哪個版本?

執行結果會輸出 "Generic version: Mike",也就是說,呼叫的是泛型(generic)的版本。

原因在於,當編譯器碰到 overloaded methods 時,會根據呼叫時所傳遞的參數型別來決定哪一個 method 才是最速配(match)的版本。在這個例子當中,由於呼叫時傳入的參數型別是泛型 T,於是編譯器會尋找同樣需要傳入泛型參數的 ReallyDoIt 方法。

如果改為直接呼叫 C.ReallyDoIt("Mike"),那麼執行的結果就會是 "Natural version: Mike"。

上面的解釋聽起來很合理,似乎沒什麼特別,但作者卻點出一個我們可能忽略的技術細節。寫過 C++ 的人,可能會以「泛型其實就是樣板(template)型別」來理解和解釋泛型,但從上面的例子可以發現,C# 的泛型和 C++ 的樣板其實骨子裡是不同的。

C++ 編譯器在處理樣板類別時,會根據實際需要的參數型別產生多種版本的類別代碼,也就是說,實際編譯出來的 code 全都是可直接繫結的真實型別,根本沒有樣板這東西了。但 C# 的泛型--如 Lippert 所言--就只是泛型;編譯器並不會因為你在呼叫泛型 method 時分別用到了 string 和 int 兩種參數而編譯出兩種版本的 method 代碼。觀察反組譯出來的 IL code 便可確認這點(僅列出宣告部分):
.class public auto ansi beforefieldinit C
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
}

.method public hidebysig static void DoIt(!!T t) cil managed
{
}

.method public hidebysig static void ReallyDoIt(!!T t) cil managed
{
}

.method public hidebysig static void ReallyDoIt(string s) cil managed
{
}
}

1 則留言:

技術提供:Blogger.
回頂端⬆️