首页 学海无涯 C#基础 C#高级编程之泛型(一):初识泛型
C#高级编程之泛型(一):初识泛型
摘要 本文介绍C#高级编程之泛型,泛型简介,泛型原理、作用,泛型的使用,以及泛型约束的了解。

问题背景

下面3个方法的分别在控制台打印int、string、DateTime类型的参数值:

void ShowInt(int val) { Console.WriteLine(val); }
void ShowString(string val) { Console.WriteLine(val); }
void ShowDateTime(DateTime val) { Console.WriteLine(val); }

如果还想打印long、char那么又得写两个方法,很明显有点费事。

解决方法1(object):

void Show(object val) { Console.WriteLine(val); }

以object作为参数的类型,不管是打印int还是string,都可以通过这一个方法实现:

Show(1);
Show("1");
Show(DateTime.Now);

这就达到了一个方法满足多种类型需求的作用。

但是,相比之前那三个方法,以object类型作为参数,在调用方法时会产生一个装箱和拆箱的过程,势必会影响性能,且存在类型安全问题。

解决方法2(泛型):

void Show<T>(T val) { Console.WriteLine(val); }

在编写Show方法时,可以使用T作为占位符,先不指定T的具体类型,等到调用的时候再指定具体类型。如:

Show<int>(1);

同样当我们想打印string类型的时候:

Show<string>("1");

同时泛型方法还可以从参数类型来推导具体的类型参数,所以就可以简化调用: 

Show(1);
Show("1");

这时候T分别是int和string类型。

这样同样达到了以object作为参数类型时的效果,同时它又不会发生装箱和拆箱的操作,所以性能方面也不会有损失,也不会有类型安全问题,真正达到了事半功倍的效果。

泛型概述

泛型是.NET Framework 2.0引入的新特性,在设计类或方法时,引入类型参数, 可以使一个类或方法满足不同类型需求,而不会产生运行时装箱和拆箱操作的成本和风险。

泛型类或方法声明时不需要确定具体的类型,调用时才需要指定具体的类型(延迟声明)。

泛型兼具可重用性、类型安全性和效率,可以用于类、方法、接口、事件、委托等。

可以对泛型进行约束以缩小泛型类型的范围。

泛型原理

以泛型类举例,泛型类在编译的之后,泛型参数会生成占位符,最终会根据调用来生成不同的类。泛型类不只是一个不确定类型的类,而是每个类型都会生成一个类,也就是说虽然代码中只写了一个类,但根据调用的不同,会生成很多类的实例。

泛型使用

在声明泛型类、接口、方法时,带上一个“<>”(尖括号),中间使用占位符如“<T>”,T是类型参数,调用的时候指定的具体类型。具体可以看上文解决方法2(泛型)

泛型约束

前面说了使用object的解决方案会引起类型安全问题,比如:

/// <summary>
/// 有钱人
/// </summary>
public class RichPeople
{
    /// <summary>
    /// 名字
    /// </summary>
    public string Name { get; set; }
}

public class MHY : RichPeople { }
public class MT : RichPeople { }
public class Me { }

public static class GenericTest
{
    //显示名字
    public static void ShowName(object people)
    {
        //var richPeople = people as RichPeople;  //无法转换时为null 编译器不报错
        var richPeople = (RichPeople)people;  //无法转换时编译器报错

        Console.WriteLine(richPeople.Name);
    }
}

//调用
GenericTest.ShowName(new MT() { Name = "马腾" });
GenericTest.ShowName(new MHY() { Name = "马化云" });
GenericTest.ShowName(new Me());   //报错

上述代码编译器是可以通过的,定义一个方法ShowName(object people),使用object参数传入一个对象,方法内部打印参数的Name属性,而只有RichPeople有Name属性,所以需要将传入的参数强制转换为RichPeople类。当调用ShowName方法传入MT和MHY对象时,可以顺利显示名称,因为MT和MHY是继承自RichPeople。但是当传入Me对象时,不管是哪种强转方式都会报错。

当调用方传入了其他对象时,程序就会抛出异常,这就是object造成的类型不安全,因为object可以接收任何类型的参数。

PS:上述两种强转方式,编译器都不会报错!

而如果用泛型方法,也同样存在这样的问题:

public static void ShowName<T>(T people)
{
    var richPeople = people as RichPeople;  //无法转换时为null 编译器不报错

    Console.WriteLine(richPeople.Name);
}

PS:有个小区别,因为泛型类型T是具体的类型,所以无法通过(RichPeople)people的方式进行强制转换,这种方式进行转换时绕不过编译器。

为了解决这个问题,泛型约束诞生了。

什么是泛型约束?

泛型约束就是将泛型类型T进行一系列的约束,使泛型类型只能是我们想要的类型。

上诉问题的根本原因就是,能够显示名字的只有RichPeople类,而我们传入了非RichPeople对象,所以我们需要让调用方只能传入RichPeople对象。

将泛型类型T加一个RichPeople类约束

public static void ShowName<T>(T people) where T : RichPeople
{
    Console.WriteLine(people.Name);
}

因为指定了T类型参数必须是RichPeople或RichPeople的派生类,那么T类型必有Name属性,所以我们直接用people对象的Name属性,编译器也不会报错。

而如果不添加RichPeople泛型约束,那会是这样:


编译器报错了,没有加泛型约束,T可以是任何类,所以不一定有Name属性,当然不能这么用。

当添加RichPeople类约束后,T类型只能是RichPeple类或其派生类对象,传入其他对象时,编译器报错:


因此,调用该方法时只能传入RichePeople或其子类,这样就不会出现类型安全问题

结语

本文标题既是初识泛型,那就不讲太多,到此为止!后续详细介绍几种泛型约束以及泛型的协变逆变


版权声明:本文由不落阁原创出品,转载请注明出处!

本文链接:http://www.leo96.com/article/detail/53

广告位

来说两句吧
最新评论
  • 不想知道!
    不想知道!

    不愧是C# 2.0劳动人名智慧的结晶