语法基础

第十七章 泛型(generic)

2018-08-21  本文已影响2人  向着远方奔跑

17.1 什么是泛型

专门为多段代码在不同的数据类型上执行相同指令的情况专门设计

17.2 C#中的泛型

泛型特性提供了一种更优雅的方式,可以让多个类型共享一组代码。泛型允许我们声明类型参数化的代码,可以用不同的类型进行实例化。即可以用 类型占位符 来写代码,然后在创建类的实例时指明真实的类型。


C# 提供了5种泛型:

前4个是类型,而方法是成员


17.3 泛型类

创建和使用常规的、非泛型的类需两步:声明类和创建类的实例
泛型类不是实际的类,而是模板,须先从他们构建实际的类类型,然后创建这个构建后的类类型的实例

17.4 声明泛型类

声明一个简单的泛型类和声明普通类差不多,区别如下

如下代码声明了一个SomeClass的泛型类。类型参数列在尖括号中,然后当作真实类型在声明的主体中使用

                 类型参数
                    ↓
class SomeClass < T1, T2 >
{          通常在这些位置使用类型
            ↓                ↓
    public T1 SomeVar = new T1();
    public T2 SomeVar = new T2();
}

在泛型类型声明中并没有特殊的关键字。取而代之的是尖括号中的类型参数列表,它可以区分泛型类与普通类的声明

17.5 创建构造类型

创建了编译类型,需要告诉编译器能使用哪些真实类型来替代占位符(类型参数)。编译器获取这些真实类型并创建构造类型(用来创建真实类对象的模板)。

创建构造类型的语法如下,包括列出类名并在尖括号中提供真实类型来替代类型参数。要替代类型参数的真实类型叫做 类型实参

编译器接受了类型实参并且替换泛型类主体中的相应类型参数,产生了构造类型——从它创建真实类型的实例


类型参数和类型实参的区别:

17.6 创建变量和实例

在创建引用和实例方面,构造类类型的使用和常规类型差不多。如下代码演示了两个类对象的创建。


和非泛型类一样,引用和实例可以分开创建,下图可以看出内存中出现的情况与非泛型类是一样的。

可以从同一个泛型类型构建出很多不同的类类型。每一个都有独立的类类型,就好像它们都有独立的非泛型类声明一样。

下面代码是从 SomeClass泛型类创建了两个类型

17.7 类型参数的约束

只要代码不访问它处理的一些类型的对象(或者只要它始终是 object 类型的成员,包括ToString、Equal、GetType等),泛型类就可以处理任何类型。符合约束的类型参数叫做 未绑定的类型参数。然后,如果代码尝试使用其他成员,编译器会产生一个错误信息。

如下代码声明了一个 Simple 的类,有一个 LessThan 的方法,接受两个泛型类型的变量。LessThan尝试用小于运算符返回结果。但由于不是所有的类都实现了小于运算符,也就不能用任何类来代替T,所以编译器会产生一个错误信息。

class Simple<T>
{
    static public bool LessThan(T i1, T i2)
    {
        return i1 < i2;     //错误:运算符“<”无法用于“T”和“T”类型的操作符
    }
}

要让泛型变得更有用,我们需要提供额外的信息让编译器知道参数可以接受哪些类型。这些额外的信息叫做 约束(constrain)。只有符合约束的类型才能替代给定的类型参数,来产生构造类型。

17.7.1 Where子句

约束使用Where子句列出。

where子句的语法如下:

        类型参数           约束列表
          ↓                  ↓
where TypeParam : constraint, constraint, ...
  ↑             ↑
关键字          冒号

如下泛型类有3个类型参数。T1是未绑定,对于T2,只有 Customer 类型或从 Customer 继承的类型的类才能用作类型实参,而对于T3,只有实现 IComparable 接口的类才能用于类型实参。

             未绑定  具有约束
                ↓     ↓            没有分隔符
class MyClass < T1, T2, T3 >           ↓
                   where T2 : Customer         //T2的约束
                   where T3 : IComparable      //T3的约束
{                                         ↑
     ...                              没有分隔符
}
17.7.2 约束类型和次序

