2013-07-16 226 views
5

我不完全確定如何使這個問題可讀/可理解,但聽到我的出來,我希望你能理解我的問題,當我們到最後(至少,它很容易再現)。爲什麼方法類型推斷無法推斷出類型參數?

我嘗試調用一個方法來驗證UnitTests中的結果。它具有以下特徵:

void AssertPropertyValues<TEnumerable, TElement, TProperty>(
    TEnumerable enumerable, 
    Func<TElement, TProperty> propertyPointer, 
    params TProperty[] expectedValues) 
    where TEnumerable : System.Collections.Generic.IList<TElement> 

這意味着,它需要以下輸入

  1. 任何對象,它是枚舉,幷包含相同類型intput 2)的對象。
  2. 一個Func(通常是封裝lambda表達式),它接受與1)的「contents」類型相同的對象,並返回與3)中提供的數組內容類型相同的Type對象。
  3. 與2)中Func的輸出相同類型的對象的數組。

因此,這種方法的實際執行可能看起來像這樣:

AssertPropertyValues(
    item.ItemGroups, 
    itemGroup => itemGroup.Name, 
    "Name1", "Name2", "Name3"); 

至少,這是我想它的樣子,但我碰上了衆所周知的編譯器錯誤: 「方法'X'的類型參數不能從用法推斷出來。」,這是我不明白的。它應該有我所能看到的所有信息,或者它是「協方差和逆變」問題的另一個版本?

所以現在我不得不做這樣的代替:

AssertPropertyValues(
    item.ItemGroups, 
    (ItemGroup itemGroup) => itemGroup.Name, 
    "Name1", "Name2", "Name3"); 

任何人都可以指出爲什麼這種情況下不能由編譯器推斷?

+1

您是否嘗試過使用'IEnumerable '或者......而不是'TEnumerable'?基本上''propertyPointer''參數應該與謂詞相同,例如在'Enumerable.Select'擴展方法中(因此整個構造工作都一樣)...... item.ItemGroups具有哪種類型(任何錯配,這使得明確的簽名是強制性的?)?否則我不會遇到你正面臨的問題... –

+0

我已經修復了代碼示例的格式,以便它們合理並重新命名爲你的問題;這與lambda無關。 –

+0

@AndreasNiedermair我的問題源於我最初有這個約束多個地方,並在一些地方使用它們作爲返回類型,因此不能只用接口「做」。在嘗試EricLippert的解決方案時不再是這種情況。 –

回答

24

您的問題是由於約束不被認爲是簽名的一部分,並且在類型推斷過程中從不用於進行扣減。您期待的推理如下:

  • TEnumerable是通過採用第一個參數的類型來確定的。
  • TElement被採取從TElement
  • TPropertyIList<T>實現信息由拉姆達體的類型來確定確定

但是C#從來沒有提過這第二個步驟,因爲這需要從約束考慮信息。正如你注意到的那樣,如果你在lambda中提供這些信息,那麼編譯器根據形式參數類型進行推導。

幸運的是,您的約束是完全沒有必要的。重寫你的方法有一個簡單的簽名,沒有約束:

void AssertPropertyValues<TElement, TProperty>(
    IList<TElement> sequence, 
    Func<TElement, TProperty> projection, 
    params TProperty[] expectedValues) 

現在你應該沒問題。

雖然你在這,但你應該簡化到IEnumerable<TElement>,除非你需要一個IList<T>出於某種原因。

+0

啊,我實際上認爲使用了約束。有點恥辱,它不是。正如我在對我的問題的評論中所指出的那樣,我無法簡化這個過程,因爲依賴於其他需要返回類型簽名的方法。經過一些重構,你的解決方案雖然爲我工作。謝謝! :)(是的,我需要的IList,但感謝指出它反正) –