泛型
现在给出一个题目:要求写出一个函数,
他能对一个int数组进行排序,并且返回这个数组的最小值和最大值。
这很容易
(int, int) GetMaximum(int[] arr)
{
Array.Sort(arr);
return (arr[0], arr[^1]);
}
好,但这不是重点。接下来要求你再写一个函数。
作用一样,但是针对的是double类型。
(double, double) GetMaximum(double[] arr)
{
Array.Sort(arr);
return (arr[0], arr[^1]);
}
好,现在要求再写一个针对string类型的。
同样的要求重复了三次,却只改动其中的一小部分。
现在你应该意识到问题了。
如果int,double,string类型都要求有效,
那之后会不会要求float,byte,甚至自定义类型也有效。
那针对每一种类型都需要写一种重载吗?
不需要,我们只需要用到泛型。
泛型函数
声明
就像我们把变量作为参数一样,泛型是类型版本的参数。
泛型的“形参”称为类型占位符,泛型的“实参”称为类型参数。
声明一个泛型的形参声明方法是在函数名和函数参数之间写一对尖括号。
尖括号中你可以任意起名字,多个标识符使用逗号隔开。
然后在这个函数中,以及参数和返回值中,这个类型会被认为是有效的类型。
(T, T) GetMaximum<T>(T[] arr)
{
Array.Sort(arr);
return (arr[0], arr[^1]);
}
使用
在使用泛型函数时需要带上尖括号,填入类型参数。
string[] s = { "123","456","#$%","hello"};
var t = GetMaximum<string>(s);
但如果仅凭参数类型就能推断出全部的类型占位符(没有var或null等无法判断类型的参数),
那么可以省略此步骤。一个常见的例子是当函数仅有out参数的时候。
bool GetArr<T>(out T[] arr) {
arr = new T[10];
return default(T) == null;
}
GetArr<int>(out var arr1);
GetArr(out int[] arr2);
此时参数还不如直接写完整类型。
泛型类
可以在类型上声明泛型,这样可以使用泛型字段和泛型属性。
方法也可以使用由类声明的泛型占位符。并且不需要额外声明占位符。
泛型类可以和同名但不同泛型占位符数量的类共存
class Tesk<T>
{
T[] arr;
public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
public T SetArr(params T[] arr)
{
this.arr = arr;
return arr[0];
}
}
class Tesk
{
}
需要注意的是:泛型类的构造器声明时不需要加泛型,调用时必须要加泛型,哪怕能从参数中识别出。
泛型静态字段
在静态一章说,静态字段是和类绑定的,而类是唯一的,所以静态字段是唯一的。
泛型类不是。每一种不同的类型,都是由运行时现场合成的。
不同的泛型参数间,泛型类的静态字段是不一样的。
Tesk<int>.a = 10;
Tesk<double>.a = 20;
Console.WriteLine(Tesk<int>.a + Tesk<double>.a + Tesk<string>.a);//35
class Tesk<T>
{
public static int a = 5;
}
泛型类的继承
泛型类可以继承其他类,也可以被继承。
在被继承时,需要有效的类型参数,可以是实际的类型,也可以是另一个泛型类的占位符。
class Derive : Tesk<int> { }
class Derive<T> : Tesk<T> { }