共有5中类型的约束

约束类型 描述
类名 只有这个类型的类或从它继承的类才能用作类型实参
class 任何引用类型,包括类、数组、委托和接口都可用作类型实参
struct 任何值类型都可以用作类型实参
接口名 只有这个接口或实现这个接口的类型才能用作类型实参
new() 任何带有 无参公共构造函数的类型都可作类型实参。叫构造函数约束

where 子句可以以任何次序列出,然而,where 子句中的约束必须有特定的顺序

如下声明给出一个where子句的示例:

17.8 泛型方法

与其他泛型不一样,方法是成员,不是类型。

泛型方法可以在泛型和非泛型的 类、结构和接口 中声明。

17.8.1 声明泛型方法

泛型方法具有类型参数和可选的约束。

                  类型参数列表             约束子句
                       ↓                      ↓
public void PrintData<S, T> (S p, T t) where S : Person
                                 ↑
                             方法参数列表

注:类型参数列表在方法名称之后,在方法参数列表之前


17.8.2 调用泛型方法

要调用泛型方法,应该在方法调用时提供类型实参:

MyMethod< short, int >();
MyMethod< int, long >();

下图演示了一个 DoStuff 的泛型方法的声明,接受两个类型参数。

推断类型

如果为方法传入参数,编译器有时可以从方法参数中推断出泛型方法的类型形参中用到的那些类型。这样可以使方法调用更简单,可读性更强。

下面代码声明了 MyMethod,它接受了一个与类型参数同类型的方法参数。

public void MyMethod <T> (T myVal) { ... }
                      ↑   ↑
                   两个都是T类型

如下所示,如果我们使用 int 类型的变量调用 MyMethod,方法调用中的类型参数的信息就多余了,因为编译器可以从方法参数中得知它是 int。

int myInt = 5;
MyMethod <int> (MyInt);
           ↑      ↑
          两个都是int

由于编译器可以从方法参数中推断类型参数,可省略类型参数和调用中的尖括号:
MyMethod(myInt)

17.12 泛型接口

泛型接口允许我们编写参数和接口成员返回类型是泛型类型参数的接口。泛型接口的声明和非泛型接口的声明差不多,但需要在接口名称之后的尖括号中放置类型参数。
下例代码声明了叫做IMyIfc的泛型接口:

    interface IMyIfc<T>       //泛型接口
    {
        T ReturnIt(T inValue);
    }

    class Simple<S> : IMyIfc<S>     //泛型类
    {
        public S ReturnIt(S inValue) { return inValue; }  //实现泛型接口
    }

    class Program
    {
        static void Main(string[] args)
        {
            var trivInt = new Simple<int>();
            var trivString = new Simple<string>();

            Console.WriteLine("{0}", trivInt.ReturnIt(5));
            Console.WriteLine("{0}", trivString.ReturnIt("Hi there"));
        }
    }

输出如下:
5
Hi there


17.12.1 使用泛型接口的示例

下例演示了泛型接口的两个额外的能力:

下例中 Simple 是实现泛型接口的非泛型类。它实现了两个 IMyIfc的实例。一个实例使用 int 类型实例化,一个使用 string 类型实例化。

    interface IMyIfc<T>        //泛型接口
    {
        T ReturnIt(T inValue);
    }

    class Simple : IMyIfc<int>, IMyIfc<string>        //非泛型类
    {
        public int ReturnIt(int inValue) { return inValue; }  //实现 int 类型接口
        public string ReturnIt(string inValue) { return inValue; }  //实现 string 类型接口
    }

    class Program
    {
        static void Main(string[] args)
        {
            Simple trivial = new Simple();

            Console.WriteLine("{0}", trivial.ReturnIt(5));
            Console.WriteLine("{0}", trivial.ReturnIt("Hi there"));
        }
    }

输出结果与上例一致。

上一篇下一篇

猜你喜欢

热点阅读