C#中的泛型

一、没有泛型之前

在没有泛型之前,我们是怎么处理不同类型的相同操作的:

示例1
 //下面是一个处理string类型的集合类型
 public class MyStringList
    {
        string[] _list;
        public void Add(string x)
        {
            //将x添加到_list中,省略实现
        }
        public string this[int index]
        {
            get { return _list[index]; }
        }
    }
 //调用
 MyStringList myStringList = new MyStringList();
 myStringList.Add("abc");
 var str = myStringList[0];
示例2
    //如果我们需要处理int类型就需要复制粘贴然后把string类型替换为int类型:
    public class MyIntList
    {
        int [] _list;
        public void Add(int x)
        {
            //将x添加到_list中,省略实现
        }
        public int this[int index]
        {
            get { return _list[index]; }
        }
    }
   //调用
    MyIntList myIntList = new MyIntList();
    myIntList.Add(100);
    var num = myIntList[0];

可以看得出我们的代码大部分是重复的,而作为有追求的程序员是不允许发生这样的事情的。
于是乎,我们做了如下改变:

示例3
 public class MyObjList
    {
        object[] _list;
        public void Add(object x)
        {
            //将x添加到_list中,省略实现
        }
        public object this[int index]
        {
            get { return _list[index]; }
        }
    }
 //调用
 MyObjList myObjList = new MyObjList();
myObjList.Add(100);
 var num = (int)myObjList[0];

从上面这三段代码中,我们可以看出一些问题:

  1. int和string集合类型的代码大量重复(维护难度大)。
  2. object集合类型发生了装箱和拆箱(损耗性能)。
  3. object集合类型是存在安全隐患的(类型不安全)。

问题1,虽然代码重复但是没有装箱、拆箱而且类型是安全的
问题2,发生了装箱和拆箱,是损耗性能影响执行效率的。
问题3,如果add的类型不是int类型,在编译器是不会检查出来的(编译通过),运行期就会报错,MyObjList类似于我们熟知的ArrayList

运行期报错

现在,我们必须解决如下问题
1、避免代码重复
2、避免装箱和拆箱
3、保证类型安全

范型为我们提供了完美的解决方案

二、什么是泛型

如果你理解类是对象的模板(类是具有相同属性和行为的对象的抽象),那么泛型就很好理解了。
泛型:generic paradigm(通用的范式),generic这个单词也很好的说明了模板这个概念:通用的,标准的。
泛型是类型的模板
不同的是:作为模板的类是通过实例化产生不同的对象,而泛型是通过不同的类型实参产生不同的类型
泛型的基本概念介绍完,我们来看看泛型到底是怎么帮我们解决问题的

如何解决代码重复:提取代码相同的部分,封装变换的部分——封装变化,而示例1和示例2中变换的部分就是int和string类型本身,如何将类型抽象呢

示例4
    //将示例3改装下
    public class MyList<T>
    {
        T [] _list;
        public void Add(T x)
        {
            //将x添加到_list中,省略实现
        }
        public T this[int index]
        {
            get { return _list[index]; }
        }
    }

类型参数 T
类型参数可以理解为泛型的"形参"("形参"一般用来形容方法的),有“形参”就会有实参。如我们声明的List<string>,string就是实参;List<int> ,int就是实参,而List<string>和List<int>是两种不同的类型。

不同的类型

通过类型参数解决了代码重复的问题

如何解决装箱、拆箱以及类型安全的问题:

 //示例5
       List<int> list = new List<int>();
       list.Add(100);//强类型无需装箱
       //list.Add("ABC"); 编译期安全检查报错
       int num = list[0];//无需拆箱   
编译期安全检查报错

声明泛型类型时,因为确定了类型实参,所以操作泛型类型不需要装箱、拆箱,而且泛型将大量安全检查从运行时转移到了编译时进行,保证了类型安全。
注:C#为我们提供了5种泛型:类、结构、接口、委托和方法。

在示例4中,自定义泛型集合只是添加和获取类型参数的实例,除此之外,没有对类型参数实例的成员做任何操作。C#的所有类型都继承自Object类型,也就是说,我们目前只能操作Object中的成员(Equals,GetType,ToString等)。但是,我自定义的泛型很多时候是需要操作类型更多的成员

新需求,打印员工的信息

示例6
    public class Person
    {
        public string Name { get; set; }
        public int Age{ get; set; }
    }
    public class Employee : Person {  }
    public class PrintEmployeeInfo<T>
    {
        public void Print(T t)
        {
            Console.WriteLine(t.Name);//报错
        }
    }
示例6:T未包含“Name”的定义

如果我们可以将类型参数T限定为Person类型,那么在泛型内部就可以操作Person类型的成员了。

三、泛型的约束

表格来至微软官方文档

约束 描述
where T:结构 类型参数必须是值类型。 可以指定除 Nullable 以外的任何值类型。
where T:类 类型参数必须是引用类型;这同样适用于所有类、接口、委托或数组类型。
where T:new() 类型参数必须具有公共无参数构造函数。 与其他约束一起使用时,new() 约束必须最后指定。
where T:<基类名称> 类型参数必须是指定的基类或派生自指定的基类。
where T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。 可指定多个接口约束。 约束接口也可以是泛型。
where T:U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。
示例7
 public class PrintEmployeeInfo<T> where T:Person
    {
        public void Print(T t)
        {
            Console.WriteLine(t.Name);
        }
    }

四、协变和逆变很简单

有一定工作经验的开发人员一定遇到过下面这样的情况:

示例8
 List<Employee> list = new List<Employee>();
 list.Add(new Employee() { Age = 20, Name = "小明" });
 IEnumerable<Person> perList;
 perList = list;
 foreach (var item in perList)
 {
     Console.WriteLine("名字:" + item.Name + ",年龄:" + item.Age);
 }

不是说,不同类型实参构造的泛型也是不同的吗,为啥可以将List<Employee>对象赋值给IEnumerable<Person>呢?
再看下面的示例

示例9
  public static void PrintEmployee(Person item)
  {
      Console.WriteLine("名字:" + item.Name + ",年龄:" + item.Age);
  }

  Action<Employee> empAction = PrintEmployee;
  empAction(new Employee() { Age = 20, Name = "小明" });

  Action<Person> perAction = PrintEmployee;
  perAction(new Employee() { Age = 20, Name = "小明" });

执行结果正常输出

正常输出

为什么可以将参数类型为Person的方法分别赋值给Action<Person>和Action<Employee>呢?
示例8说明了泛型的协变性,示例9说明了泛型的逆变性(听起来很唬人)
其实协变和逆变只要弄清楚两个概念一切就非常清晰了

  1. 类型参数分为输入参数(in)、输出参数(out)和不变参数(没有关键字)
  2. 设计原则:里氏替换原则——派生类(子类)对象能够替换其基类(超类)对象被使用

IEnumerable的定义
public interface IEnumerable<out T> : IEnumerable
示例8中IEnumerable<Person>输出参数类型需要的Person类型,而List类型参数给的是Employee(Employee继承了Person)——里氏替换原则
委托Action的定义
public delegate void Action<in T>(T obj);
示例9中方法PrintEmployee需要的参数类型是Person,而Action<Employee>输入类型参数是Employee(Employee继承了Person)——里氏替换原则
如果将PrintEmployee的参数类型变为Employee,示例9中其他代码不变,会怎样?

编译错误

清楚的错误信息

方法PrintEmployee需要的参数类型是Employee,而Action的输入参数是Person,显然Person不一定是Employee

注:in和out关键字只适用于接口和委托类型

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容