C#泛型学习笔记
本笔记摘抄自:,记录一下学习过程以备后续查用。
一、什么是泛型
泛型是c#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。泛型类就类似于一个模板,可以在需要时为这个模板传入任何我们需要的类型。
二、为什么使用泛型
下面代码演示输出几种类型的相关信息:
class program { /// <summary> /// 打印帮助类 /// </summary> public class showhelper { /// <summary> /// showint /// </summary> /// <param name="intparam"></param> public static void showint(int intparam) { console.writeline($"class={typeof(showhelper).name},type={intparam.gettype().name},parameter={intparam}"); } /// <summary> /// showstring /// </summary> /// <param name="strparam"></param> public static void showstring(string strparam) { console.writeline($"class={typeof(showhelper).name},type={strparam.gettype().name},parameter={strparam}"); } /// <summary> /// showdatetime /// </summary> /// <param name="dtparam"></param> public static void showdatetime(datetime dtparam) { console.writeline($"class={typeof(showhelper).name},type={dtparam.gettype().name},parameter={dtparam}"); } } static void main(string[] args) { #region 非泛型打印方式一 showhelper.showint(123); showhelper.showstring("hello world."); showhelper.showdatetime(datetime.now); console.read(); #endregion } }
运行结果如下:
上面3个方法很相似,除了参数类型不同外,实现的功能是一样的,可以稍作优化。
下面代码演示使用继承的方式输出几种类型的相关信息:
class program { /// <summary> /// 打印帮助类 /// </summary> public class showhelper { /// <summary> /// showtype /// </summary> /// <param name="obj"></param> public static void showtype(object obj) { console.writeline($"class={typeof(showhelper).name},type={obj.gettype().name},parameter={obj}"); } } static void main(string[] args) { #region 非泛型打印方式二 showhelper.showtype(123); showhelper.showtype("hello world."); showhelper.showtype(datetime.now); console.read(); #endregion } }
功能实现没有问题,只是object与其它类型的转换,涉及到装箱和拆箱的过程,这个是会损耗程序的性能的。
三、泛型类型参数
在泛型类型或方法的定义中,泛型类型参数可认为是特定类型的占位符。
下面代码演示使用泛型的方式输出几种类型的相关信息:
class program { /// <summary> /// 打印帮助类 /// </summary> public class showhelper { /// <summary> /// show /// </summary> /// <param name="obj"></param> public static void show<t>(t tparam) { console.writeline($"class={typeof(showhelper).name},type={tparam.gettype().name},parameter={tparam}"); } } static void main(string[] args) { #region 泛型打印方式 showhelper.show(123); showhelper.show("hello world."); showhelper.show(datetime.now); console.read(); #endregion } }
运行结果如下:
1、为什么泛型可以解决上面的问题呢?
泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到调用的时候才给它指定。
2、泛型究竟是如何工作的呢?
程序执行原理:控制台程序最终会编译成一个exe程序。当exe被点击的时候,会经过jit(即时编译器)的编译,最终生成二进制代码才能被计算机执行。
泛型工作原理:泛型加入到语法以后,vs自带的编译器做了升级,升级之后编译时若遇到泛型,会做特殊的处理:生成占位符。然后经过jit编译的时候,
会把上面编译生成的占位符替换成具体的数据类型。
下面代码演示泛型占位符:
class program { static void main(string[] args) { #region 泛型占位符 console.writeline(typeof(list<>)); console.writeline(typeof(dictionary<,>)); console.read(); #endregion } }
运行结果如下:
3、泛型性能问题
下面代码演示泛型性能测试:
class program { static void main(string[] args) { #region 泛型性能测试 long commontime = 0; long objecttime = 0; long generictime = 0; stopwatch watch = new stopwatch(); watch.start(); for (int i = 0; i < 10000; i++) { showhelper.showint(123); } watch.stop(); commontime = watch.elapsedmilliseconds; watch.reset(); watch.start(); for (int i = 0; i < 10000; i++) { showhelper.showtype(123); } watch.stop(); objecttime = watch.elapsedmilliseconds; watch.reset(); watch.start(); for (int i = 0; i < 10000; i++) { showhelper.show(123); } watch.stop(); generictime = watch.elapsedmilliseconds; console.clear(); console.writeline($"common time={commontime}ms"); console.writeline($"object time={objecttime}ms"); console.writeline($"generic time={generictime}ms"); console.read(); #endregion } }
运行结果如下:
从结果可以看出,泛型的性能是最高的。
四、泛型类
下面代码演示泛型类:
class program { /// <summary> /// 泛型类 /// </summary> /// <typeparam name="t"></typeparam> public class genericclass<t> { public t vart; } static void main(string[] args) { #region 泛型类 //t是int类型 genericclass<int> genericint = new genericclass<int> { vart = 123 }; console.writeline($"the value of t={genericint.vart}"); //t是string类型 genericclass<string> genericstring = new genericclass<string> { vart = "123" }; console.writeline($"the value of t={genericstring.vart}"); console.read(); #endregion } }
运行结果如下:
五、泛型接口
注:泛型在声明的时候可以不指定具体的类型,继承的时候也可以不指定具体类型,但是在使用的时候必须指定具体类型。
下面代码演示泛型接口:
class program { /// <summary> /// 泛型接口 /// </summary> public interface igenericinterface<t> { t gett(t t); } /// <summary> /// 泛型接口实现类 /// </summary> /// <param name="args"></param> public class genericget<t> : igenericinterface<t> { t vart; public t gett(t t) { vart = t; return vart; } } static void main(string[] args) { #region 泛型接口 igenericinterface<int> genericinterface = new genericget<int>(); var result = genericinterface.gett(123); console.writeline($"result={result}"); console.read(); #endregion } }
运行结果如下:
六、泛型委托
下面代码演示泛型委托:
class program { /// <summary> /// 泛型委托 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="t"></param> public delegate void sayhi<t>(t t); static void main(string[] args) { #region 泛型委托 sayhi<string> sayhi = sayhello; sayhi("hello world"); console.read(); #endregion } /// <summary> /// sayhello /// </summary> /// <param name="greeting"></param> public static void sayhello(string greeting) { console.writeline($"{greeting}"); } }
运行结果如下:
七、泛型约束
泛型约束,实际上就是约束的类型t,使t必须遵循一定的规则。比如t必须继承自某个类或者t必须实现某个接口等等。
怎样给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。
泛型约束总共有五种:
约束 | s说明 |
t:结构 | 类型参数必须是值类型 |
t:类 | 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 |
t:new() | 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。 |
t:<基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
t:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。 |
7.1基类约束
下面代码演示基类约束:
/// <summary> /// 运动类接口 /// </summary> public interface isports { void pingpong(); } /// <summary> /// 人类基类 /// </summary> public class people { public string name { get; set; } public virtual void greeting() { console.writeline("hello world."); } } /// <summary> /// 中国人 /// </summary> public class chinese : people, isports { public void finetradition() { console.writeline("自古以来,中华民族就保持着勤劳的优良传统。"); } public override void greeting() { console.writeline("吃饭了没?"); } public void pingpong() { console.writeline("乒乓球是中国的国球。"); } } static void main(string[] args) { #region 泛型约束:基类约束 chinese chinese = new chinese() { name = "中国人" }; showpeople(chinese); console.read(); #endregion } /// <summary> /// 基类约束 /// </summary> /// <param name="obj"></param> public static void showpeople<t>(t tparam) where t:people { console.writeline($"{((people)tparam).name}"); } }
运行结果如下:
注:基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用作约束就无任何意义了,因为sealed类没有子类。
7.2接口约束
下面代码演示接口约束:
class program { /// <summary> /// 运动类接口 /// </summary> public interface isports { void pingpong(); } /// <summary> /// 人类基类 /// </summary> public class people { public string name { get; set; } public virtual void greeting() { console.writeline("hello world."); } } /// <summary> /// 中国人 /// </summary> public class chinese : people, isports { public void finetradition() { console.writeline("自古以来,中华民族就保持着勤劳的优良传统。"); } public override void greeting() { console.writeline("吃饭了没?"); } public void pingpong() { console.writeline("乒乓球是中国的国球。"); } } static void main(string[] args) { #region 泛型约束:接口约束 chinese chinese = new chinese() { name = "中国人" }; getsportsbyinterface(chinese); console.read(); #endregion } /// <summary> /// 接口约束 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="t"></param> /// <returns></returns> public static t getsportsbyinterface<t>(t t) where t : isports { t.pingpong(); return t; } }
运行结果如下:
7.3引用类型约束 class
引用类型约束保证t一定是引用类型的。
下面代码演示引用类型约束:
class program { /// <summary> /// 运动类接口 /// </summary> public interface isports { void pingpong(); } /// <summary> /// 人类基类 /// </summary> public class people { public string name { get; set; } public virtual void greeting() { console.writeline("hello world."); } } /// <summary> /// 中国人 /// </summary> public class chinese : people, isports { public void finetradition() { console.writeline("自古以来,中华民族就保持着勤劳的优良传统。"); } public override void greeting() { console.writeline("吃饭了没?"); } public void pingpong() { console.writeline("乒乓球是中国的国球。"); } } static void main(string[] args) { #region 泛型约束:引用类型约束 chinese chinese = new chinese() { name = "中国人" }; getsportsbyclass(chinese); console.read(); #endregion } /// <summary> /// 引用类型约束 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="t"></param> /// <returns></returns> public static t getsportsbyclass<t>(t t) where t : class { if (t is isports) { (t as isports).pingpong(); } return t; } }
运行结果如下:
7.4值类型约束 struct
值类型约束保证t一定是值类型的。
下面代码演示值类型约束:
class program { /// <summary> /// 绩效工资 /// </summary> public struct achievement { public double meritpay { get; set; } public string level { get; set; } public double reallypay() { switch (level) { case "a": meritpay = meritpay * 1.0; break; case "b": meritpay = meritpay * 0.8; break; case "c": meritpay = meritpay * 0.6; break; case "d": meritpay = 0; break; default: meritpay = 0; break; }; return meritpay; } } static void main(string[] args) { #region 泛型约束:值类型约束 achievement achievement = new achievement { meritpay = 500, level = "b" }; var result = getreallypay(achievement).reallypay(); console.writeline($"reallypay={result}"); console.read(); #endregion } /// <summary> /// 值类型约束 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="t"></param> /// <returns></returns> public static t getreallypay<t>(t t) where t : struct { return t; } }
运行结果如下:
7.5无参数构造函数约束 new()
下面代码演示无参数构造函数约束:
class program { /// <summary> /// 运动类接口 /// </summary> public interface isports { void pingpong(); } /// <summary> /// 人类基类 /// </summary> public class people { public string name { get; set; } public virtual void greeting() { console.writeline("hello world."); } } /// <summary> /// 中国人 /// </summary> public class chinese : people, isports { public void finetradition() { console.writeline("自古以来,中华民族就保持着勤劳的优良传统。"); } public override void greeting() { console.writeline("吃饭了没?"); } public void pingpong() { console.writeline("乒乓球是中国的国球。"); } } /// <summary> /// 广东人 /// </summary> public class guangdong : chinese { public guangdong() { } public string dialect { get; set; } public void mahjong() { console.writeline("这麻将上瘾的时候,一个人也说是三缺一呀。"); } } static void main(string[] args) { #region 泛型约束:无参数构造函数约束 guangdong guangdong = new guangdong() { name = "广东人" }; getmahjong(guangdong); console.read(); #endregion } /// <summary> /// 无参数构造函数约束 /// </summary> /// <typeparam name="t"></typeparam> /// <param name="t"></param> /// <returns></returns> public static t getmahjong<t>(t t) where t : people, isports, new() { if (t is guangdong) { (t as guangdong).mahjong(); } return t; } }
运行结果如下:
从上面可以看出,泛型约束可以有多个,但是有多个泛型约束时,new()约束要放到最后。
八:泛型的协变和逆变
协变和逆变是在.net 4.0的时候出现的,只能放在接口或者委托的泛型参数前面,out协变covariant,用来修饰返回值;in:逆变contravariant,用来修饰
传入参数。
下面代码演示父类与子类的声明方式:
class program { /// <summary> /// 动物基类 /// </summary> public class animal { public int breed { get; set; } } /// <summary> /// 猫类 /// </summary> public class cat : animal { public string name { get; set; } } static void main(string[] args) { #region 泛型的协变和逆变 //直接声明animal类 animal animal = new animal(); //直接声明cat类 cat cat = new cat(); //声明子类对象指向父类 animal animal2 = new cat(); //声明animal类的集合 list<animal> listanimal = new list<animal>(); //声明cat类的集合 list<cat> listcat = new list<cat>(); #endregion } }
以上代码是可以正常运行的。假如使用下面的声明方式,是否正确呢?
list<animal> list = new list<cat>();
答案是错误的,因为list<animal>和list<cat>之间没有父子关系。
解决方法是使用协变的方式:
ienumerable<animal> list1 = new list<animal>(); ienumerable<animal> list2 = new list<cat>();
按f12查看ienumerable定义:
可以看到,在泛型接口的t前面有一个out关键字修饰,而且t只能是返回值类型,不能作为参数类型,这就是协变。使用协变以后,左边声明的是基类,
右边的声明可以是基类或者基类的子类。
协变除了可以用在接口上面外,还可以用在委托上面:
func<animal> func = new func<cat>(() => null);
除了使用.net框架定义好协变以外,我们也可以自定义协变:
//使用自定义协变 icustomerlistout<animal> customerlist1 = new customerlistout<animal>(); icustomerlistout<animal> customerlist2 = new customerlistout<cat>();
再来看看逆变:
在泛型接口的t前面有一个in关键字修饰,而且t只能方法参数,不能作为返回值类型,这就是逆变。
/// <summary> /// 逆变 只能是方法参数 /// </summary> /// <typeparam name="t"></typeparam> public interface icustomerlistin<in t> { void show(t t); } public class customerlistin<t> : icustomerlistin<t> { public void show(t t) { } }
使用自定义逆变:
//使用自定义逆变 icustomerlistin<cat> customerlistcat1 = new customerlistin<cat>(); icustomerlistin<cat> customerlistcat2 = new customerlistin<animal>();
协变和逆变也可以同时使用。
下面代码演示自定义协变与逆变:
class program { /// <summary> /// 动物基类 /// </summary> public class animal { public int breed { get; set; } } /// <summary> /// 猫类 /// </summary> public class cat : animal { public string name { get; set; } } #region 泛型的自定义协变和逆变 /// <summary> /// int-逆变 outt-协变 /// </summary> /// <typeparam name="int"></typeparam> /// <typeparam name="outt"></typeparam> public interface imylist<in int, out outt> { void show(int t); outt get(); outt do(int t); } public class mylist<t1, t2> : imylist<t1, t2> { public void show(t1 t) { console.writeline(t.gettype().name); } public t2 get() { console.writeline(typeof(t2).name); return default(t2); } public t2 do(t1 t) { console.writeline(t.gettype().name); console.writeline(typeof(t2).name); return default(t2); } } #endregion static void main(string[] args) { #region 泛型的自定义协变与逆变 imylist<cat, animal> mylist1 = new mylist<cat, animal>(); imylist<cat, animal> mylist2 = new mylist<cat, cat>(); //协变 imylist<cat, animal> mylist3 = new mylist<animal, animal>(); //逆变 imylist<cat, animal> mylist4 = new mylist<animal, cat>(); //逆变+协变 mylist1.get(); mylist2.get(); mylist3.get(); mylist4.get(); console.read(); #endregion } }
运行结果如下:
九、泛型缓存
类中的静态类型无论实例化多少次,在内存中只会有一个,静态构造函数只会执行一次。在泛型类中,t类型不同,每个不同的t类型,都会产生一个不同
的副本,所以会产生不同的静态属性、不同的静态构造函数。
下面代码演示泛型缓存:
class program { /// <summary> /// 泛型缓存 /// </summary> /// <typeparam name="t"></typeparam> public class genericcache<t> { private static readonly string typetime = ""; static genericcache() { console.writeline("这个是泛型缓存的静态构造函数:"); typetime = string.format("{0}_{1}", typeof(t).fullname, datetime.now.tostring("yyyymmddhhmmss.fff")); } public static string getcache() { return typetime; } } /// <summary> /// 泛型缓存测试类 /// </summary> public class genericcachetest { public static void show() { for (int i = 0; i < 5; i++) { console.writeline(genericcache<int>.getcache()); thread.sleep(10); console.writeline(genericcache<long>.getcache()); thread.sleep(10); console.writeline(genericcache<datetime>.getcache()); thread.sleep(10); console.writeline(genericcache<string>.getcache()); thread.sleep(10); console.writeline(genericcache<genericcachetest>.getcache()); thread.sleep(10); } } } static void main(string[] args) { #region 泛型缓存 genericcachetest.show(); console.read(); #endregion } }
运行结果如下:
从上面的截图中可以看出,泛型会为不同的类型都创建一个副本,因此静态构造函数会执行5次,另外每次静态属性的值都是一样的。利用泛型的这一特性,可以实现缓存。
注:只能为不同的类型缓存一次;泛型缓存比字典缓存效率高;泛型缓存不能主动释放。
上一篇: 异性使你荷尔蒙大增的时候