为什么需要泛型

大家有没有觉得一个类的成员类型或者一个方法的返回值类型和参数类型一旦写好就不太方便修改,如果一个经常使用的类,它的某个成员类型有时候需要int,有时候需要string,我们需要不停的修改这个类,或者声明一个与其名字相似但成员类型不同的新的类,这样做非常麻烦。
另外还有一个函数,它的参数类型有时候需要int,有时候需要double,虽然我们可以进行函数重载,但代码挺多的。
为了解决上述问题,提供了新的解决方案——泛型。
我们可以使用“类型占位符”书写代码,然后在创建类的实例时知名真实的类型。
泛型类型不是类型,而是类型的模板。

泛型类

1
2
3
4
5
6
7
8
9
10
static void Main(string[] args) {
MyClass<int, double> myClass = new MyClass<int, double>() { Value1 = 12 ,Value2 = new double[] { 1,2,3} };
}
}
public class MyClass<T1 , T2> {
public T1 Value1 { get; set; }
public T2[] Value2;
public void Fun1(T1 x) { ... }
public T2 Fun2() { ...}
}

以上代码,如果我们想要修改MyClass类成员的类型,只需要在声明时修改<>内的内容,也就是类型参数就行。
泛型的声明有点长可以使用var进行简化。

类型参数的约束(where)

由于泛型栈不知道它们保存的项的类型是什么,所以也就不会知道这些类型实现的成员。
栈可以确认的是,这些保存的项都实现了object类的成员,包括ToString、Equals、以及GetType方法,除此之外,它不知道还有哪些成员可用。
为了让泛型变得更有用,我们需要提供额外的信息让编译器知道参数可以接受哪种类型,这样的信息称为约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal class Program {
static void Main(string[] args) {
MyClass<int, double[]> myClass = new MyClass<int, double[]>() { Value1 = 12 ,Value2 = new double[] { 1,2,3} };
}
}
public class MyClass<T1 , T2>where T1: struct
where T2 : class, IEnumerable<double> {
public T1 Value1 { get; set; }
public T2 Value2;
public void Fun1(T1 x) {
Console.WriteLine(x);
}
public T2 Fun2() {
return Value2 ;
}
}

约束类型:

约束类型 描述
类名 只有这个类型的类或从它派生的类才能用作类型实参
class 任何引用类型,包括类、数组、委托和接口都可以用作类型实参
struct 任何值类型都可以用作类型实参
接口名 只有这个接口或实现这个接口的类型才能用作类型实参
new() 任何具有无参公共构造函数的类型都可以用作类型实参
where子句可以以任何次序列出,但是where字句中的约束必须有特定的顺序:
最多只能有一个主约束,而且必须放在第一位。
可以有任意多的接口名称约束。
如果存在构造函数约束,则必须放在最后。
主约束 次约束
ClassName InterfaceName
class
struct

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
internal class Program {
static void Main(string[] args) {
var intArray = new int[] { 3, 5, 7, 9, 11 };
var stringArray = new string[] { "first", "second", "third" };
Simple.ReverseAndPrint<int>(intArray);
Simple.ReverseAndPrint(stringArray);//自动推断
}
}
class Simple {
static public void ReverseAndPrint<T>(T[] arr) {
Array.Reverse(arr);
foreach (var item in arr) {
Console.WriteLine(item);
}
Console.WriteLine("");
}
}

编译器可以帮我们从方法参数的类型中推断出应用作为泛型方法的类型参数的类型,简化调用时书写的代码。

泛型结构

1
2
3
4
struct MyStruct<T1,T2> {
public T1 Value1;
public T2 Value2;
}

泛型委托

1
2
3
4
5
6
7
8
9
10
internal class Program {
static void Main(string[] args) {
MyDel<int> del = func;
del.Invoke(0);
}
static void func(int i) {
Console.WriteLine(i);
}
}
delegate void MyDel<T>(T t);

泛型接口

如果一个类实现了泛型接口,那么这个类本身也是泛型的。
如果一个类实现了特化之后的泛型接口,那么这个类就不再是泛型类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal class Program {
static void Main(string[] args) {
Simple<string> simple = new Simple<string>();
Simple<int> int1 = new Simple<int>();
Console.WriteLine(simple.ReturnIt("hello world"));
Console.WriteLine(int1.ReturnIt(100));
}
}
interface IMyIf<T> {
T ReturnIt( T inValue);
}
class Simple<S> : IMyIf<S> {
public S ReturnIt(S inValue) {
return inValue;
}
}

用不同类型参数实例化的泛型接口的实例是不同的接口。
我们可以在非泛型类型中实现泛型接口。
必须实现每一个接口的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
internal class Program {
static void Main(string[] args) {
Simple simple = new Simple();
Console.WriteLine(simple.ReturnIt("hello world"));
Console.WriteLine(simple.ReturnIt(100));
}
}
interface IMyIf<T> {
T ReturnIt( T inValue);
}
class Simple : IMyIf<int>, IMyIf<string> {
public int ReturnIt(int inValue) {
return inValue;
}

public string ReturnIt(string inValue) {
return inValue;
}
}

实现泛型类型接口时,必须保证类型实参的组合不会在类型中产生两个重复的接口。
泛型接口的名字不会与非泛型冲突。