泛型的概念总结

###本章阅读列表###

泛型很难理解?不然
泛型集合和ArrayList的装箱拆箱
常见的泛型类型
泛型类和泛型方法
泛型约束
泛型委托
###泛型很难理解?不然 ###
在接触的一个新的概念的时候,总会感觉难以理解,当你掌握并能熟练地使用的时候,发现这个概念其实简单的,我相信大多数码农都会有这种似曾相识的感觉。可能大多数人刚学习泛型的时候觉得很难理解,当然我也是这样的,所以便写下这篇文章加深一下对泛型的印象。
第一次接触泛型那还是在大二上学期的时候,那会是学c#面向对象的时候接触过泛型集合,但尴尬的是那会还没有“泛型”这个概念,仅仅只停留在泛型集合的使用。关于泛型入门的文章csdn和博客园有很多,这里我也写一篇关于我对泛型学习的一个总结,如果出现错误表达不当的地方,还希望评论指出。
###泛型优点###
官方文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/introduction-to-generics
简介:
泛型是.NET Framework2.0新增的一个特性,在命名空间System.Collections.Generic,包含了几个新的基于泛型的集合类,官方建议.net 2.0 及更高版本的应用程序使用心得泛型集合类,而不使用非泛型集合类,例如ArrayList。
官方解释:
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。
泛型的定义主要有以下两种:
1.在程序编码中一些包含类型参数的类型,也就是说泛型的参数只可以代表类,不能代表个别对象。(这是当今较常见的定义)
2.在程序编码中一些包含参数的类。其参数可以代表类或对象等等。(人们大多把这称作模板)不论使用哪个定义,泛型的参数在真正使用泛型时都必须作出指明
官方的解释虽然很难理解,用我的话来解释那就是,声明类和方法时一般都需要定义是什么类,class Brid ,Class Food… 声明泛型类和方法时只需要传入类型的地方用 ,有点类似于占位符的作用,用的时候传入具体的类型。当针对不同类型具有相同行为的时候,也就是泛型发挥作用的时候。
优点:
1.使用泛型类、方法,我们可以极大提高代码的重用性,不需要对类型不同代码相同(仅类型参数不同)的代码写多次。
2.创建泛型类,可在编译时创建类型安全的集合
3.避免装箱和拆箱操作降低性能,在大型集合中装箱和拆箱的影响非常大.

泛型集合和ArrayList的装箱拆箱###
装箱:是指从值类型转换成引用类型
拆箱:是指从引用类型转换成值类型
下面的例子是借鉴官方的一段代码:

System.Collections.ArrayList list1 = new System.Collections.ArrayList();
list1.Add(3);
list1.Add(105);

System.Collections.ArrayList list2 = new System.Collections.ArrayList();
list2.Add("科比");
list2.Add("詹姆斯");
1
2
3
4
5
6
7
ArrayList是一个极为方便的集合类,可以用于存储任何引用或值类型。但是缺点也很明显,第一个缺点是编译的时候不会检查类型,例如

System.Collections.ArrayList list1 = new System.Collections.ArrayList();
list1.Add(3);
list1.Add(105);
list1.Add("sd");
foreach (int item in list1)
{
Console.WriteLine(item.ToString());
}
1
2
3
4
5
6
7
8
编译正常,运行的时候会出现转换类型错误。
至于ArrayList第二个缺点就是装箱拆箱的时候回造成性能的损失。我们看看ArrayList的Add方法的定义。

参数是一个object类型,也就是说ArrayList添加任何引用类型或值类型都会隐士转换成Object,这个时候便发生装箱操作,在遍历检索它们时必须从object 类型转换成指定的类型,这个时候便发生拆箱操作。这两种操作会大大降低性能。所以.net 2.0的程序时应该放弃使用ArrayList,推荐使用使用List《T》 泛型集合。这也是我们为什么要使用泛型的原因之一。

###常见的泛型类型###
在泛型类型的定义中,出现的每个T(一个展位变量而已叫别的名字也行)在运行时都会被替换成实际的类型参数。下面是一些基础的泛型类型
1.泛型类

class MyGenericClass<T>
{
//......
}
1
2
3
4
2.泛型接口

interface GenericInterface<T>
{
void GenericMethod(T t);
}
1
2
3
4
3.泛型方法

public void MyGenericMethod<T>()
{
//......
}
1
2
3
4
4.泛型数组

public T[] GenericArray;
1
5.泛型委托

public delegate TOutput GenericDelagete<TInput, TOutput>(TInput input);
1
6.泛型结构

struct MyGenericStruct<T>
{

}
1
2
3
4
在使用时所有的T的都要替换成具体的类型。
类型参数命名指南,参见官方文档

###泛型类和泛型方法###
我们先来看看泛型方法,这个方法的用途是来交换两个变量的

