泛型是FW2.0的语法,是比较悠久的技术之一,由于泛型的出现极大的满足了灵活开发的要求,下面就来看看泛型是什么?
为什么要有泛型?
在没出现泛型之前,当我们需要调用一个方法时,需要用 “对象.方法()” 的方式来调用,而当我们调用不同参数类型的重载方法的时候,就需要写多个重载方法,然后使用的时候参数类型对应且分别调用,如:
// 声明一个类
class Student
{
public int id { get; set; }
public string name { get; set; }
public void Write(int a)
{
Console.WriteLine(a);
}
public void Write(string b)
{
Console.WriteLine(b);
}
public void Write(bool c)
{
Console.WriteLine(c);
}
}
// 调用方法
class Program
{
static void Main(string[] args)
{
Student student = new Student();
student.Write(1);
student.Write("字符");
student.Write(true);
}
}
由上面可以看出来,Student类中的write方法极大的耦合了,所以应当改进,在FW2.0之前是使用obj类型来改进方法的:
// 将耦合的三个方法写成一个方法,可以满足扩展的要求,因为所有的属性都是object的子类
public void Write(object a)
{
Console.WriteLine(a);
}
但是在2.0 的时候有了转机,微软提供了一种泛型类型,使扩展性进一步提升:
// 是方法中这样声明就可以使用泛型
public void Write<T>(T a)
{
Console.WriteLine(a);
}
// 调用的时候
static void Main(string[] args)
{
Student student = new Student();
// 完整写法
student.Write<int>(1);
// 语法糖
student.Write(1);
student.Write("字符");
student.Write(true);
}
泛型的出现解决了什么问题?
当需要调用的方法的参数类型不同而执行逻辑是一样的时候,就可以将这一类方法规整为一个泛型方法,泛型方法的出现,让方法脱离了参数类型的限制,声明的泛型方法不需要指定参数类型,而是在方法调用的时候指定类型,这就是延迟声明的思想(把参数类型的声明推迟到调用的时候)。
为什么要用泛型?
1、泛型是为了解决一个方法的不同参数类型的、相同的代码重用问题。
2、泛型可以用在类、方法、接口、委托上。
3、泛型比obj类型更容易扩展,而且省一些性能。
泛型的继承问题
1、普通类继承泛型类
// 声明泛型类
class Person<T>
{
public int id { get; set; }
public string name { get; set; }
}
// 实现泛型类
class Student : Person<int>
{
}
当一个泛型的类指定了类型的时候,他就不再是一个泛型了,所以普通的类可以继承。将泛型类变为非泛型类。
2、泛型类继承泛型类:
class Student<T> : Person<T>
{
}
这样,只要能将继承的泛型类型满足后,就可以继承泛型类。
3、泛型类可以继承非泛型类
class BaseStudent
{
}
class Student<T> : BaseStudent
{
}
这里的继承就没有什么限制,就是普通的继承关系。
泛型的约束问题
使用泛型的约束,可以为泛型带来像一个的权利,也有了相应的义务。
泛型约束一共五种,分别是:
1、基类约束:规定泛型的类型参数必须是基类或者是基类的子类。
2、接口约束:规定泛型必须是接口
3、引用类型约束:规定泛型必须是引用类型
4、值类型约束:规定泛型必须是值类型
5、无参数构造函数约束:规定泛型必须有无参构造函数
class Student<T> : BaseStudent where T: BaseStudent,new()
{
}
使用的时候可以对一个泛型进行对个约束,但是注意不用的约束有先后的问题,使用的时候注意,一般编译器会提示。
泛型的返回值
泛型是可以作为返回值的
public T Write<T>(T a)
{
Console.WriteLine(a);
// default方法可以返回对应参数的默认初始类型
return default(T);
}
DataTime是值类型
逆变与协变
在泛型这,最难懂的就是逆变与协变,逆变与协变只会发生在接口和委托的泛型调用上,在06那节已经分析的挺透彻了,建议翻看。这里做一个结论
为了解决接口与委托的泛型产生的父子关系的引用类型的泛型转换问题:
协变:指定泛型为out,说明编译是允许传递泛型为当前泛型与当前泛型的子类但是是指定了泛型为out后,说明泛型只能 作为返回值,不能作为参数
逆变:指定泛型为in(泛型前面加in),指定后,泛型只能做参数,不能做返回值。接口指定的泛型,实现可以是该泛型或该泛型的父类
泛型缓存
众所周知:
泛型类型:一个泛型类,在指定不同的参数类型的时候,其实是会编译成不同的类。
静态类型与静态构造函数:一个类型只能初始化一次,并且常驻内存。
当这两个结合的时候,就会产生泛型缓存,在泛型类中使用静态字段,那么相同泛型类型的字段是会缓存在内存中,不同的参数类型的字段互补干扰,相互独立。
所以说在泛型类中使用静态类型,就有一种缓存的概念。但是这种缓存性能
特别的高,但是清除不了。
class Student<T> : BaseStudent
{
// 当传递过来的T不同时,会在内存中缓存多个不同的参数类型的MyProperty字段,当相同时,就会返回第一次的MyProperty字段。
public static T MyProperty { get; set; }
}