當泛型方法推斷,擴展方法遇到泛型類型in/out時。。。


  說到泛型方法,這個是.net 2.0的時候引入的一個重要功能,c#2.0也對此作了非常好的支持,可以不需要顯試的聲明泛型類型,讓編譯器自動推斷,例如:

1 void F<T>(T value){}
2 //...
3 int i = 0;
4 F(i);

此時,編譯器可以自動推導出這里的T就是int,這極大的方便了我們寫代碼的效率。

  說到擴展方法,這個是.net 3.5的時候引入的另一個重要功能,c#3.0也在linq中大量的應用這個功能,當擴展方法是擴展一個泛型的類型時,顯然也不需要我們指定具體的泛型類型,編譯器會為我們自動推斷,例如:

1 static void F<T>(this List<T> list){}
2 //...
3 List<int> list = new List<int>();
4 list.F();

  最后說到協變和逆變(也就是c#中的in/out),這個是.net 4.0的時候引入的一個重要的功能,例如:

1 Func<string> foo = () => "Foo";
2 Func<object> bar = foo;

  然后,我們將泛型方法推斷和協變和逆變放在一起:

1 public static void Foo(this Action<string> action){}
2 //...
3 Action<object> action = o => {};
4 action.Foo();

  看起來很不錯,不過要是遇到些復雜點的會怎么樣?

1 public static void Foo(this IEnumerable<IEnumerable<object>> that) {}
2 //...
3 List<List<string>> bar = new List<List<string>>();
4 bar.Foo();

  看到這里相信所有都為c#的in/out拍手叫好,不過別急,除了out+out我們還可以玩令人抓狂的in+in:

1 public static void Foo(this Action<Action<object>> that) {}
2 //...
3 Action<Action<string>> action = a => a("O_O");
4 action.Foo();

  看到這里有沒有發現什么問題?如果你沒覺得有什么不舒服的感覺,說明你一定是懂協變和逆變的高手或是完全不懂的初學者。

  先想下定義:Action<in T>,T 是in的,也就是Action<object>里面的object可以被string這樣更具體的類型替代,而這里Action<Action<string>>里面的Action<string>被Action<object>替代了,怎么看都感覺有些怪異,不過在細細品味一下,就可以發現這個結果是完全合理的。string雖然比object更具體,不過一個接受string的方法可比一個接受object的方法更抽象,所以可以簡單的得到一個結論:in+in=>out

  文章要是到這里結束,估計很多人就認為本文是對c#的無比贊美了吧,不過,重點是這里,別忘了,多個泛型參數可以玩出很多猥瑣的東西,例如,雙/多泛型鎖定(隨便起的名字):

1 public static void Foo<T>(this Action<T, T> that) {}
2 //...
3 Action<string, object> action = (s, o) => {};
4 action.Foo();

  c#編譯器對擴展方法支持的確是有一手,這么變態的T也可以被推斷出是object,不得不佩服一把,再來看看out的情況(別忘了前面的結論in+in=>out):

1 public static void Foo<T>(this Action<Action<T>, Action<T>> that) {}
2 //...
3 Action<Action<string, object>> action = (s, o) => {};
4 action.Foo();

  c#編譯器依然表現出專業的結果,正確的推斷出了T應當是string,不過,泛型方法的類型推斷卻完全是另外一番風景:

1 public void Foo<T>(Action<T, T> that) {}
2 //...
3 Action<string, object> action = (s, o) => {};
4 Foo(action); // failed.
5 Foo<object>(action); // failed.
6 Foo((Action<object, object>)action); // succeeded.

  看到這個結果是不是想大罵c#編譯器:這也太山寨了吧。

  別急,我們還可以玩得更加浮雲:

1 public static Foo<T>(this Action<Action<T>, Action<T>> that) {}
2 // ...
3 Action<Action<List<string>>, Action<ArrayList>> bar = null;
4 bar.Foo(); // failed.
5 bar.Foo<IEnumerable>(); // succeeded.

  或者這個:

public static Foo<T>(this Action<T, T> that) {}
// ...
Action<IEnumerable<char>, IComparable<string>> action = null;
action.Foo(); // failed.
action.Foo<string>(); // succeeded.

  c#編譯器顯然不想瞎猜T的類型是什么,要求必須編程者明確指出T的具體類型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM