C#高级静态语言效率利器之泛型详解
作者:微小冷 发布时间:2023-01-24 09:18:02
引入
所谓泛型,就是创建一个函数,对所有数据类型都生效。最常见的例子就是运算符,毕竟1+1=2,1.0+1.0=2.0,足以看出+是对多种数据类型起作用的。
但是,如想创建一个函数add(int a, int b),那么输入add(1.0, 1.0)是肯定要报错的,VS直接就给标红了。
泛型的出现,就很好地解决了这个尴尬的问题
T add<T>(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
return (T)(d1 + d2);
}
Console.WriteLine(add<int>(1, 1));
Console.WriteLine(add<double>(1.0, 1.0));
上面代码中,T表示某种数据类型,在调用函数add时,根据add后面的<>加以声明。
但如果就此就写return a+b显然也是不行的,因为+这种运算符并没有对T进行重载,编辑器并不会允许两种未知的类型相加。
这个时候就需要用到dynamic,用来让编辑器放弃类型检查,将任何可能发生的错误都留给运行阶段。
最后,运行结果为
2
2
类型约束
dynamic用着确实爽,但后果就是责任自负,这玩意要是用在团队协作的场合,简直就是灾难,毕竟并非所有对象都可以驾驭加法。
所以,C#的泛型,是可以被约束的泛型,关键就是where,将上述代码写为
T add<T>(T a, T b) where T : struct{
dynamic d1 = a;
dynamic d2 = b;
return (T)(d1 + d2);
}
where T : struct表示T必须是数值类型的一种,所以编译器的类型检查仍会发挥作用,在调用add时,如果T不是数值类型,就会报错。
C#一共有5种约束方案,列表如下
类别 | 条件 |
---|---|
struct | T必须是值类型 |
class | T必须是引用类型 |
new() | T必须有无参数的构造函数 |
基类名 | T必须是基类或派生自基类 |
接口名 | T必须是指定接口 |
裸类型 |
不同类型的约束,或相同类型不同种类的约束,一般是可以混用的,如果不能混用,编译器会提醒。比如struct几乎不能和其他类型混用。如果new()参与了约束,则放在最后。
子类泛型
除了函数可以采用泛型,类当然也可以,不仅可以,而且还能继承。
class MyList<T>
{
public T[] a;
public MyList(){} //无参数的构造函数,用于继承
public MyList(int n){
a = new T[n];
}
public T this[int index]{
get => a[index];
set => a[index] = value;
}
}
MyList相当于是给数组套了一层壳,其构造函数并不存在什么难以理解的地方,唯一有些问题的可能是下面的索引器public T this[int index],这种写法可以实现方括号形式的索引。
可以测试一下
var a = new MyList<int>(5);
for (int i = 0; i < 5; i++)
{
a[i] = i;
Console.WriteLine(a[i]);
}
结果就不粘贴了,接下来新建一个子类
class MyStack<T> : MyList<T>
{
public MyStack(int n)
{
a = new T[n];
}
public T Pop()
{
T p = a[a.Length- 1];
a = a[0..(a.Length-1)];
return p;
}
}
然后测试一下
var a = new MyStack<int>(3);
for (int i = 0; i < 3; i++)
{
a[i] = i;
}
for (int i = 0; i < 3; i++)
{
Console.WriteLine(a.Pop());
}
结果为
2
1
0
常用的泛型数据结构
C#通过泛型定义了很多数据结构,例如在讲解switch...case时提到的字典
Dictionary<int, string> card = new Dictionary<int, string>
{
{1,"A" },
{11, "J" },
{12, "Q" },
{13, "K" }
};
这种<U, V>的写法,正是泛型的特点,其中U, V就是可以随意声明的变量。如果查看字典的类型参数,可以发现其定义方法是这样的
public class Dictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, ... where TKey : notnull
考虑到本节并不是为了将面向对象,所以字典继承的那一大坨类就省略了,关键是where Tkey:notnull,也就是说,字典对键值对的要求只有一个,就是键不得为null。
除了字典之外,还有一些常见的数据结构采用了泛型,列表如下,没事儿可以练习练习。
数据结构 | 说明 | 常用方法 |
---|---|---|
List<T> | 泛型列表 | Add, Remove, RemoveAt |
LinkedList<T> | 双端链表 | AddFirst, AddLast, RemoveFirst, RemoveLast |
Queue<T> | 先进先出列表 | Enqueue, Dequeue |
Stack<T> | 栈,先进后出 | Push, Pop |
泛型委托
委托,是函数的函数;泛型,可以让函数的参数类型更加灵活,二者结合在一起,就是更加灵活的函数的函数,即泛型委托。
只要学过了泛型和委托,那么对泛型委托将毫无理解上的难度,回想前面定义的运算符委托
delegate int Op(int a, int b);
再回想定义泛型时的<T>,那么泛型委托可以非常简单地定义出来
delegate T Op<T>(T a, T b);
然后就可以根据委托,建立一个泛型函数
T add<T>(T a, T b)
{
dynamic d1 = a;
dynamic d2 = b;
return (T)(d1 + d2);
}
var addTest = new Op<int>(add<int>);
//也可以省略add后的<int>,写成下面的形式
//var addTest = new Op<int>(add);
Console.WriteLine(addTest(3, 5));
运行之后控制台出现了8,就是这么简单。
来源:https://tinycool.blog.csdn.net/article/details/128918277
猜你喜欢
- 熟悉Eclipse的都知道Eclipse经常性的会出现一些莫名其妙的问题,有时候运行的好好的突然重启一下项目就莫名的报错,所以经常会用到cl
- 一、Thread.start()与Thread.run()的区别通过调用Thread类的start()方法来启动一个线程,这时此线程是处于就
- 在spring boot中,简单几步,使用spring AOP实现一个 * :1、引入依赖:<dependency> &nbs
- Unity中的PostProcessBuild:深入解析与实用案例在Unity游戏开发中,我们经常需要在构建完成后对生成的应用程序进行一些额
- 一、RatingBar简单介绍RatingBar是基于SeekBar(拖动条)和ProgressBar(状态条)的扩展,用星形来显示等级评定
- 本文实例为大家分享了C#实现打字小游戏的具体代码,供大家参考,具体内容如下using System;using System.Drawing
- MyEclipse2017创建Spring项目,供大家参考,具体内容如下1、创建一个Web Project2、右击项目-->Prope
- 1、引言你能搜到这个教程,说明你对 Maven 感兴趣,但是又不是太理解。那么接下来这个系列的教程将会详细讲解 Maven 的用法,相信你看
- 填充背景色,一般可以选择多种不同样式来填充背景,包括填充为纯色背景、渐变背景、图片背景或者纹理背景等。下面的内容将分别介绍通过C#来设置Ex
- 提到数组大家肯定不会陌生,但我们也知道数组有个缺点就是在创建时就确定了长度,之后就不能更改长度。所以Java官方向我们提供了ArrayLis
- 记得在2013年12月的时候,有系列文章是介绍怎么开发一个智能手表的App,让用户可以在足球比赛中记录停表时间。随着Android Wear
- 承蒙各位厚爱,我们一起每天进步一点点!(鼠标选中空白处查看答案)1、现有如下代码段:x = 2;while(x<n/2){x = 2*
- Java Benchmark 基准测试的实例详解import java.util.Arrays; import java.util.conc
- 一.话题引入在做项目过程中,我们一般都是最先编写登录注册功能,登录功能最重要的是登录成功后,系统还会保存该登录用户信息,这种保存用户信息的逻
- 本文介绍了JAVA中实现原生的 socket 通信机制原理,分享给大家,具体如下:当前环境jdk == 1.8知识点socket 的连接处理
- 前言需要对一个List中的对象进行唯一值属性去重,属性求和,对象假设为BillsNums,有id、nums、sums三个属性,其中id表示唯
- 目录前言解决方案前言我们在开发Spring应用时可能会不小心注入两个相同类型的Bean,比如实现了两个相同Service接口的类,示例伪代码
- 背景为了了解Seata AT模式的原理,我通过源码解读的方式画出了Seata AT模式启动的图示:如果是基于Springboot项目的话,项
- 关于java中遍历map具体哪四种方式,请看下文详解吧。方式一 这是最常见的并且在大多数情况下也是最可取的遍历方式。在键值都需要时使用。 M
- java模拟实现图书检索系统 (基础版),供大家参考,具体内容如下练习实现3个简单的功能,没有优化,可以根据需求,自行添加想要实现的功能。B