第一课《.net之--泛型》
今天我来学习泛型,泛型是编程入门学习的基础类型,从.net诞生2.0开始就出现了泛型,今天我们开始学习泛型的语法和使用。
什么是泛型?
泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性。泛型为.NET框架引入了类型参数(type parameters)的概念。类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。这意味着使用泛型的类型参数T,写一个类MyList<T>,客户代码可以这样调用:MyList<int>, MyList<string>或 MyList<MyClass>。这避免了运行时类型转换或装箱操作的代价和风险。
上面是官方腔调,我说人话:泛型就是为了满足不同类型,相同代码的重用!
为什么要有泛型?
下面我们举例子来讲解为什么会要泛型,以下我列举了三个例子来讲解:
我们列举了ShowInt,ShowString,ShowDatatime三个方法,如果我们在程序中每封装一个方法有不同参数,就要像下面这样写一个函数的话,代码会变得很累赘,在调用的时候必须传递吻合的参数,所以不得不写出了3个方法,那有没有好的方法来解决这样的问题呢?答案是当然有,微软是很聪明的,倾听小满哥慢慢来讲。
/// <summary>
/// 打印个int值/// </summary>
/// <param name="iParameter"></param>
public static void ShowInt(int iParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter);
} /// <summary>
/// 打印个string值/// </summary>
/// <param name="sParameter"></param>
public static void ShowString(string sParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter);
} /// <summary>
/// 打印个DateTime值/// </summary>
/// <param name="oParameter"></param>
public static void ShowDateTime(DateTime dtParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter);
}
定义一些测试变量
int iValue = 123;
string sValue = "456";
DateTime dtValue = DateTime.Now;
object oValue = "789";
普通方式调用演示
ShowInt(iValue);
ShowInt(sValue);//这样不行类型必须吻合
ShowString(sValue);
ShowDateTime(dtValue);
在.net 1.0的时候微软出现了object这个概念,下面有一个方法ShowObject,你们就会发现不管参数是int srtring datetime 我们都可以调用ShowObject来操作实现,那为什么会这样呢?
1:Object类型是一切类型的父类。
2:任何父类出现的地方,都可以用子类来代替。
出现这个基本满足了开发人员的一些需求,在.net1.0和1.1的时候,这个时候还没有泛型就用object代替。
/// <summary>
/// 打印个object值
/// 1 object类型是一切类型的父类
/// 2 任何父类出现的地方,都可以用子类来代替
/// .Net Framework 1.0 1.1
/// </summary>
/// <param name="oParameter"></param>
public static void ShowObject(object oParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(CommonMethod), oParameter.GetType().Name, oParameter);
}
定义一些测试变量
int iValue = 123;
string sValue = "456";
DateTime dtValue = DateTime.Now;
object oValue = "789";
object方式调用演示
ShowObject(oValue);
ShowObject(iValue);
ShowObject(sValue);
ShowObject(dtValue);
接着吹牛比,劳动人民的智慧还是很屌的,经过之前的经历在.net2.0的时候,微软让主角登场了"泛型",当然同时出现的还有“线程池”这个我们先不讲,回到正轨什么是泛型?你们在开发的时候有没有用过List<T>这个集合?这个就是泛型,深化下直接举个栗子吧,下面我写一个例子Show<T>(T tParameter)看下泛型的写法:
有个毛用?下面这个方法也可以向上面ShowObject一样,你们会发现不管参数是int srtring datetime 我们也可以调用Show<T>(T tParameter)来操作实现,替换了ShowObject这个方法的实现,具备了满足了不同参数类型的方法实现,更适用性,泛型方法声明的时候,没有指定类型,而是调用的时候指定,具有延迟声明和延迟思想,这个思想对我们在开发框架的时候灰常有用,不得不说老外这点还是很几把厉害(还是我们被洗脑了?也许吧,我相信等中文编程语言出来估计会更屌,中华文化博大精深嘛),现在小伙伴们是不是大概了解泛型的基础作用了?
/// <summary>
/// 泛型方法声明的时候,没有指定类型,而是调用的时候指定
/// 延迟声明:把参数类型的声明推迟到调用
/// 延迟思想:推迟一切可以推迟的
///
/// 2.0泛型不是语法糖,而是由框架升级提供的功能
/// 泛型方法性能上和普通方法差不多的/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tParameter"></param>
public static void Show<T>(T tParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString());
}
调用演示
Show<object>(oValue);
Show<int>(iValue);
Show(iValue);//可以去掉,自动推算
Show<string>(iValue);//必须匹配
Show<string>(sValue);
Show<DateTime>(dtValue);
那问题来了,既然都可以实现为什么要用这个呢?我们做事凡事都要带着疑问去看待,有些事别人说好,但真的好不好我们要自己亲自试试才知道,我们最关注的的效率问题,下面是测试代码:
public static void Show()
{
Console.WriteLine("****************Monitor******************");
{
int iValue = ;
long commonSecond = ;
long objectSecond = ;
long genericSecond = ; {
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
ShowInt(iValue);
}
watch.Stop();
commonSecond = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
ShowObject(iValue);
}
watch.Stop();
objectSecond = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = ; i < ; i++)
{
Show<int>(iValue);
}
watch.Stop();
genericSecond = watch.ElapsedMilliseconds;
}
Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}"
, commonSecond, objectSecond, genericSecond);
}
}
结果如下:commonSecond=508,objectSecond=916,genericSecond=452 你会发现普通方法508ms,object方法916ms,泛型是452ms,其中object最慢,为什么最慢呢?因为object会经过一个装箱拆箱的过程,所以性能上会损失一些,但是在我看来这样上亿次这点损耗,算不了什么,但是可以证明泛型和普通类型速度是差不多的,这一点可以认可泛型还是性能挺好的,这个可以推荐使用泛型的理由之一。
但泛型仅仅表现在这个层面吗?远远不止,我们用泛型远远不是为了提升刚刚那点性能,为什么要用泛型?答案来了,我们要用泛型就是为了满足不同类型,相同代码的重用,下面我继续举栗子:
泛型的一些用法,泛型只有四种用途,泛型类,泛型接口,泛型委托,泛型方法,如下:
public class GenericClass<T>
{
public T Property { get; set; }
}
public interface IGenericInterface<T>
{
}
public delegate void DoNothing<T>();
调用演示
List<int> intList = new List<int>();//原生态List类
List<string> stringList = new List<string>();
GenericClass<int> iGenericClass = new GenericClass<int>();
iGenericClass.Property = 1;
GenericClass<string> sGenericClass = new GenericClass<string>();
sGenericClass.Property = "1233";
DoNothing<int> method = new DoNothing<int>(() => { });
还有一种,别被吓到:T,S,Xiaomange这些语法,只是占位符别怕,你可以自己定义的,在你调用的时候确定类型就OK了,好了差不多能理解泛型了吧?再说一次泛型就是为了满足不同类型,相同代码的重用。
public class ChildClassGeneric<T, S, XiaoManGe> : GenericClass<T>, IGenericInterface<S>
{ }
接下来我们来聊一聊拓展的一部分,好像泛型很吊的样子感觉什么都能用泛型类型代替,但是天下哪有那么好的事情,双刃剑的道理都懂,所以出现了泛型的约束这个紧箍咒。
泛型的约束
直接来代码:
很简单的一个例子,接口ISports,IWork,People类,Japanese类等简单继承了一下,目前准备的一些代码。
public interface ISports
{
void Pingpang();
} public interface IWork
{
void Work();
} public class People
{
public int Id { get; set; }
public string Name { get; set; } public void Hi()
{ }
}
public class Chinese : People, ISports, IWork
{
public void Tradition()
{
Console.WriteLine("仁义礼智信,温良恭俭让");
}
public void SayHi()
{
Console.WriteLine("吃了么?");
} public void Pingpang()
{
Console.WriteLine("打乒乓球...");
} public void Work()
{
throw new NotImplementedException();
}
}
public class Hubei : Chinese
{ public Hubei(int id)
{
}
public string Changjiang { get; set; }
public void Majiang()
{
Console.WriteLine("打麻将啦。。");
}
}
public class Japanese : ISports
{
public int Id { get; set; }
public string Name { get; set; } public void Pingpang()
{
Console.WriteLine("打乒乓球...");
}
public void Hi()
{ }
}
再来个调用类Constraint
public class Constraint
{
/// <summary>
/// 代码编译没问题,执行的时候才报错
/// 代码安全问题
/// </summary>
/// <param name="oParameter"></param>
public static void ShowObject(object oParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(CommonMethod), oParameter.GetType().Name, oParameter); People people = (People)oParameter; Console.WriteLine(people.Id);//这里就不行了 代码安全问题,调用不到,但编译不会报错。
Console.WriteLine(people.Name);
} /// <summary>
/// 基类约束:
/// 1 带来权力,可以使用基类里面的属性和方法
/// 2 带来义务,类型参数必须是基类或者其子类
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tParameter"></param>
public static void Show<T>(T tParameter)
where T : People, ISports, new()//都是and 关系
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); Console.WriteLine(tParameter.Id);
Console.WriteLine(tParameter.Name);
tParameter.Hi();
tParameter.Pingpang();
T t = new T();
} public static void ShowPeople(People tParameter)
{
Console.WriteLine("This is {0},parameter={1},type={2}",
typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); Console.WriteLine(tParameter.Id);
Console.WriteLine(tParameter.Name);
tParameter.Hi();
//tParameter.Pingpang();
} public static T DoNothing<T>(T tParameter)
//where T : ISports//接口约束
//where T : class//引用类型约束
//where T : struct//值类型约束
where T : new()//无参数构造函数约束
{
//tParameter.Pingpang();
//return null;
T t = new T();
return default(T);//会根据T的类型,去产生一个默认值
}
}
有兴趣的可以测试下,用ShowObject的方法和泛型Show<T>(T tParameter)调用来看差异,如果不加入约束,想调用参数的属性和方法,代码安全问题是调用不了的,会报错,但是加入基类约束之后是可以调用到的,所以泛型约束带来了权利,可以使用基类的属性和方法,但也带来义务,参数只能是基类和子类,又想马儿跑,又想马儿不吃草的事情是没有的,权利和义务是相对的,在享受权利的同时也会带来义务。
其次,约束可以多重约束,然后即可作为参数约束也可以作为返回值约束,例如default(T)会根据泛型类型返回一个默认值,如果是无参数构造约束就可以类似这样写返回值T t=new T()。
总之,我觉得泛型约束为了更灵活的满足不同条件的需求而产生的,就是我们在写一些固定的需求,约束叠加来完成我们的功能,同时不让泛型肆无忌惮。
泛型约束范围如下:
约束 |
描述 |
where T: struct |
类型参数必须为值类型。 |
where T : class |
类型参数必须为引用类型。 |
where T : new() |
类型参数必须有一个公有、无参的构造函数。当于其它约束联合使用时,new()约束必须放在最后。 |
where T : <base class name> |
类型参数必须是指定的基类型或是派生自指定的基类型。 |
where T : <interface name> |
类型参数必须是指定的接口或是指定接口的实现。可以指定多个接口约束。接口约束也可以是泛型的。 |
好了泛型的约束的先说到这,继续套底子,了解到的都倒出来。
逆变和协变
逆变和协变不知道有没有小伙伴熟悉的,我开始是不知道这个的,第一次看到也是一脸懵逼,到现在也有点迷糊,能不能讲清楚看造化了,哈哈
继续举栗子:
//协变
public interface IEnumerable<out T> : IEnumerable
//逆变
public delegate void Action<in T>(T obj);
这段代码是不是很熟悉?里面有个Out T 还有 in T,这里的Out 不是我们熟悉的参数返回Out,ref的作用,是协变专用的,逆变和协变指出现在接口或者委托泛型前面,
In只能作为参数传入,Out只能作为参数传出。
下面来个代码
public class Bird
{
public int Id { get; set; }
}
public class Sparrow : Bird
{
public string Name { get; set; }
}
调用实例
IEnumerable<int> intList = new List<int>();
Action<int> iAction = null; Bird bird1 = new Bird();
Bird bird2 = new Sparrow();//左边是父类 右边是子类
Sparrow sparrow1 = new Sparrow();
//Sparrow sparrow2 = new Bird();//不是所有的鸟,都是麻雀 List<Bird> birdList1 = new List<Bird>();//一群鸟 是一群鸟
//List<Bird> birdList2 = new List<Sparrow>();//一群麻雀难道不是一群鸟 ? 不是的,没有父子关系
List<Bird> birdList3 = new List<Sparrow>().Select(c => (Bird)c).ToList();//这里使用别扭了,明明知道但就是不能这样写
以上代码发现问题了吗?很明显出现了一些不和谐的地方,我们换个方式如下:
/// <summary>
/// 逆变:只能修饰传入参数
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListIn<in T>
{
//T Get(); void Show(T t);
} public class CustomerListIn<T> : ICustomerListIn<T>
{
//public T Get()
//{
// return default(T);
//} public void Show(T t)
{
}
} /// <summary>
/// out 协变 只能是返回结果
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListOut<out T>
{
T Get(); //void Show(T t);//不能做参数
} public class CustomerListOut<T> : ICustomerListOut<T>
{
public T Get()
{
return default(T);
} //public void Show(T t)
//{ //}
}
{//协变:接口泛型参数加了个out,就是为了解决刚才的不和谐
IEnumerable<Bird> birdList1 = new List<Bird>();
IEnumerable<Bird> birdList2 = new List<Sparrow>(); //Func<Bird> func = new Func<Sparrow>(() => null); ICustomerListOut<Bird> customerList1 = new CustomerListOut<Bird>();
ICustomerListOut<Bird> customerList2 = new CustomerListOut<Sparrow>();
} {//逆变
ICustomerListIn<Sparrow> customerList2 = new CustomerListIn<Sparrow>();
ICustomerListIn<Sparrow> customerList1 = new CustomerListIn<Bird>(); //customerList1.Show() ICustomerListIn<Bird> birdList1 = new CustomerListIn<Bird>();
birdList1.Show(new Sparrow());
birdList1.Show(new Bird()); Action<Sparrow> act = new Action<Bird>((Bird i) => { });
}
这样可以了,协变IEnumerable加入协变Out 左边是个父类,右边可以是子类,逆变In 左边是个字类,右边也可以是父类,下面这段就更晕了,稍微看下吧。
{
IMyList<Sparrow, Bird> myList1 = new MyList<Sparrow, Bird>();
IMyList<Sparrow, Bird> myList2 = new MyList<Sparrow, Sparrow>();//协变
IMyList<Sparrow, Bird> myList3 = new MyList<Bird, Bird>();//逆变
IMyList<Sparrow, Bird> myList4 = new MyList<Bird, Sparrow>();//协变+逆变
}
总结下原理:泛型在JIT编译时指定具体类型,同一个泛型类,不同的类型参数,其实会变成不用的类型。
我走的很慢,但从不后退!
第一课《.net之--泛型》的更多相关文章
- vue.js学习(第一课)
学习资料 来自台湾小凡! vue.js是javascript的一个库,只专注于UI层面,核心价值永远是 API的简洁. 第一课: 不支持IE8. 1.声明式渲染: el元素的简称 element : ...
- Magento学习第一课——目录结构介绍
Magento学习第一课--目录结构介绍 一.Magento为何强大 Magento是在Zend框架基础上建立起来的,这点保证了代码的安全性及稳定性.选择Zend的原因有很多,但是最基本的是因为zen ...
- <-0基础学python.第一课->
初衷:我电脑里面的歌曲很久没换了,我想听一下新的歌曲,把他们下载下来听,比如某个榜单的,但是一首一首的点击下载另存为真的很恶心 所以我想有没有办法通过程序的方式来实现,结果还真的有,而且网上已经有有人 ...
- 留念 C语言第一课简单的计算器制作
留念 C语言第一课简单的计算器制作 学C语言这么久了. /* 留念 C语言第一课简单的计算器制作 */ #include<stdio.h> #include<stdlib.h ...
- MFC学习-第一课 MFC运行机制
最近由于兴趣爱好,学习了孙鑫的MFC教程的第一课.看完视频了,自己便用visual studio 2010尝试了MFC编程,其中遇到了一些问题. 1.vs2010不像vs6.0那样可以新建一个空的MF ...
- OpenCV 第一课(安装与配置)
OpenCV 第一课(安装与配置) win10,opencv-2.4.13, 安装, vs2013, 配置 下载安装软件 官网OpenCV下载地址下载最新版本,我下载的是opencv.2.4.13,然 ...
- 【第一课】神奇的Context
初学Android的困惑 初学Android跳转页面的时候,往往教程里是这么写的: Intent intent = new Intent(); //MyActivity就是当前的Activity,It ...
- CodeIgniter框架入门教程——第一课 Hello World!
本文转载自:http://www.softeng.cn/?p=45 今天开始,我将在这里连载由我自己编写的<CodeIgniter框架入门教程>,首先,这篇教程的读着应该是有PHP基础的编 ...
- ruby代码重构第一课
(文章是从我的个人主页上粘贴过来的, 大家也可以访问我的主页 www.iwangzheng.com) 新手写代码的时候往往会出现很多重复的代码没有提取出来,大师高瞻远瞩总能提点很多有意义的改进,今天重 ...
- [译]Quartz 框架 教程(中文版)2.2.x 之第一课 开始使用Quartz框架
第一课:开始使用Quartz框架 在你使用调度器之前,需要借助一些具体的例子去理解(谁愿意只是猜啊?).你可以使用SchedulerFactory类来达到程序调度的目的.有一些Quartz框架的用户可 ...
随机推荐
- Python_检查程序规范
''' 检查Python程序的一些基本规范,例如,运算符两测是否有空格,是否每次只导入一个模块,在不同的功能模块之间是否有空行,注释是否够多,等等 ''' import sys import re d ...
- textarea 里设置 style="resize:none"
禁止textarea拉伸的方法是:: 设置这个 style="resize:none" 属性 例子: < ...
- springboot+redis实现分布式session共享
官方文档,它是spring session项目的redis相关的一个子文档:https://docs.spring.io/spring-session/docs/2.0.0.BUILD-SNAPSHO ...
- resteasy上传文件写法
resteasy服务器代码 @Path(value = "file") public class UploadFileService { private final String ...
- Unity文档阅读 第二章 依赖注入
Introduction 介绍Chapter 1 outlines how you can address some of the most common requirements in enterp ...
- 解决C语言程序报错:return type defaults to‘int’
下面是通过自定义一个函数printN,之后在main函数中调用printN,使得可以通过输入整数N,将从1到N的全部整数都打印出来的程序. 但是在编译过程中却报错: return type defau ...
- 分布式服务跟踪及Spring Cloud的实现
在分布式服务架构中,需要对分布式服务进行治理——在分布式服务协同向用户提供服务时,每个请求都被哪些服务处理?在遇到问题时,在调用哪个服务上发生了问题?在分析性能时,调用各个服务都花了多长时间?哪些调用 ...
- Collection集合详解
/*Collection--List:元素是有序的,元素可以重复.因为该集合体系有索引. ---ArrayList;底层的数据结构使用的是数组结构.特点:查询速度很快.但是增删很慢.线程不同步 --- ...
- 玩转spring MVC(八)----spring MVC整合json
这篇文章在前边项目的基础上来整合json,最新项目资料见:http://download.csdn.net/detail/u012116457/8436187 首先需要的jar包:jackson-co ...
- BZOJ_3675_[Apio2014]序列分割_斜率优化
BZOJ_3675_[Apio2014]序列分割_斜率优化 Description 小H最近迷上了一个分隔序列的游戏.在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列.为了 ...