一、泛型篇
1、泛型概念
1.1泛型的含义
1.1泛型能解决什么问题?
传统解决办法1:传统的解决方案,声明三个不同的方法,分别输出相同的属性。
传统解决办法2:使用Object类型装箱和拆箱来解决该问题
解决方案3:泛型解决方案
2. 泛型的原理和性能测试
2.1泛型的原理
2.2 泛型性能测试
3. 泛型的种类
3.1泛型方法
3.2泛型类
3.3泛型接口
3.4泛型委托
4.泛型约束
4.1五种基础泛型约束:
4.2多参数约束:
4.3多条件约束:
4.4分别测试各种约束
A:各种约束的代码
B:各种实体代码
D:代码调用
D:测试结果
6. 补充default(T)
7. 泛型缓存
八. 逆变和协变
1、泛型概念
1.1泛型的含义
泛型的就是“通用类型”,它可以代替任何的数据类型,使类型参数化,从而达到只实现一个方法就可以操作多种数据类型的目的。
泛型为.NET框架引入了类型参数(type parameters)的概念。
类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。
这意味着使用泛型的类型参数T,可以多种形式调用,运行时类型转换避免了装箱操作的代价和风险。
1.1泛型能解决什么问题?
开发案例:分别输出实体model1、model2、model3的id和name值,这三个实体有相同的属性名字id和name。
传统解决办法1:传统的解决方案,声明三个不同的方法,分别输出相同的属性。
缺点:该解决方案十分繁琐,明明相同的属性,不得不声明三个不同的方法,造成代码的大量冗余。
public static void showM1(model1 m1)
{ Console.WriteLine("id值为:" + m1.id + " name值为:" + m1.name);3 }
public static void showM2(model2 m2)
{
Console.WriteLine("id值为:" + m2.id + " name值为:" + m2.name);
}
public static void showM3(model3 m3)
{
Console.WriteLine("id值为:" + m3.id + " name值为:" + m3.name);
}
传统解决办法2:使用Object类型装箱和拆箱来解决该问题
原理:object是所有类型的父类,所有用到父类的地方都可以用子类去代替,即:里氏替换原则
缺点:需要记住可能会传进来哪几种类型;最大的问题是值类型和引用类型之间的互换(即拆箱和装箱)严重影响性能
public static void showObj(object obj)
{
if (obj.GetType() == typeof(model1))
{
model1 m = (model1)obj;
Console.WriteLine("id值为:" + m.id + " name值为:" + m.name);
}
else if (obj.GetType() == typeof(model2))
{
model2 m = (model2)obj;
Console.WriteLine("id值为:" + m.id + " name值为:" + m.name);
}
else if (obj.GetType() == typeof(model3))
{
model3 m = (model3)obj;
Console.WriteLine("id值为:" + m.id + " name值为:" + m.name);
}
}
解决方案3:泛型解决方案
为了解决上述问题,我们这里引入泛型的概念来解决:方案一代码冗余的问题、方案二object装箱和拆箱性能低下的问题。
基于以上两种传统的解决方案都缺点明显,所以在 .Net 2.0的时候,推出了一个通用语言运行时(CRL)的新特性即:泛型。
泛型为.NET框架引入了类型参数(type parameters)的概念,它属于System.Collections.Generic命名空间下。
类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。
这意味着使用泛型的类型参数T,可以多种形式调用,运行时类型转换避免了装箱操作的代价和风险。
优点:
1.减少代码冗余量,精简代码
2.避免了拆箱和装箱过程过程中代理的性能损失
3.结合IDE的只能提示,提高了开发效率
///
/// 泛型方法的引入
///
///
///
public static void showModel
{
Console.WriteLine(t);
}
///
/// 泛型约束的引入,如果要输出实体中的值,需要配合“基类约束”方可以实现
///
///
///
public static void showModelDetils
{
Console.WriteLine("id值为:" + t.id + " name值为:" + t.name);
}
//总结:
/*
1. 以上两个方法均为泛型方法,T代表的类型在使用时才声明,俗称“延迟声明”。
2. 如果要使用类中的属性时,需要配合泛型的“基类约束”来实现
*/
总结:
1. 以上两个方法均为泛型方法,T代表的类型在使用时才声明,俗称“延迟声明”。
2. 如果要使用类中的属性时,需要配合泛型的“基类约束”来实现。
说明:除了泛型方法外,还有泛型接口、泛型委托等,同样泛型约束除了“基类约束”外,还有其他约束。
全代码附录:
public class myUtils
{
//要求:分别输出实体model1、model2、model3的id和name值,这三个实体有相同的属性名字id和name
//传统的解决方案(一):由于三个不同的实体,所以要声明三个不同的方法来输出
/*
缺点:该解决方案十分繁琐,明明相同的属性,不得不声明三个不同的方法,造成代码的大量冗余
*/
public static void showM1(model1 m1)
{
Console.WriteLine("id值为:" + m1.id + " name值为:" + m1.name);
}
public static void showM2(model2 m2)
{
Console.WriteLine("id值为:" + m2.id + " name值为:" + m2.name);
}
public static void showM3(model3 m3)
{
Console.WriteLine("id值为:" + m3.id + " name值为:" + m3.name);
}
//传统的解决方案(二):使用Object类型来解决该问题
//原理:object是所有类型的父类,所有用到父类的地方都可以用子类去代替,即:里氏替换原则
/*
缺点:需要记住可能会传进来哪几种类型;最大的问题是值类型和引用类型之间的互换(即拆箱和装箱)严重影响性能
*/
public static void showObj(object obj)
{
if (obj.GetType() == typeof(model1))
{
model1 m = (model1)obj;
Console.WriteLine("id值为:" + m.id + " name值为:" + m.name);
}
else if (obj.GetType() == typeof(model2))
{
model2 m = (model2)obj;
Console.WriteLine("id值为:" + m.id + " name值为:" + m.name);
}
else if (obj.GetType() == typeof(model3))
{
model3 m = (model3)obj;
Console.WriteLine("id值为:" + m.id + " name值为:" + m.name);
}
}
//基于以上两种传统的解决方案都缺点明显,所以在 .Net 2.0的时候,推出了一个通用语言运行时(CRL)的新特性即:泛型。
/*
泛型为.NET框架引入了类型参数(type parameters)的概念。类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。
这意味着使用泛型的类型参数T,可以多种形式调用,运行时类型转换避免了装箱操作的代价和风险。
*/
}
2. 泛型的原理和性能测试
2.1泛型的原理
延时声明,在运行时进行编译
2.2 泛型性能测试
下面测试一下:普通方法、使用Object类型、使用泛型 三种情况对同一个数据进行处理,耗时情况
测试代码:
public class utils
{
///
/// 正常方法
///
///
public static void ShowCommon(int iParameter)
{ }
///
/// object方法
///
///
public static void ShowObject(object oParameter)
{ }
///
/// 泛型方法
///
///
///
public static void ShowGeneric
{ }
}
Console.WriteLine("----------测试正常方法、Object方法、泛型方法各自耗时情况------------");
long commonTime = 0;
long objectTime = 0;
long genericTime = 0;
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
//循环一亿次
for (int i = 0; i < 1000000000; i++)
{
utils.ShowCommon(ivalue);
}
stopwatch.Stop();
commonTime = stopwatch.ElapsedMilliseconds;
}
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
//循环一亿次
for (int i = 0; i < 1000000000; i++)
{
utils.ShowObject(ivalue);
}
stopwatch.Stop();
objectTime = stopwatch.ElapsedMilliseconds;
}
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
//循环一亿次
for (int i = 0; i < 1000000000; i++)
{
utils.ShowGeneric
}
stopwatch.Stop();
genericTime = stopwatch.ElapsedMilliseconds;
}
Console.WriteLine("commonTime = {0} objectTime = {1} genericTime = {2}", commonTime, objectTime, genericTime);
测试结果:
结论:正常的方法和泛型方法耗时基本一致,泛型方法甚至用的时间更短,但是采用object类型转换的方法,耗时明显长于其他两种
3. 泛型的种类
泛型包括:泛型方法、泛型类、泛型接口、泛型委托四类。
这里主要介绍泛型方法,在后续会配合泛型的五种约束继续深入介绍。
泛型方法:一个方法满足多个类型的需求
泛型类:一个类满足多个类型的需求
泛型接口:一个接口满足多个类型的需求
泛型委托:一个委托满足多个类型的需求
3.1泛型方法
///
/// 泛型方法
///
public class GenericsMethord
{
//这里介绍泛型方法,在之前02-泛型的引入中,使用的就是泛型方法,这里再重复一次
/*
详解:T为泛型的一个代表,换成别的字母同样可以
T和A代表的类型在使用时才声明,俗称“延迟声明”
*/
public static void ShowModel
where T : ModelFather
where A : model3
{
Console.WriteLine("id值为:" + model1.id + " name值为:" + model1.name);
Console.WriteLine("id值为:" + model2.id + " name值为:" + model2.name);
}
}
3.2泛型类
///
/// 泛型类
///
public class GenericsClass
{
public string id { get; set; }
public string name { get; set; }
public void Test(T t)
{
Console.WriteLine(t.GetType());
}
}
3.3泛型接口
///
/// 泛型接口
///
public class GenericsInterface
{
public interface IGet
{ }
}
3.4泛型委托
///
/// 泛型委托
///
public class GenericsDelegate
{
public delegate void GetHandler
}
4.泛型约束
4.1五种基础泛型约束:
1. 基类约束:where T:<基类名>
2. 接口约束:where T:<接口名称> 约束的接口也可以是泛型的
3. 无参构造函数约束:where T: new() 提供的任何类型参数都必须具有可访问的无参数(或默认)构造函数。当与其他约束一起使用时,new() 约束必须最后指定。
4. 值类型约束:where T:struct 类型参数必须是值类型,可以指定除 Nullable 以外的任何值类型。
5. 引用类型约束: where T:class 类型参数必须是引用类型,也适用于任何类、接口、委托或数组类型。当与其他约束一起使用时,class 约束必须最先指定。
4.2多参数约束:
可以写多个where条件
public static void ShowModel
where T : ModelFather
where A : model3
4.3多条件约束:
当有多个条件约束时,无参构造函数约束要写到最后
public void ShowManyCon
· where T : class, IWork, ISport, new()
4.4分别测试各种约束
A:各种约束的代码
1 public class genericsConstraint
2 {
3
4 ///
5 /// 1. 基类约束和接口约束
6 ///
7 ///
8 ///
9 public void Show
10 {
11 Console.WriteLine("id值为:" + t.id + " name值为:" + t.name);
12 t.Say();
13 t.Work();
14 }
15 ///
16 /// 2. 无参构造函数约束
17 ///
18 ///
19 ///
20 public void ShowNo
21 {
22 T t1 = new T();
23 Console.WriteLine(t1);
25 }
26 ///
27 /// 3. 值类型约束
28 /// C#值类型包括:结构体、数据类型(整型、字符型、浮点型、decimal型)、bool型、枚举、可空类型
29 ///
30 ///
31 ///
32 public void ShowZhi
33 {
34 Console.WriteLine(t);
35 }
36 ///
37 /// 4. 引用类型约束
38 /// C#引用类型包括:数组、类、接口、委托、object、字符串
39 ///
40 ///
41 ///
42 public void ShowYin
43 {
44 Console.WriteLine(t);
45 }
46 ///
47 /// 5. 多参数约束
48 ///
49 ///
50 ///
51 ///
52 ///
53 public void ShowMany
54 where T : People
55 where G : IWork
56 {
57 Console.WriteLine(t);
58 Console.WriteLine(g);
59 }
60 ///
61 /// 6. 多条件约束
62 ///
63 ///
64 ///
65 public void ShowManyCon
66 {
67
68 }
B:各种实体代码
1 namespace Generics._04_泛型约束
2 {
3 ///
4 /// People类
5 ///
6 public class People
7 {
8 public string id { get; set; }
9
10 public string name { get; set; }
11
12 public void Say()
13 {
14 Console.WriteLine("我会说话");
15 }
16
17 }
18 ///
19 /// Chinese类
20 ///
21 public class Chinese : People, ISport, IWork
22 {
23 public void Sport()
24 {
25 Console.WriteLine("我在运动");
26 }
27
28 public void Work()
29 {
30 Console.WriteLine("我在工作");
31 }
32 }
33 ///
34 /// Janpanese类
35 ///
36 public class Janpanese : IWork
37 {
38 public string id { get; set; }
39
40 public string name { get; set; }
41 public void Work()
42 {
43 Console.WriteLine("我在工作");
44 }
45 }
46 ///
47 /// YanTai
48 ///
49 public class YanTai : Chinese
50 {
51
52 }
53 ///
54 /// IWork接口
55 ///
56 public interface IWork
57 {
58 void Work();
59 }
60 ///
61 /// ISport接口
62 ///
63 public interface ISport
64 {
65 void Sport();
66 }
67 ///
68 /// 构造函数无参的 类
69 ///
70 public class Test1
71 {
72 public Test1()
73 {
74
75 }
76 }
77 ///
78 /// 构造函数有参的类
79 ///
80 public class Test2
81 {
82 public Test2(string id,string name)
83 {
84
85 }
86 }
87
88
89 }
C:初始化代码
1 //1.四个实体类
2 People people = new People()
3 {
4 id = "p1",
5 name = "mr1"
6 };
7 Chinese chinese = new Chinese()
8 {
9 id = "p2",
10 name = "mr2"
11 };
12 Janpanese janpanese = new Janpanese()
13 {
14 };
15 YanTai yanTai = new YanTai()
16 {
17 id = "p3",
18 name = "mr3"
19 };
20 //2. 构造函数有参数和无参数的两个类
21 Test1 test1 = new Test1();
22 Test2 test2 = new Test2("pp","maru");
23 //3. 声明一个值类型和一个引用类型
24 int num = 12; //值类型
25 YanTai yt2 = new YanTai(); //引用类型
D:代码调用
1 Console.WriteLine("------------------------------------四 泛型约束------------------------------------");
2 //4.1 基类约束和接口约束
3 Console.WriteLine("------------------------------------4.1 基类约束和接口约束------------------------------------");
4 genericsConstraint gc = new genericsConstraint();
5 //gc.Show
6 gc.Show
7 //gc.Show
8 gc.Show
9 //4.2 无参构造函数约束
10 Console.WriteLine("------------------------------------4.2 无参构造函数约束------------------------------------");
11 gc.ShowNo
12 // gc.ShowNo
13 //4.3 值类型约束
14 Console.WriteLine("------------------------------------4.3 值类型约束------------------------------------");
15 gc.ShowZhi
16 //gc.ShowZhi
17 //4.4 引用类型约束
18 Console.WriteLine("------------------------------------4.4 引用类型约束------------------------------------");
19 //gc.ShowYin
20 gc.ShowYin
21 //4.5 多参数约束
22 Console.WriteLine("------------------------------------4.5 多参数约束------------------------------------");
23 gc.ShowMany
24 //4.6 多条件约束
25 Console.WriteLine("------------------------------------4.6 多条件约束------------------------------------");
26 Console.WriteLine("针对多条件约束,这里不做测试");
D:测试结果
6. 补充default(T)
了解:
1. 在泛型中如果需要返回泛型类型的默认值则会用到这个关键字。
2. T是值类型而非结构的则default(T) 数值类型返回0,字符串返回空。
3. 是非引用类型是结构时候返回初始化为零或空的每个结构成员。
4. 引用类型返回NULL
5. 其实就是为了返回默认值,比如int i =0;这样是可以的,但是int i=null是不可以的,但是泛型的时候不知道是值类型还是引用类型所以不知道如何赋默认值。
用这个关键字就解决了这个问题
7. 泛型缓存
1. 知识普及:如果一个(普通)类中有静态变量或者静态构造函数,当实例化的时候,CRL会优先初始化静态变量和静态构造函数,且只有在第一次实例化的时候进行初始化,后续都不在进行初始化。(PS:利用这两个特性可以创建单例模式)
特别注意:如果是泛型类的话,不同类型的第一次实例化的时候都要初始化静态变量和静态构造函数(每个不同的T,都会生成一份不同的副本)。
2. 先用一普通例子来抛砖引玉:
新建一个CommonClass类,里面有静态字段和静态方法,然后调用4次,发现每次输出的时间都是相同,证明静态字段和静态构造函数只有第一次实例化的时候才调用。
或者通过加断点的形式,也可以发现,只有在第一次实例化的时候才能进入静态字段和静态构造函数,同样证明了上述结论。
3. 下面建一个泛型类GenericsClass
4. 最后自己总结一下泛型缓存的定义:
对于泛型类而言,不同类型都会生成不同的副本(即都要调用静态的构造函数和静态字段),但相同的类型,实例化一次后,再次实例化,将不会在生成副本(即不再调用静态类和静态构造函数),这就是泛型缓存。
代码分享:
1 ///
2 /// 一个普通类
3 ///
4 public class CommonClass
5 {
6 static CommonClass()
7 {
8 _InitTime = string.Format("调用构造函数的时间:{0}", DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
9 }
10 private static string _InitTime = "";
11 public void show()
12 {
13 Console.WriteLine(_InitTime);
14 }
15 }
16 ///
17 /// 泛型类
18 ///
19 ///
20 public class GenericsClass
21 {
22 static GenericsClass()
23 {
24 _InitTime = string.Format("调用构造函数的时间:{0}", DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
25 }
26 private static string _InitTime = "";
27 public void show()
28 {
29 Console.WriteLine(_InitTime);
30 }
31 }
1 public static void ShowGenericsCacheDemo()
2 {
3
4 {
5 Console.WriteLine("1. 测试普通类");
6 CommonClass c1 = new CommonClass();
7 c1.show();
8 Thread.Sleep(2000);
9 CommonClass c2 = new CommonClass();
10 c2.show();
11 Thread.Sleep(2000);
12 CommonClass c3 = new CommonClass();
13 c3.show();
14 }
15 {
16 Console.WriteLine("2. 测试泛型类");
17 GenericsClass
18 c1.show();
19 Thread.Sleep(2000);
20 GenericsClass
21 c2.show();
22 Thread.Sleep(2000);
23 GenericsClass
24 c3.show();
25 Thread.Sleep(2000);
26 GenericsClass
27 c4.show();
28 }
结果:
八. 逆变和协变
1. 目标:了解是什么即可,实际中用的很少。
2. 事前准备:BaseClass和DerivedClass两个类,DerivedClass类继承BaseClass类。
1 public class BaseClass
2 {
3 public string id { get; set; }
4
5 public int age { get; set; }
6
7 public string name { get; set; }
8 }
9 public class DerivedClass : BaseClass
10 {
11
12 }
3. 逆变:借助Action
4. 协变:借助IEnumerable
分享相关代码:
1 {
2 //里氏替换原则(也是多态的一种)
3 BaseClass bClass = new DerivedClass();
4 }
5 {
6 //推断:List集合进行继承(编译不过,报错)
7 //List
8 }
9 {
10 //借助Action
11 //这就是 → “逆变”
12 Action
13 Action
14 dAction(new DerivedClass());
15 }
16 {
17 //借助IEnumerable
18 //这就是 → “协变”
19 IEnumerable
20 }