在面向对象语言中,我们可以编写一个元素为某个专用类型(可能需要为此创建一个ListElement)的List类,或者使用一个非常通用、允许添加任何类型元素的基类(在.NET中,首先想到的是System.Object)。这两种方法都有缺点。使用通用类型的基类时,很可能会把不相容的元素添加到列表中;如果使用元素为专用类型的列表,只是推迟此问题的发生,因为实际类型是在最后封装到这个类中。泛型提供了这个问题的解决方法。
一般而言,一个泛型类型G可以应用于一个或多个其他类型——如O1、O2等——其思想是,G的实现不需要对Ox等类型知道很多。
C#函数式程序设计之泛型函数
任何方法在为方法签名添加一个或多个类型参数后,就成为泛型。如下所示:
1
2
3
4 |
static
void OutputThing<T>(T thing) { Console.WriteLine( "Thing: {0}" ,thing); } |
这里T是类型变量,它出现在方法名后面的一对尖括号中。这样声明后,这个参数就可以在参数列表中和方法体重当成类型使用。这个方法并不关心这个thing元素和它的类型,它只是把它的值传递给其他方法以进一步处理。
下面是用显示类型参数调用这个函数:
1
2 |
OutputThing< string >( "A string" ); OutputThing< int >(42); |
使用显式类型参数意味着这个类型要受到Visual Studio的智能感知和C#编译器的双重检查。如下面这个调用会产生编译错误信息:
1 |
OutputThing< double >( "A string" ); |
错误如下:
尽管这个例子很简单,但它说明泛型的一个作用:不是使用类型对象的参数,而是在调用中显式说明类型,这回启动严格的类型检查。
另一方面,许多程序员认为直接使用显式类型过于草率,OutputThing方法也可以像下面这样调用:
1
2 |
OutputThing( "A string" ); OutputThing(42); |
这里使用类型推断机制可以根据字面值推断它的类型。这并不是一个无类型调用,在OutputThing方法中还有一个类型变量T。上面两个调用语句实际上T分别代表了string和int,编译器会在编译时自动为它们替换为该类型。
然而,许多程序员把C#类型推断看成是一个只有在必要时才使用的功能,而不是一个任何时候都可以使用的通用工具,这是正确的,使用类型推断,会让复杂的代码的可读性变差。
下面是一个稍微复杂(同时也比较有用)的有关泛型的李子
1
2
3
4
5
6
7 |
static
void Apply<T>(IEnumerable<T> sequence, Action<T> action) { foreach
(T item in
sequence) { action(item); } } |
本例中也有一个类型参数T,但是它作用在这个方法的两个参数上,它们之间存在一种关联:第一个参数是事件序列,第二个参数是一个委托,此委托作用的参数就是在此序列中的事件类型。这正是泛型表现出强大功能的地方,如果不使用泛型,但仍然希望此方法可以灵活应用于不同类型,就无法表现出这种关联性。
泛型元素并不关心类型本身。下面是对Apply方法的调用:
1
2 |
var values = new
List< int > { 10, 20, 35 }; Apply(values, v => Console.WriteLine( "Value:{0}" , v)); |
调用Apply方法,但是省略了泛型参数,编译器需要推断Apply调用语句中参数T的类型,为此需要检查参数。
C#函数式程序设计之泛型类
也可以给类添加类型信息。在这种情况下,类型参数的作用域是整个类,但其用法与前面完全一样:它代表某个类型。下面的例子是一个链表的实现(不完整):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 |
public
class ListItem<T> { public
ListItem(T value) { this .value = value; } private
ListItem(T value, ListItem<T> next) : this (value) { this .next = next; } private
readonly T value; public
T Value { get
{ return
value; } } private
ListItem<T> next; public
ListItem<T> Prepend(T value) { return
new ListItem<T>(value, this ); } } |
ListItem类有一个泛型参数T,这个参数封装在ListItem容器中,在类中任何需要显式类型的位置都可以使用这个类。使用泛型会使ListItem类更加灵活,因为它可以把任何其他类型的信息封装到链表列中。
同时,泛型系统会使编译器的类型检查功能更强大。上例中的Prepend方法只接受T类型的值。从ListItem类的实例角度来看,T是固定的,换言之,新的值必须与当前实例的值具有相同类型。分析下面的例子:
1
2
3
4
5
6 |
public
static void List() { var
intItem = new
ListItem< int >(10); var
secondItem = intItem.Prepend(20); var
thirdItem = secondItem.Prepend( "string" ); } |
这里,当ListItem类与new关键字一起使用时,要在类名中添加一个类型参数,现在保存在ListItem变量中的实例是类型化的,包含了类型为int的值。其结果是,Prepend方法可以接受一个为int的类型参数,因此,上例会报错:
泛型语法的最后一个部分是多个类型参数。在任何情况下,只要使用类型参数,就不会只有一个。看下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 |
public
class Test<T1, T2> { private
Test(T1 val1, T2 val2) { this .val1 = val1; this .val2 = val2; } private
readonly T1 val1; public
T1 Val1 { get { return
val1; } } private
readonly T2 val2; public
T2 Val2 { get { return
val2; } } } |
使用多个泛型参数实际上并没有什么特别之处。重要的是必须认识到这是完全可行的,最后一点是:类中的类型参数与方法中的类型参数可以同时使用,但是必须保证它们不会发生冲突。
C#函数式程序设计之泛型(上),布布扣,bubuko.com
原文:http://www.cnblogs.com/Ribbon/p/3603197.html