温故而知新-C#泛型

前言

在开发中,经常会遇到功能相同的模块,但是参数类型不同,这时候只能分别写不同的方法处理不同的数据类型。那么有没有一种办法,只用同一个方法来处理不同类型参数的方法

示例

1
2
3
4
5
6
7
8
9
10
11
12
public static void ShowTypeName(int i)
{
Console.WriteLine(i.GetType().Name);
}
public static void ShowTypeName(string i)
{
Console.WriteLine(i.GetType().Name);
}
public static void ShowTypeName(DateTime i)
{
Console.WriteLine(i.GetType().Name);
}

上面几个方法分别获取int、string、DateTime类型的名称,可以看到对应写了三种类型的重载方法。

1
2
3
4
5
6
// int
CommonMethod.ShowTypeName(1);
// string
CommonMethod.ShowTypeName("1");
// datetime
CommonMethod.ShowTypeName(DateTime.Now);

结果:

这个时候我们就会想面向对象的继承,我们知道在C#语言中,所有的类型都继承自基类object

优化:

1
2
3
4
public static void ShowObjectName(object i)
{
Console.WriteLine(i.GetType().Name);
}

结果:

image-20211004163223821

可以看到,使用object类型作为参数传入达到了我们预期的效果,解决了代码的复用性

但是这里又会引发另一个问题:装箱和拆箱带来的性能损耗问题。

装箱和拆箱:在把值类型转换为引用类型或把引用类型转换成值类型时,需要进行装箱和拆箱的操作。

值类型存储在栈上,引用类型存储在堆上。

值类型 ==> 引用类型 装箱 隐式转换(自动转换)

引用类型 ==> 值类型 拆箱 显示转换(强制转换)需要类型转换运算符

性能

泛型就是为了解决这个问题而产生的,在.NET2.0推出泛型

泛型是C#.NET的一个重要概念,不仅是C#语言的一部分而且和IL中间语言紧密结合。

在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时,客户端指定的特定类型的占位符。

泛型类( GenericList<T>)无法按原样使用,因为它不是真正的类型;它更像是类型的蓝图。 若要使用 GenericList<T>,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。 此特定类的类型参数可以是编译器可识别的任何类型。 可创建任意数量的构造类型实例,其中每个使用不同的类型参数。

定义泛型参数

优化:

1
2
3
4
public static void ShowName<T>(T i)
{
Console.WriteLine(i?.GetType().Name);
}
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
26
27
28
29
30
var i = 123;
var s = "Jonty";
var d = DateTime.Now;

Console.WriteLine("***************CommonMethod*************");
// int
CommonMethod.ShowTypeName(i);
// string
CommonMethod.ShowTypeName(s);
// datetime
CommonMethod.ShowTypeName(d);

Console.WriteLine("***************ObjectMethod*************");
// int
CommonMethod.ShowObjectName(i);
// string
CommonMethod.ShowObjectName(s);
// datetime
CommonMethod.ShowObjectName(d);


Console.WriteLine("***************GenericMethod*************");
// int
CommonMethod.ShowName(i);
// string
CommonMethod.ShowName(s);
// datetime
CommonMethod.ShowName(d);

Console.ReadKey();

可以看到,效果是一摸一样的:

image-20211004170341353

为什么泛型可以解决这个问题呢?

泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型

泛型是如何工作的呢?

控制台程序最终会编译成一个exe程序,exe被点击的时候,会经过JIT(即时编译器)的编译,最终生成二进制代码,才能被计算机执行。泛型加入到语法以后,visual studio自带的编译器又做了升级,升级之后编译时遇到泛型,会做特殊的处理:生成占位符。再次经过JIT编译的时候,会把上面编译生成的占位符替换成具体的数据类型。

示例:

1
2
Console.WriteLine(typeof(List<>));
Console.WriteLine(typeof(Dictionary<,>));

结果:

可以看到,泛型在编译后会生成占位符

image-20211004171349172

性能对比:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public static void Show()
{
var i = 11932
long commonSecond = 0;
long objectSecond = 0;
long genericSecond = 0
{
Stopwatch stopwatch = new();
stopwatch.Start();
for (var j = 0; j < 1000000000; j++)
{
ShowInt(i);
}
stopwatch.Stop();
commonSecond = stopwatch.ElapsedMilliseconds;
}
{
Stopwatch stopwatch = new();
stopwatch.Start();
for (var j = 0; j < 1000000000; j++)
{
ShowObject(i);
}
stopwatch.Stop();
objectSecond = stopwatch.ElapsedMilliseconds;
}
{
Stopwatch stopwatch = new();
stopwatch.Start();
for (var j = 0; j < 1000000000; j++)
{
Show(i);
}
stopwatch.Stop();
genericSecond = stopwatch.ElapsedMilliseconds;
}
Console.WriteLine(
$"commonMethod:{commonSecond}\nobjectMethod:{objectSecond}\ngenericMethod:{genericSecond}");
}
#region Private Metho
private static void ShowInt(int i)
{
}
private static void ShowObject(object i)
{
}
private static void Show<T>(T i)
{
}
#endregion

结果:

泛型方法<普通方法<Object方法

image-20211004172530409

类型安全

泛型的另一个特性就是类型安全

示例:

ArrayList可以添加任意类型的参数

1
2
3
4
5
6
7
8
9
ArrayList arrayList = new ArrayList();
arrayList.Add(123);
arrayList.Add("jonty");
arrayList.Add(DateTime.Now);

foreach (var item in arrayList)
{
Console.WriteLine(item);
}

使用泛型可以进行类型约束:

如果我们添加其他类型的参数则会编译报错,参数无效

1
2
3
4
List<int> arrInts = new List<int>();
arrInts.Add(1);
arrInts.Add(2);
arrInts.Add(3);

命名约定

  • 泛型类型的名称首字母用T

  • 如果没有特殊要求,泛型类型允许用任何类代替;且只使用了一个泛型类型时,可以直接使用T作为泛型类型的名称

    1
    public class List<T>{}
  • 如果泛型的类型有特殊的要求,或者使用了两个或多个泛型类型,那么泛型类应该使用描述性名称

    1
    2
    public delegate void Eventhandler<TEventArgs>(Object sender,TEventargs e);
    public class SortedList<Tkey,TValue>{};