2015-09-17 25 views
17

推斷請看下面的例子:「雙級」通用方法參數與委託

class Test 
{ 
    public void Fun<T>(Func<T, T> f) 
    { 
    } 

    public string Fun2(string test) 
    { 
     return ""; 
    } 

    public Test() 
    { 
     Fun<string>(Fun2); 
    } 
} 

這將編譯好。

我想知道爲什麼我不能刪除<string>泛型參數?我收到一個錯誤,無法從使用情況中推斷出它。

我明白,這樣的推論對編譯器來說可能是一個挑戰,但似乎是可能的。

我想解釋一下這個行爲。

編輯回答喬恩·漢娜的回答是:

那麼,爲什麼這個工程?

class Test 
{ 
    public void Fun<T1, T2>(T1 a, Func<T1, T2> f) 
    { 
    } 

    public string Fun2(int test) 
    { 
     return test.ToString(); 
    } 

    public Test() 
    { 
     Fun(0, Fun2); 
    } 
} 

在這裏,我綁定T1 a只有一個參數,但T2似乎是同樣困難。

+0

重新編輯,您現在已經提供了編譯器 - 通過「Fun(0,Fun2)」調用。編譯器現在知道它需要一個方法,來自'Fun2'方法組,它有'T1'類型,在這種情況下'int'。這將其縮小爲一種方法,因此可以推斷使用哪一種方法。 –

+0

但是首先有一個Fun2,所以它不會真正「縮小」任何東西。 –

+0

C#編譯器拒絕將僅包含一個方法的方法組作爲委託來處理。您必須提供信息才能從組中選擇一種方法,以便爲此而煩惱。如果它看着這個團隊,意識到只有一種方法並選擇了它,那很好,但可悲的是,這不是它的工作原理。 –

回答

13

它不能推斷出類型,因爲這裏沒有定義類型。

Fun2不是Func<string, string>,它是可以分配給Func<string, string>的東西。

所以如果你使用:

public Test() 
{ 
    Func<string, string> del = Fun2; 
    Fun(del); 
} 

或者:

public Test() 
{ 
    Fun((Func<string, string>)Fun2); 
} 

就會明確地從Fun2創建Func<string, string>,和通用類型推斷相應的工作。

相反,當你這樣做:

public Test() 
{ 
    Fun<string>(Fun2); 
} 

那麼集合重載Fun<string>的只包含一個接受一個Func<string, string>,編譯器可以推斷,要使用Fun2這樣。

但是你問它來推斷都基於參數的類型泛型類型,並根據泛型類型參數的類型。這是一個比它可以做的任何推斷類型都要大的問題。

(這是值得考慮的是,在.NET 1.0不僅是代表不是通用的,所以你將不得不定義delgate string MyDelegate(string test) - 但它也有必要與構造Fun(new MyDelegate(Fun2))創建對象。語法已更改爲使用的代表更容易以多種方式進行,但隱含地使用Fun2作爲Func<string, string>仍然是幕後代表對象的構造)。

那麼,爲什麼這個工程?

class Test 
{ 
    public void Fun<T1, T2>(T1 a, Func<T1, T2> f) 
    { 
    } 

    public string Fun2(int test) 
    { 
     return test.ToString(); 
    } 

    public Test() 
    { 
     Fun(0, Fun2); 
    } 
} 

因爲那麼它可以推斷出,爲了:

  1. T1int
  2. Fun2被分配到Func<int, T2>一些T2
  3. Fun2可以分配給一個Func<int, T2>如果T2string。因此T2是字符串。

特別是,返回類型Func可以從函數中推斷出來,只要你有參數類型。這也是一樣的(值得編譯器付出努力),因爲它在Linq的Select中很重要。這引起了一個相關的案例,事實是隻有x.Select(i => i.ToString())我們沒有足夠的信息來知道lambda轉換爲什麼。一旦我們知道了xIEnumerable<T>IQueryable<T>我們知道,我們要麼有Func<T, ?>Expression<Func<T, ?>>,其餘的可以從那裏推斷。

這也是這裏值得注意的,即推斷返回類型是不受的模糊性推導的其他類型。考慮如果我們的Fun2(需要stringint)在同一個班級。這是有效的C#重載,但會扣除Func<T, string>的類型Fun2可以投射到不可能;兩者都是有效的。

但是,雖然.NET允許在返回類型上重載,但C#沒有。因此,一旦確定了T的類型,則從方法(或lambda)創建的Func<T, TResult>的返回類型中沒有有效的C#程序可能不明確。這相對容易,加上巨大的用處,使得編譯器可以很好地爲我們推斷。

+0

當然,我的編輯靈感來自「選擇」。你的意思是在這種情況下實現返回類型推斷更簡單嗎?爲了避免重載問題? //編輯,這就是你寫的,我們有一場比賽;-)我很想看到在ECMA中,必須挖掘它。 –

+0

剛剛添加了一點,因爲它確實更簡單,因爲在所有情況下都有可能,但在某些情況下,推斷參數類型是不明確的。 (更糟糕的是,如果他們確實增加了對明確情況的支持,那麼可以通過合理的添加來破壞代碼,使其再次變得模糊不清)。 –

+0

爲什麼不能推斷它? –

3

您希望編譯器從Fun2推斷出string,這對C#編譯器提出了太多要求。這是因爲它在Test中引用時將Fun2視爲方法組,而不是代表。

如果你改變了代碼由Fun2所需要的參數傳遞和Fun調用它,那麼就需要消失了,因爲你現在有一個string參數,允許類型推斷:

class Test 
{ 
    public void Fun<T>(Func<T, T> f, T x) 
    { 
     f(x); 
    } 

    public string Fun2(string test) 
    { 
     return test; 
    } 

    public Test() 
    { 
     Fun(Fun2, ""); 
    } 
} 

要回答您編輯的問題版本,通過提供T1類型,您現在已經提供了編譯器 - 通過Fun(0, Fun2);呼叫附加信息。它現在知道它需要一個方法,來自Fun2方法組,它有一個T1參數,在這種情況下爲int。這將其縮小到一種方法,因此它可以推斷使用哪一種方法。'