调用泛型方法时,指定类型实参常常会显得很多余。为简化工作,c#2编译器被赋予了一定的“智能”,让你在调用方法时,不需要显式声明类型实参。
在深入讨论这个主题之前,必须强调一下:类型推断只适用于泛型方法,不适用于泛型类型。
例子:static List<T> MakeList<T>(T first,T second)
...
List<String> list=MakeList<string>("Line1","Line2");
方法中的每个参数都声明为类型T。即使拿掉方法中调用表达式的<string>部分,也很容易看出在调用方法时,为T使用的类型实参是string。编译器允许将其省略,变成:
List<string> list=MakeList("Line2","Line2");
使用类型推断并非总是意味着增加了可读性。某些情况下会造成读者更难判断你要使用什么类型实参-即使编译器能轻松地判断出来。建议具体情况具体分析。在推断可用的时候,让编译器推断类型实参,多数情况下都没有问题。
注意,编译器之所以能够确定我们要将string作为类型实参使用,是因为对list的赋值也在起作用,它也指定了并且必须指定类型实参。然而,假如编译器推断错了你想要使用的类型实参,你仍可能得到一个编译错误。
编译器为什么会弄错呢?假定实际想用object作为类型实参。我们传递的方法实参仍是有效的,编译器认为我们想要的是string,因为传递的两个参数都是字符串。修改其中一个实参,把它显示转换为object,类型推断就会失败。因为一个方法实参指出T应该是string,另一个指出T应该是一个object。此时,编译器会考虑这种情况并且告知用户将T设为object能满足一切情况,将T设为string则不能。但是,在c#语言规范中,只提供了有限的推断步骤。其基本步骤如下:
1.针对每一个方法实参(普通圆括号中的参数,而不是尖括号中的),都尝试推断出泛型方法的一些类型实参。这一步是相当简单的技术
2.验证步骤一的所有结果都是一致的—换言之,假如从一个方法实参中推断出了某类型参数的类型实参,但根据另外一个类型实参推断出同一个类型参数具有另一个类型实参,则此次方法调用的推断失败。
3.验证泛型方法需要的所有类型实参都已被推断出来,不能让编译器推断一部分,自己显式指定另一部分。要么全部推断,要么全部指定。
类型推断可以结合基于类型参数数量的类型名称重载的想法,来简化泛型类型的使用。
程序清单:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace 类型推断 { class Program { public static List<T> MakeList<T>(T f, T s) { List<T> list = new List<T>(); list.Add(f); list.Add(s); return list; } static void Main(string[] args) { List<object> list = MakeList("123", (object)"456"); foreach (string s in list) { Console.WriteLine(s); } } } }
运行结果:
试验知:类型推断中,如果类型如object,至少要强制转换类型实参中的一个参数,否则推断失败。