撸·泛型
因为下面这张图片,逼自己再看一遍泛型,并做下记录,加深印象。翻看.net core源码,发现泛型已经使用的到处都是,所以掌握泛型是成为最基础的知识了。
![](https://img2018.cnblogs.com/blog/710776/201902/710776-20190218181940499-616182179.png)
前奏
> 泛型 * 引入时间:.net framework 2.0 * 引入目的:为了解决装箱、拆箱带来的性能损失。 * 安全效果: 1. 装箱拆箱有一个安全转换问题,所以又解决了类型安全问题。 2. 在父子类或者接口继承的时候,泛型很好的起到了安全约束的作用,如第一张图片。 * 阳光普照效果: 节约代码量,提高重复利用(object也可以实现,但是有性能损失)。泛型的协变和逆变
- 引入时间:.net framework4.0
- 引入目的:为了解决泛型父子类型的转换问题。
- 使用规则:
- 只能放在接口或者委托的泛型参数前面。
- out 协变covariant,用来修饰返回值,儿子可以赋值给老父亲。
- in:逆变contravariant,用来修饰传入参数,老父亲可以赋值给儿子。
正文
### 泛型方法 定义:泛型方法是通过类型参数声明的方法。 在下面的代码中,Method方法名称后面必须紧跟类型占位符T,如果没有,编译器会将方法参数类型T默认识别为正常类型,而不是占位符,所以会报错的。正确样例如下: ``` public class Generic { ///同理,如果返回类型是泛型的话,Method方法名称后面必须紧跟类型占位符T,如果没有,编译器会将方法参数类型T默认识别为正常类型,而不是占位符,所以会报错的。正确样例如下:
public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters)
{
throw null;
}
泛型类
定义:泛型类封装不特定于特定数据类型的操作。 泛型类最常见用法是用于链接列表、哈希表、堆栈、队列和树等集合。 无论存储数据的类型如何,添加项和从集合删除项等操作的执行方式基本相同。
通常,创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。样例如下:
public class Generic<T>
{
public T t;
}
泛型接口
跟泛型类定义一样,只要将class改为interface。样例如下:
public interface Generic<T>
{
T GetT(T t);
}
泛型委托
跟方法一样,同样是约束方法参数类型。样例如下:
public delegate void SayHi<T>(T t);
default 关键字
在泛型类和泛型方法中会出现的一个问题是,如何把缺省值赋给参数化类型,此时无法预先知道以下两点: 1. T将是值类型还是引用类型 2. 如果T是值类型,那么T将是数值还是结构对于一个参数化类型T的变量t,仅当T是引用类型时,t = null语句才是合法的; t = 0只对数值的有效,而对结构则不行。这个问题的解决办法是用default关键字,它对引用类型返回空,对值类型的数值型返回零。而对于结构,它将返回结构每个成员,并根据成员是值类型还是引用类型,返回零或空。如下所示:
public class MyList<T>
{
//...
public T GetNext()
{
T temp = default(T);
if (current != null)
{
temp = current.Data;
current = current.Next;
}
return temp;
}
}
使用注意点(重点)
### 注意点1 > 泛型在声明的时候可以不指定具体的类型,但是在使用的时候必须指定具体类型; 如果子类也是泛型的,那么继承的时候可以不指定具体类型 ``` namespace MyGeneric { ////// <summary>
/// 子类也是泛型的,继承的时候可以不指定具体类型
/// </summary>
/// <typeparam name="T"></typeparam>
public class CommonClassChild<T>:GenericClass<T>
{
}
}
### 注意点2
> 类实现泛型接口也是这种情况
namespace MyGeneric
{
///
/// 必须指定具体类型
///
public class Common : IGenericInterface
{
public string GetT(string t)
{
throw new NotImplementedException();
}
}
/// <summary>
/// 可以不知道具体类型,但是子类也必须是泛型的
/// </summary>
/// <typeparam name="T"></typeparam>
public class CommonChild<T> : IGenericInterface<T>
{
public T GetT(T t)
{
throw new NotImplementedException();
}
}
}
<h2 style="background-color:#226DDD;width:100%;color:#FFF;text-indent:1em">泛型约束</h2>
  为什么要泛型约束,其主要问题还是解决安全问题,规范开发人员写代码的规范性,避免一些在运行时期才能检查到的错误。比如下面的代码,在编译器是不会报错的,但是在运行期会出现转换异常。
public class 动物
{
}
public class 狗 :动物
{
}
public class 猫
{
}
public class Generic
{
public void method(object cat)
{
动物 animal= (动物)cat;
}
}
  所谓的泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。那么怎么给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。
序号|约束 | 说明
:--- |:---:| ---
1 | T:struct | 类型参数必须是值类型
2 | T:class | 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。
3 | T:new() | 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。
4 | T:基类名 | 类型参数必须是指定的基类或派生自指定的基类。
5 | T:接口名称 | 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。
6 | T:基类名,接口名称,new() | 泛型约束也可以同时约束多个,但是new()必须放在最后
<h2 style="background-color:#226DDD;width:100%;color:#FFF;text-indent:1em">协变和逆变</h2>
在OO的世界里,可以安全地把子类的引用赋给父类引用。但是在T的世界里,就不一定了。有的能变,有的不能变,先了解以下几点:
* 以前的泛型系统(或者说没有in/out关键字时),是不能“变”的,无论是“逆”还是“顺(协)”。
* 当前仅支持接口和委托的逆变与协变 ,不支持类和方法。但数组也有协变性。
* 值类型不参与逆变与协变。
如果不能理解以上几句话,就先看下面的知识点
### 协变
  所谓协变,就是为了解决子类泛型接口或委托能回到父类泛型接口或委托上来。(Foo<父类> = Foo<子类> )
//泛型委托:
public delegate T MyFuncA
public delegate T MyFuncB
MyFuncA