簡體   English   中英

c#泛型和非泛型方法之間的重載決策

[英]c# overload resolution between generic and non-generic methods

我在互聯網和stackoverflow上做了一些基本搜索,當涉及泛型版本方法和非泛型版本方法時,我看到了很多關於重載解析的討論。 我知道重載解析是在編譯時完成的 - 因此如果我有這個代碼:

public class A<T>
{
    public void DoStuff(T value)
    {
         InternalDoStuff(value);
    }

    protected void InternalDoStuff(int value)
    {
         Console.WriteLine("Non-generic version");
    }

    protected void InternalDoStuff(T value)
    {
         Console.WriteLine("Generic version");
    }

}

public class Test
{
    static void Main (string [] args)
    {
         A<int> a = new A<int> ();
         a.DoStuff(100);
    }
}

輸出將是“通用版本”,因為“InternalDoStuff”的分辨率已由編譯器整理出來,編譯器看到的是“在DoStuff中使用T類型參數調用InternalDoStuff”。

但是我不知道這是否會有所不同:

public class B : A <int> 
{

}

public class Test
{
    static void Main (string [] args)
    {
         B b = new B ();
         b.DoStuff(100);
    }
}

現在我可以說編譯器有足夠的信息來決定“B是A的特定版本”,因此調用InternalDoStuff的非泛型版本嗎?

分析這種過載分辨率是否有任何一般原則?

第二種方法在任何意義上都與第一種方法沒有區別。

從A中派生B類絕不會改變為A類生成的IL代碼.B只是繼承這些方法。

如果查看A類的IL代碼,可以看到它編譯為調用泛型版本而不是非泛型版本 -

.method public hidebysig instance void DoStuff(!T 'value') cil managed
{
    .maxstack 8
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldarg.1 
    L_0003: call instance void
            ConsoleApplication1.A`1<!T>::InternalDoStuff(!0) <-- Generic version
    L_0008: nop 
    L_0009: ret 
}

從喬恩斯基特的文章在這里 -

正如提醒一樣,當您有兩個具有相同名稱但簽名不同的方法時,會發生重載。 在編譯時,編譯器根據參數的編譯時類型和方法調用的目標計算出它將調用哪一個。 (我假設你在這里沒有使用動態 ,這在某種程度上使事情變得復雜。)

正如他提到的那樣使用動態延遲解析直到運行時。 這段代碼將為您的兩種方法調用非泛型版本方法 -

public void DoStuff(T value)
{
   dynamic dynamicValue = value;
   InternalDoStuff(dynamicValue);
} 

請參閱Jon SkeetEric Lippert在此詳細描述的答案。

在C ++中,程序在執行期間可能創建的每種類型都必須在編譯時生成。 雖然C ++模板看起來像C#泛型,但它們的行為更類似於替換宏。 因為編譯器單獨生成可能由泛型類型替換產生的每個類,所以它可以分別為每個類評估重載決策等內容。 C#泛型不能那樣工作。

C#代碼的編譯分為兩個階段。 第一階段是在構建時完成的。 處理該階段的編譯器獲取源代碼並將其轉換為“通用中間語言”形式(相同的CIL形式用於VB.NET,F#等 - 因此名稱)。 源代碼中的每個泛型類定義(例如List<T> )都以CIL形式生成一個類定義。 在生成CIL之前,編譯器會做出關於將應用哪些函數重載的所有決策。

稍后,當程序運行時,公共語言運行時將不會為程序可能會使用的所有類生成代碼,而是推遲為每個類生成代碼,直到第一次實際使用它為止。 在此步驟中, List<int>類的東西將從List<string>List<KeyValuePair<Dictionary<int,string>, Tuple<Cat,Dog>>>生成不同的機器代碼。 程序想要使用的一組可能類型不需要限制。 可以在C#中合法地擁有一個方法,在給定通用T的參數的情況下,將使用List<T>調用泛型方法(如果給定List<T>將傳遞List<List<T>> ,並且如果鑒於這將傳遞List<List<List<T>>>等。 如果事情嵌套得太深,程序可能會因OutOfMemoryException或類似問題而死,但與C ++不同,程序可以生成的類型數量不需要在編譯時受限; 只有當程序試圖實際使用太多不同的類型時才會出現問題。

CLR能夠在生成代碼時進行某些類型的通用替換,但它不處理重載解析(如上所述,在C#到CIL轉換步驟中處理)。 雖然在CLR中使用重載解析等功能可能會有一些優勢,但它也會使CLR變得更加復雜。 如果一個特別棘手的過載解決問題需要四分之一秒,這可能不是編譯C#代碼的問題,但是對於這樣的事情停止運行時間為四分之一是不可取的。

在此輸入圖像描述 這將調用“非泛型”版本:

public class A<T>
{
    public virtual void DoStuff(T value)
    {
        InternalDoStuff(value);
    }

    protected void InternalDoStuff(int value)
    {
        Console.WriteLine("Non-generic version");
    }

    protected void InternalDoStuff(T value)
    {
        Console.WriteLine("Generic version");
    }

}
public class B : A<int>
{
    public override void DoStuff(int value)
    {
        InternalDoStuff(value);
    }
}

DoStuff InternalDoStuffInternalDoStuff的調用在編譯A<T>時被綁定。 呼叫來自B實例的事實不會以任何方式影響重載決策。

在編譯DoStuff ,有2個InternalDoStuff成員可供選擇

  • InternalDoStuff(T value)
  • InternalDoStuff(int value)

DoStuff方法傳遞一個T值,因此int的重載不起作用。 因此,只有一個適用的成員InternalDoStuff(T) ,編譯器選擇這個。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM