2010-11-04 69 views
12

這段代碼不在LINQPad中編譯。爲什麼不委託使用值類型的逆變函數?

void Main() 
{ 
    (new[]{0,1,2,3}).Where(IsNull).Dump(); 
} 

static bool IsNull(object arg) { return arg == null; } 

編譯器的錯誤信息是:

否過載關於 'UserQuery.IsNull(對象)' 匹配委託 'System.Func'

它爲一個字符串數組,但對int[]不起作用。這顯然與拳擊有關,但我想知道細節。

+0

應該是'.Where(x => IsNull(x))'? – 2010-11-04 11:50:27

+0

@Joel Etherton:同樣的事情(差不多)。 – leppie 2010-11-04 11:52:19

+0

嘗試使'IsNull'通用。廢話,這就是你問的:) – leppie 2010-11-04 11:53:21

回答

38

給出的答案(沒有變化涉及價值類型)是正確的。當變量類型參數之一是值類型時,協變和逆變不起作用的原因如下。假設它確實起作用並顯示事情發生嚴重錯誤:

Func<int> f1 =()=>123; 
Func<object> f2 = f1; // Suppose this were legal. 
object ob = f2(); 

好的,會發生什麼? f2與f1參考相同。因此無論f1如何,f2都會。 f1做什麼?它在堆棧中放置一個32位整數。這項任務做了什麼?它需要堆棧中的內容並將其存儲在變量「ob」中。

拳擊教學在哪裏?沒有一個!我們只是將一個32位整數存儲到存儲器中,而不是一個整數,而是一個64位指針指向包含盒裝整數的堆位置。所以你只是錯過了堆棧,並用無效的引用破壞了變量的內容。這個過程很快就會消失。

那麼拳擊教學應該去哪裏?編譯器必須在某處生成一個裝箱指令。在調用f2後無法執行,因爲編譯器認爲f2返回已裝箱的對象。它不能進入​​到f1的調用,因爲f1返回一個int,而不是一個盒裝的int。它不能在對f2的呼叫和對f1 的呼叫之間進行,因爲它們是相同的代表;沒有'之間'

,我們可以在這裏做的唯一的事情是使第二行實際上是說:

Func<object> f2 =()=>(object)f1(); 

,現在我們沒有f1和f2之間的參考身份了,所以什麼變化點?具有協變參考轉換的整點是保留參考標識

無論你如何切片,事情都會出現可怕的錯誤,並且無法解決問題。因此,最好的做法是首先使該功能非法。泛型委託類型不允許出現差異,其中值類型會變化。

更新:我應該在這裏注意到我的答案,在VB中,你可以將int返回的委託轉換爲返回對象的委託。 VB簡單地生成第二個委託,該委託將調用包裝到第一個委託並將結果包裝起來。 VB選擇放棄參考轉換保留對象標識的限制。

這說明了C#和VB的設計哲學中一個有趣的區別。在C#中,設計團隊總是在思考:「編譯器如何發現用戶程序中可能存在的錯誤並引起他們的注意?」而VB團隊正在思考「我們如何才能弄清楚用戶可能會發生什麼,並代表他們做這件事?」簡而言之,C#哲學是「如果你看到某些東西,說些什麼」,而VB哲學則是「盡我所指,而不是我說的」。兩者都是完全合理的哲學;有趣的是,由於設計原理,看到兩種具有幾乎相同特徵集的語言在這些小細節上有所不同。

0

它不適用於int,因爲沒有對象。

嘗試:

void Fun() 
{ 
    IEnumerable<object> objects = (new object[] { 0, 1, null, 3 }).Where(IsNull); 

    foreach (object item in objects) 
    { 
     Console.WriteLine("item is null"); 
    } 
} 

bool IsNull(object arg) { return arg == null; } 
3

因爲Int32是值類型和反方不會對價值類型的工作。

你可以試試這個:

(new **object**[]{0,1,2,3}).Where(IsNull).Dump(); 
+2

轉儲()可能是一個擴展方法 – Konstantin 2010-11-04 12:18:29

相關問題