static void Main(string[] args)
{
int a = 1;
int b = 2;
SwapInt(ref a,ref b);
Console.WriteLine($"a={a}b={b}");
}
public static void SwapInt(ref int a, ref int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
结果是a=2,b=1,但是我们现在要换成string类型呢,是不是得再写一个string参数的方法呢,如果是char、double…,这每个不同类型的参数都要写一个参数,的确太麻烦并且没有这个必要,Object ?当然可以

static void Main(string[] args)
{
object a = 1;
object b = 2;
SwapObject(ref a,ref b);
Console.WriteLine($"a={a}b={b}");
}
public static void SwapObject(ref object a, ref object b)
{
object temp;
temp = a;
a = b;
b = temp;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
这确实能解决代码复用的需求,但是上面我们已经知道使用Object类型会发生装箱拆箱的操作,会降低性能。所以我们可以使用泛型方法解决这个缺点。

static void Main(string[] args)
{
int a = 1;
int b = 2;
SwapGeneric(ref a,ref b);
Console.WriteLine($"a={a}b={b}");
}
//交换两个变量的方法
public static void SwapGeneric<T>(ref T a, ref T b)
{
T temp;
temp = a;
a = b;
b = temp;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
泛型类:这个泛型类常用api通用接口的泛型类。

class Program
{
static void Main(string[] args)
{
List<Product> data = new List<Client.Product>() {
new Client.Product() { Id=12,Name="土豆"},
new Client.Product() { Id=12,Name="茄子"},
new Client.Product() { Id=12,Name="黄瓜"}
};
var resultProduct = Result<Product>.Success(data);
var resultUser = Result<User>.Error("没有数据");
foreach (var item in resultProduct.Data)
{
Console.WriteLine(item.Id+","+item.Name);
}
Console.WriteLine(resultUser.IsSuccess+resultUser.Message);
}

}
public class Result<T> { //泛型类,声明T变量类型
public bool IsSuccess { get; set; }
public List<T> Data { get; set;}//未定义具体类型的泛型集合
public string Message { get; set; }
public static Result<T> Error(string message)
{
return new Client.Result<T> { IsSuccess = false, Message = message };
}
//泛型方法,初始化数据
public static Result<T> Success(List<T> data)
{
return new Client.Result<T> { IsSuccess =true,Data =data}; //成功就没有提示消息的原则
}
}
public class Product {
public int Id { get; set; }
public string Name { get; set; }
}
public class User {
public int Age { get; set; }
public string Name { get; set; }
}
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
使用该通用的泛型类的好处在于,获取不同的对象集合不需要写多个方法,获取Product数据集合、获取User数据集…。只需要调用Success方法既可,使代码变得可复用。
###泛型类型参数约束###
为什么要使用类型参数的约束呢,简单点说就是筛选类型参数,在使用泛型的代码中如果违反了某个约束不允许的类型来实例化则会产生编译错误,类型参数的约束是使用关键字where。 下面列出了6中类型的约束

where T: struct
类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可以为 null 的类型(C# 编程指南)。
where T : class
类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。
where T:new()
类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。
where T:<基类名>
类型参数必须是指定的基类或派生自指定的基类。
where T:<接口名称>
类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
where T:U
为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。
我们在看看上面那个交换两个变量的方法SwapGeneric,加上必须是值类型的约束
public static void SwapGeneric<T>(ref T a,ref T b) where T :struct
{
T temp;
temp = a;
a = b;
b = a;
}
//实例化
Product p = new Product() { Id=1,Name="土豆"};
Product p1 = new Product() { Id=2,Name ="茄子"};
SwapGeneric<Product>(ref p,ref p1);
1
2
3
4
5
6
7
8
9
10
11
我们在使用的时候编译给我们提示了以下的错误:

“类型Product必须是不可以为NUll值得类型”,引用类型的默认值就是NULL,所以该房型方法的类型参数不能是引用类型,这就是使用类型参数约束的好处。
约束多个参数

class List<TLive,U> where TLive:User where U:struct
{

}
1
2
3
4
###泛型委托###
泛型委托可以自己定义自己的类型参数,声明的时候还是和泛型类、泛型方法一样加个<坑> 站个坑,其实泛型委托使用的时候不是很多,要慎用。,如以下事例

public delegate T DelegateGeneric<T>(T item);
DelegateGeneric<string> test = StringMethod;
public static string StringMethod(string name)
{
return "你好" + name;
}
1
2
3
4
5
6
将上面的交换两个变量的方法改成委托是这样的

public delegate void DelegateGenericSwap<T>(ref T a,ref T b);
string a = "张林";
string b = "科比";
DelegateGenericSwap<string> swap = GenericSwap;
Console.WriteLine($"a:{a}b:{b}");
public static void GenericSwap<T>(ref T a,ref T b)
{
T temp;
temp = a;
a = b;
b = a;
}
————————————————
版权声明:本文为CSDN博主「dotnet全栈开发」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/kebi007/article/details/77800954

posted @ 2020-06-18 08:49  厦门哈韩  阅读(186)  评论(0编辑  收藏  举报