zoukankan      html  css  js  c++  java
  • 解读经典《C#高级编程》泛型 页114-122.章4

    前言


    本章节开始讲解泛型。.Net从2.0开始支持泛型,泛型不仅是C#的一部分,也与IL代码紧密集成。所以C#中泛型的实现非常优雅。相对于C#,Java是后期引入的泛型,受限于最初的设计架构,就实现的比较别扭,遭到不少人的吐槽,比如“类型擦除”问题。使用C#还是幸福的。
    使用泛型最典型的应用,可能是List<T>了,从List<T>我们可以看到,使用泛型最明显的优点是:

    • 将List的功能和包含类T的功能分开,功能各自实现。使得代码内聚性、复用性、灵活性都更好。
    • 使用强类型的T代替了原先的Object,不再需要类型强转,使用强类型更加安全便捷。

    名词约定
    对于Lis<T>定义: List<T>叫做“泛型类”,而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<int>和List<string>创造出两个临时的新类。我们可以认为,类似C++模板的工作,被隐藏到.Net框架底层去处理了,机器处理的代码可能没少,但最终程序员写的代码是减少了。

    命名约定

    这里,少见的匈牙利命名法又出现了。前面我们讲到,接口命名采用是匈牙利命名法。泛型类型也是。

    • 泛型类型名称以字母T作为前缀
    • 泛型类型可以是任意类型,如果泛型类中只定义一个类型,可以直接命名为T,如List<T>
    • 如果有多个泛型类型,或者T不足以表达含义,可以使用T开头的命名,比如:SortedList<TKey, TValue>

    创建泛型类

    泛型类的创建,实际应用场景其实不多。如果你写的是通用类库,那可能会比较常用。但如果是应用层面的代码,实际上很少会需要你自己去建立泛型类。我应用在多个产品的基础框架里,似乎也只建立过一个泛型类,而且使用很少,是非常边缘化的一个功能。为什么会这样?还是因为.Net框架已经将常用的泛型类封装的很好了,拿来用就足够应付99%的应用场景。
    也因为如此,可能不少人对为什么要创建泛型类,以及如何创建一个泛型类,并不是很了解。
    首先我从原理上说明一下,实际上泛型类可以理解为:支持多种泛型类型的“包装类”。包装类是Java的概念,比如从int -> Integer,Integer就是int的包装类。其实相应的,在C#中,int?也可以认为是int的包装类,但C#不叫包装类,它刚好是个泛型,int? 就是 Nullable<int>泛型类。
    但我们仍然可以做一个大脑体操,我们引入一下包装类的概念,然后推理一下:程序员定义了一个泛型类,它在CLR执行时的执行原理是怎样的?以int?为例:

    1. 首先JIT编译器根据泛型类Nullable<int>,生成一个临时命名的新类,它包装了值类型int,是个“包装类”
    2. 执行包装类的具体功能方法,输出结果

    实际上JIT编译器生成的“包装类”代码是怎么样的呢?它的样子,我手写模仿了一下(部分实现):

    /// <summary>
    /// 允许Null的Int类型
    /// </summary>
    public class NullInt
    {
        private int value;
        private bool hasValue;
    
        /// <summary>
        /// NullInt的Int值
        /// </summary>
        public int Value
        {
            get
            {
                return value;
            }
        }
    
        /// <summary>
        /// 输出
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if (!hasValue) return null;
            return value.ToString();
        }
    
        /// <summary>
        /// 赋值操作符
        /// </summary>
        /// <param name="value"></param>
        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<T>的裁剪版本,以下是Nullable<T>的完整定义:
    图片

    现在int类型的包装类已经实现了,那我还想实现long的包装类,想实现decimal的包装类,怎么办?写n多个类?显然有点啰嗦了。
    .Net泛型类就为提供这种能力而创造的。
    我们如果反编译Nullable<T>的源代码,我们会发现实现结构和我手写的包装类是类似的,只是用T代替了int:
    图片

    最后,在贴一个我的产品框架中建立的泛型类实例(节选),它的实现的具体细节,在后面章节也会有分析:

    /// <summary>
    /// 单一事务处理服务,用于单表的数据读写事务
    /// </summary>
    /// <typeparam name="TViewModel"></typeparam>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TDbContext"></typeparam>
    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;
    
        /// <summary>
        /// 构造方法
        /// </summary>
        public EFRepository()
        {
            dbContext = new TDbContext();
            dbSet = dbContext.Set<TEntity>();
        }
    
        /// <summary>
        /// 根据主键获取单条数据
        /// </summary>
        /// <param name="keyValues"></param>
        /// <returns></returns>
        public TViewModel Get(params object[] keyValues)
        {
            return dbSet.Find(keyValues)
                .MapTo<TEntity, TViewModel>();
        }
    
        /// <summary>
        /// 新增单条数据
        /// </summary>
        /// <param name="model"></param>
        public void Add(TViewModel model)
        {
            var entity = model.MapTo<TViewModel, TEntity>();
            dbSet.Add(entity);
            dbContext.SaveChanges();
        }
    
        /// <summary>
        /// 根据主键删除单条数据
        /// </summary>
        /// <param name="keyValues"></param>
        public void Delete(params object[] keyValues)
        {
            TEntity entity = dbSet.Find(keyValues);
            dbSet.Remove(entity);
            dbContext.SaveChanges();
        }
    }
    

    下一篇,我们继续讲泛型的使用细节。


    觉得文章有意义的话,请动动手指,分享给朋友一起来共同学习进步。
    欢迎关注本人如下公众号 “产品技术知与行” ,打造全面的结构化知识库,包括原创文章、免费课程(C#,Java,Js)、技术专题、视野知识、源码下载等内容。

    微信公众号
    扫描二维码关注

    回到目录,再看看相关文章

  • 相关阅读:
    13,发布CRM
    12,nginx+uWSGI+django+virtualenv+supervisor发布web服务器
    11.2,nginx负载均衡实验
    11.1,nginx集群概念
    11,nginx入门与实战
    10,python开发之virtualenv与virtualenvwrapper
    9.5web service基础知识
    9.4python开发之virtualenv与virtualenvwrapper
    9.3centos7安装python3 以及tab补全功能
    Google 浏览器被劫持怎么办?
  • 原文地址:https://www.cnblogs.com/holyknight17/p/10451897.html
Copyright © 2011-2022 走看看