下面3个方法的分别在控制台打印int、string、DateTime类型的参数值:
void ShowInt(int val) { Console.WriteLine(val); }
void ShowString(string val) { Console.WriteLine(val); }
void ShowDateTime(DateTime val) { Console.WriteLine(val); }
如果还想打印long、char那么又得写两个方法,很明显有点费事。
void Show(object val) { Console.WriteLine(val); }
以object作为参数的类型,不管是打印int还是string,都可以通过这一个方法实现:
Show(1);
Show("1");
Show(DateTime.Now);
这就达到了一个方法满足多种类型需求的作用。
但是,相比之前那三个方法,以object类型作为参数,在调用方法时会产生一个装箱和拆箱的过程,势必会影响性能,且存在类型安全问题。
void Show<T>(T val) { Console.WriteLine(val); }
在编写Show方法时,可以使用T作为占位符,先不指定T的具体类型,等到调用的时候再指定具体类型。如:
Show<int>(1);
同样当我们想打印string类型的时候:
Show<string>("1");
同时泛型方法还可以从参数类型来推导具体的类型参数,所以就可以简化调用:
Show(1);
Show("1");
这时候T分别是int和string类型。
这样同样达到了以object作为参数类型时的效果,同时它又不会发生装箱和拆箱的操作,所以性能方面也不会有损失,也不会有类型安全问题,真正达到了事半功倍的效果。
泛型是.NET Framework 2.0引入的新特性,在设计类或方法时,引入类型参数, 可以使一个类或方法满足不同类型需求,而不会产生运行时装箱和拆箱操作的成本和风险。
泛型类或方法声明时不需要确定具体的类型,调用时才需要指定具体的类型(延迟声明)。
泛型兼具可重用性、类型安全性和效率,可以用于类、方法、接口、事件、委托等。
可以对泛型进行约束以缩小泛型类型的范围。
以泛型类举例,泛型类在编译的之后,泛型参数会生成占位符,最终会根据调用来生成不同的类。泛型类不只是一个不确定类型的类,而是每个类型都会生成一个类,也就是说虽然代码中只写了一个类,但根据调用的不同,会生成很多类的实例。
在声明泛型类、接口、方法时,带上一个“<>”(尖括号),中间使用占位符如“<T>”,T是类型参数,调用的时候指定的具体类型。具体可以看上文解决方法2(泛型)。
前面说了使用object的解决方案会引起类型安全问题,比如:
/// <summary>
/// 有钱人
/// </summary>
public class RichPeople
{
/// <summary>
/// 名字
/// </summary>
public string Name { get; set; }
}
public class MHY : RichPeople { }
public class MT : RichPeople { }
public class Me { }
public static class GenericTest
{
//显示名字
public static void ShowName(object people)
{
//var richPeople = people as RichPeople; //无法转换时为null 编译器不报错
var richPeople = (RichPeople)people; //无法转换时编译器报错
Console.WriteLine(richPeople.Name);
}
}
//调用
GenericTest.ShowName(new MT() { Name = "马腾" });
GenericTest.ShowName(new MHY() { Name = "马化云" });
GenericTest.ShowName(new Me()); //报错
上述代码编译器是可以通过的,定义一个方法ShowName(object people),使用object参数传入一个对象,方法内部打印参数的Name属性,而只有RichPeople有Name属性,所以需要将传入的参数强制转换为RichPeople类。当调用ShowName方法传入MT和MHY对象时,可以顺利显示名称,因为MT和MHY是继承自RichPeople。但是当传入Me对象时,不管是哪种强转方式都会报错。
当调用方传入了其他对象时,程序就会抛出异常,这就是object造成的类型不安全,因为object可以接收任何类型的参数。
PS:上述两种强转方式,编译器都不会报错!
而如果用泛型方法,也同样存在这样的问题:
public static void ShowName<T>(T people)
{
var richPeople = people as RichPeople; //无法转换时为null 编译器不报错
Console.WriteLine(richPeople.Name);
}
PS:有个小区别,因为泛型类型T是具体的类型,所以无法通过(RichPeople)people的方式进行强制转换,这种方式进行转换时绕不过编译器。
为了解决这个问题,泛型约束诞生了。
泛型约束就是将泛型类型T进行一系列的约束,使泛型类型只能是我们想要的类型。
上诉问题的根本原因就是,能够显示名字的只有RichPeople类,而我们传入了非RichPeople对象,所以我们需要让调用方只能传入RichPeople对象。
将泛型类型T加一个RichPeople类约束:
public static void ShowName<T>(T people) where T : RichPeople
{
Console.WriteLine(people.Name);
}
因为指定了T类型参数必须是RichPeople或RichPeople的派生类,那么T类型必有Name属性,所以我们直接用people对象的Name属性,编译器也不会报错。
而如果不添加RichPeople泛型约束,那会是这样:
编译器报错了,没有加泛型约束,T可以是任何类,所以不一定有Name属性,当然不能这么用。
当添加RichPeople类约束后,T类型只能是RichPeple类或其派生类对象,传入其他对象时,编译器报错:
因此,调用该方法时只能传入RichePeople或其子类,这样就不会出现类型安全问题。
本文标题既是初识泛型,那就不讲太多,到此为止!后续详细介绍几种泛型约束以及泛型的协变和逆变。
版权声明:本文由不落阁原创出品,转载请注明出处!
广告位
跟不落阁,学DOTNET!
广告位
不愧是C# 2.0劳动人名智慧的结晶
2020-07-14 15:42回复