本章节开始讲解泛型。.Net从2.0开始支持泛型,泛型不仅是C#的一部分,也与IL代码紧密集成。所以C#中泛型的实现非常优雅。相对于C#,Java是后期引入的泛型,受限于最初的设计架构,就实现的比较别扭,遭到不少人的吐槽,比如“类型擦除”问题。使用C#还是幸福的。
使用泛型最典型的应用,可能是List
名词约定
对于Lis定义: List 叫做“泛型类”,而T叫做“泛型类型”。泛型类型和泛型类后面会经常提到。
泛型和C++的模板类似,不同之处在于C++实例化模板时,需要模板的源代码。而泛型是一种内化在CLR中的结构,可以认为它是CLR封装好的一种“类型”,使用起来更加简单安全。以下逐个讲述使用泛型的优点以及它所解决的问题。
性能
泛型性能的优势体现在装箱和拆箱上。在泛型出现之前,要填充一个列表使用的是非泛型集合如ArrayList。ArrayList存储的是Object对象,因此将值类型存储到ArrayList时需要经过装箱的操作,数据取出处理时又需要拆箱的操作。在遍历操作时,性能损耗是比较明显的。
名词解释
装箱:值类型转换为引用类型
拆箱:将引用类型转换为值类型
ArrayList list = new ArrayList();
list.Add(10); //装箱
int value = (int)list[0]; //拆箱
List<int> listg = new List<int>();
listg.Add(10); //没有装箱
类型安全
ArrayList中存储的是Object,什么类型都可以添加,那么拆箱时如果出现类型不符就会导致程序异常。这样一来,代码的准确性就只能完全靠程序员的能力和责任了。
ArrayList list2 = new ArrayList();
list2.Add(10); //列表实际定义为int列表
list2.Add("11"); //不小心加入了字符串类型的数字
foreach(int item in list2)
{
Console.WriteLine(item); //遍历时将出现异常
}
二进制代码重用 & 代码的扩展
前面也已经讲到,泛型是内化在CLR中的,也就是说泛型类型中可重用的部分已经内化在.Net框架中了,不需要编程人员根据不同的泛型类型去写代码实现多种不同的类。
实际上,假如我们程序员自己写代码来实现同样强类型列表功能,但不使用泛型,就要用不同的类型实现不同的包装处理类,比如写一个包含int的处理类,以及一个包含string的处理类的代码。而.Net提供了List解决了问题,但实际上代码量是少了吗?并不一定。因为本质上,JIT编译器会根据List
命名约定
这里,少见的匈牙利命名法又出现了。前面我们讲到,接口命名采用是匈牙利命名法。泛型类型也是。
泛型类的创建,实际应用场景其实不多。如果你写的是通用类库,那可能会比较常用。但如果是应用层面的代码,实际上很少会需要你自己去建立泛型类。我应用在多个产品的基础框架里,似乎也只建立过一个泛型类,而且使用很少,是非常边缘化的一个功能。为什么会这样?还是因为.Net框架已经将常用的泛型类封装的很好了,拿来用就足够应付99%的应用场景。
也因为如此,可能不少人对为什么要创建泛型类,以及如何创建一个泛型类,并不是很了解。
首先我从原理上说明一下,实际上泛型类可以理解为:支持多种泛型类型的“包装类”。包装类是Java的概念,比如从int -> Integer,Integer就是int的包装类。其实相应的,在C#中,int?也可以认为是int的包装类,但C#不叫包装类,它刚好是个泛型,int? 就是 Nullable
但我们仍然可以做一个大脑体操,我们引入一下包装类的概念,然后推理一下:程序员定义了一个泛型类,它在CLR执行时的执行原理是怎样的?以int?为例:
实际上JIT编译器生成的“包装类”代码是怎么样的呢?它的样子,我手写模仿了一下(部分实现):
///
/// 允许Null的Int类型
///
public class NullInt
{
private int value;
private bool hasValue;
///
/// NullInt的Int值
///
public int Value
{
get
{
return value;
}
}
///
/// 输出
///
///
public override string ToString()
{
if (!hasValue) return null;
return value.ToString();
}
///
/// 赋值操作符
///
///
public static implicit operator NullInt(int value)
{
return new NullInt
{
value = value,
hasValue = true
};
}
}
Main()方法:测试输出
NullInt age = 10; //赋值
Console.WriteLine(age.Value); //输出10
Console.WriteLine(age); //输出10
只实现了部分代码,它实际上是Nullable
现在int类型的包装类已经实现了,那我还想实现long的包装类,想实现decimal的包装类,怎么办?写n多个类?显然有点啰嗦了。
.Net泛型类就为提供这种能力而创造的。
我们如果反编译Nullable
最后,在贴一个我的产品框架中建立的泛型类实例(节选),它的实现的具体细节,在后面章节也会有分析:
///
/// 单一事务处理服务,用于单表的数据读写事务
///
///
///
///
public class EFRepository<TViewModel, TEntity, TDbContext> : IDisposable
where TEntity : class,new()
where TViewModel : class,new()
where TDbContext : DbContext,new()
{
private DbContext dbContext;
private DbSet<TEntity> dbSet;
///
/// 构造方法
///
public EFRepository()
{
dbContext = new TDbContext();
dbSet = dbContext.Set<TEntity>();
}
///
/// 根据主键获取单条数据
///
///
///
public TViewModel Get(params object[] keyValues)
{
return dbSet.Find(keyValues)
.MapTo<TEntity, TViewModel>();
}
///
/// 新增单条数据
///
///
public void Add(TViewModel model)
{
var entity = model.MapTo<TViewModel, TEntity>();
dbSet.Add(entity);
dbContext.SaveChanges();
}
///
/// 根据主键删除单条数据
///
///
public void Delete(params object[] keyValues)
{
TEntity entity = dbSet.Find(keyValues);
dbSet.Remove(entity);
dbContext.SaveChanges();
}
}
下一篇,我们继续讲泛型的使用细节。
扫描二维码关注
回到目录,再看看相关文章