8

所以我讀Eric Lippert's 'Constraints are not part of the signature',現在我明白規範指定在重載解析後檢查類型約束,但我仍不清楚爲什麼必須如此。下面是Eric的例子:爲什麼類型約束不是方法簽名的一部分?

這不會編譯因爲重載解析:Foo(new Giraffe())推斷Foo<Giraffe>是最好的超負荷比賽,但隨後的類型約束失敗和編譯時錯誤被拋出。用埃裏克的話來說:

這裏的原則是重載決議(和方法類型推理)找到參數列表和每個候選方法的形式參數列表之間的最佳可能匹配。也就是說,他們看候選方法的簽名。

類型約束不是簽名的一部分,但爲什麼他們不能?在某些情況下,考慮類型約束是簽名的一部分是個壞主意?難以或不可能實現?我並不主張如果最好的選擇超載是出於任何原因不可能打電話,然後默默地回退到第二好的;我會討厭這個。我只是想了解爲什麼類型約束不能用來影響最佳過載的選擇。

我想象中的內部C#編譯器,只有重載的目的(它不會永久改寫法)以下內容:

static void Foo<T>(T t) where T : Reptile { } 

被轉化爲:

static void Foo(Reptile t) { } 

爲什麼不能將類型約束「拉進」形式參數列表?這是如何以任何不好的方式改變簽名的?我覺得它只是加強了簽名。那麼Foo<Reptile>將永遠不會被視爲超載候選人。

編輯2:難怪我的問題很混亂。我沒有正確閱讀埃裏克的博客,我引用了一個錯誤的例子。我在我認爲更合適的例子中進行了編輯。我也將標題更改爲更具體。這個問題似乎並不像我想象的那麼簡單,也許我錯過了一些重要的概念。我不太確定這是否是stackoverflow材料,因此最好將此問題/討論移到其他地方。

+1

您在問題頂部引用的關於鬣蜥的一點是爲了說明類型推斷*會考慮約束的情況,即對C中約束條件的限制,因此重載決議最終選擇本例中的*非泛型*方法。你確定*這是你想要提出這個問題的文章的相關位?問題的其餘部分似乎並不遵循邏輯。 – 2012-02-25 05:02:52

+1

我覺得你打算問爲什麼當類型推斷*成功*但推斷違反方法類型參數*的約束*的類型時,爲什麼在有替代方法時重載解析失敗。我發現這個問題很混亂,但是再一次,這是規範中令人困惑的部分。 – 2012-02-25 05:05:43

+0

你說得對。我誤讀了您的博客,並使用了錯誤的示例。難怪我的問題很困惑。我試圖盡我所能地澄清我的問題;我還將閱讀您的其他一些博客文章,看看我是否可以提高我對這個主題的理解。 – Daryl 2012-02-26 01:31:30

回答

4

C#編譯器必須不考慮類型約束部分作爲該方法的簽名,因爲它們不是爲CLR方法簽名的一部分。如果重載決議對不同語言的工作方式不同(這主要是由於運行時可能發生的動態綁定,不應該因語言不同而不同,否則所有的地址都會丟失),那將是災難性的。

爲什麼決定這些約束不會成爲方法簽名的一部分,因爲CLR是另一個問題,我只能對此做出不明確的假設。我會讓知道的人回答這個問題。

+0

但是,這只是將問題從「爲什麼不是C#的一部分,爲什麼不是CLR的一部分」這個問題轉移來的?......支持重載解析的主要問題是考慮類型約束(請參閱我的答案) 。 – 2012-02-26 02:45:22

+0

在幾種.Net語言中有幾個功能沒有被其他功能實現。主要給出的原因是輔助語法可以簡單地轉換爲適當的語法。這是簡單的決定放置哪種方法的方法之一。此外,這不可能是語言中可能含糊不清的唯一情況,編譯器如果不能解決特定的方法調用,就不會簡單地使用它發現的或給出錯誤的理由,就像它在幾十個其他情況。語言功能不是VM功能。 – 2015-09-15 15:19:19

0

如果T匹配多個約束,則會創建一個不能自動解析的歧義。例如你有一個泛型類與約束

where T : IFirst

,另一個約束

where T : ISecond

你現在想噸至是實現既IFirstISecond類。

混凝土代碼例如:

public interface IFirst 
{ 
    void F(); 
} 

public interface ISecond 
{ 
    void S(); 
} 

// Should the compiler pick this "overload"? 
public class My<T> where T : IFirst 
{ 
} 

// Or this one? 
public class My<T> where T : ISecond 
{ 
} 

public class Foo : IFirst, ISecond 
{ 
    public void Bar() 
    { 
     My<Foo> myFoo = new My<Foo>(); 
    } 

    public void F() { } 
    public void S() { } 
} 
+0

..這會導致名稱衝突;在我的問題中,錯誤是由於編譯器不知道爲方法調用選擇哪種重載方法:'Bar(new Iguana(),null)'。我可能很慢,但我還沒有看到這兩者之間的聯繫。我將更多地研究你的例子.. – Daryl 2012-02-25 04:02:55

+4

@EricJ你也可以導致方法歧義而不使用泛型 - 如果一個方法的兩個重載分別使用了'IFirst'和'ISecond',並且你試過了解決用'Foo'類型的參數調用哪個方法。我不認爲這就是爲什麼C#團隊決定不把它作爲方法簽名的一部分。 – 2012-02-26 02:16:15

+0

@ChrisShain嗯,但如果你確實對使用IFirst和ISecond的方法調用有歧義,編譯器會出錯,你可以通過將參數強制轉換爲你想要的接口來解決這個問題。如果通用約束是方法簽名的一部分,則需要在調用站點的不同約束之間進行選擇的語法,並且對庫的通用約束進行的任何更改都意味着調用代碼必須重新編譯才能繼續工作,我認爲這並不理想。 – 2017-09-08 21:24:04

相關問題