02011801 泛型01-什么是泛型、泛型类、构造类型、类型参数、where子句
1. 什么是泛型
- 我们可以把类的行为提取出来或重构,使之不仅能应用到它们编码的数据类型上,而且还能应用到其它类型上的话,类会更加有用。通过泛型可以实现这一点。
- 我们可以重构代码并且额外增加一个抽象层,这样对于某些代码来说,数据类型就不用硬编码了。
- 泛型是专门为多段代码在不同的数据类型上执行相同指令而设计的。
2. C#中的泛型
- 泛型(generic)允许我们声明类型参数化的代码,用不同的类型进行实例化。我们可以使用“类型占位符”来编写代码,然后在创建类的实例时指明真实的类型。
- 类型不是对象,而是对象的模版。
- 泛型也不是类型,而是类型的模版。
- C#提供了5种泛型。
- 类
- 结构
- 接口
- 委托
- 方法。注意,前面4种是类型,而方法是成员。
3. 泛型的引入
// 一个普通int类型栈示例
class MyStack
{int StackPointer = 0;int StackArray;public void Push(int x) {...}public int Pop() {...}
}
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 如果要将类型的栈变为float类型的栈,我们需要把类声明中相应的int类型改为float类型
class MyStack
{int StackPointer = 0;float StackArray;public void Push(float x) {...}public float Pop() {...}
}注意,这种方法虽然可行,但是有如下缺点:
1. 需要仔细检查类的每一个部分来看哪里声明需要修改,哪里声明需要保留。
2. 每次需要新类型(long,double...)的栈时,都需要重复这个过程。
3. 调试和维护这些相似的实现容易出错,并且重复工作量很大。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 通过泛型来实现
class MyStack<T>
{int StackPointer = 0;T[] StackArray;public void Push(T x) {...}public T Pop() {...}
}说明:
1. 在MyIntStack类定义中,使用类型占位符T而不是float来替换int。
2. 修改类名称为MyStack,并在类名后放置<T>。
4. 泛型类
- 创建和使用常规的、非泛型的类有两个步骤。
- 声明类和创建类的实例。
- 泛型类不是实际的类,而是类的模版,所以我们必须先从它们构建实际的类类型,然后创建这个类类型的引用和实例。使用泛型类需要三个步骤。
- 在某些类型上使用站位符来声明一个类,即声明泛型类型。
- 为占位符提供真实类型。这样就有了真实类的定义,填补了所谓的“空缺”,该类型称为构造类型。
- 创建构造类型的实例。
4.1 声明泛型类
// 声明泛型类的语法类型参数↓
class SomeClass <T1, T2>
{通常在这些位置使用泛型↓public T1 SomeVar;public T2 OtherVar;
}说明:声明一个简单的泛型类和声明普通类差不多,区别如下。
1. 在类名之后放置一组<>。
2. 在尖括号中使用逗号分隔符的占位符字符来表示需要提供的类型,这叫做类型参数。
3. 在泛型类声明的主体中使用类型参数来表示替代类型。
4. 在泛型类的声明中没有特殊的关键字,取而代之的是<>中的类型参数列表,它可以区分泛型类与普通类的声明。
4.2 创建构造类型
- 一旦声明了泛型类型,我们就需要告诉编译器能使用那些真实类型来替代占位符(类型参数)。编译器获取这些真实类型并创建构造类型(用来创建真实类对象的模版)。
// 创建构造类型的语法类型参数↓
SomeClass<short, int>说明:
1. 编译器接受类型实参并且替换泛型类主体中的相关类型参数,产生了构造类型。
2. 可以通过构造类型创建真实类型的实例。
4.3 类型参数和类型实参的区别
- 泛型类声明上的类型参数用作类型的占位符,如:T1,T2...。
- 在创建构造类型时提供的真实类型是类型实参。
类型参数↓
class SomeClass<T1, T2> {...} // 声明泛型类型类型实参↓
SomeClass<int, float> {...} // 创建构造类型
4.4 创建变量和实例
- 在创建变量和实例方法,构造类类型的使用和常规类型差不多。
MyNonGenClass myNGC = new MyNonGenClass(); // 常规类创建对象
SomeClass<short, int> = mySC1 = new SomeClass<short, int>(); // @1 构造类创建对象
var mySC2 = new SomeClass<short, int>(); // @2 构造类创建对象说明:
1. 在@1处,是SomeClass泛型类型对象的创建,使用short和int类型进行实例化。这种形式和常规类创建对象差不多,只不过把普通类型名改为构造类形式。
2. 在@2处,并没有在等号两边都列出构造类型,而是使用var关键字让编译器使用类型引用。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 引用实例分开创建
class SomeClass<T1, T2>
{public T1 SomeVar;public T2 OtherVar
}SomeClass<short, int> myInst; // @1 在栈中分配类变量
myInst = new SomeClass<short, int>; // @2 在堆中分配实例。说明:
1. 和非泛型类一样,泛型类的引用和实例可以分开创建。
2. 在@1声明的泛型类变量myInst分配了一个引用,值是null。
3. 在@2处等号左边分配实例,并将实例的引用赋值给变量myInst。
5. 泛型类和非泛型类比较
非泛型 | 泛型 | |
---|---|---|
源代码大小 | 更大,需要为每一种类型编写一个新的实现 | 更小,不管构造类型的数量有多少个,只需要一个实现 |
可执行文件大小 | 无论每一个版本的栈是否会被使用,都会在编译的版本中出现 | 可执行文件中只会出现有构造类型的类型 |
书写难易度 | 易于书写,因为它更加具体 | 比较难写,因为它更加抽象 |
维护的难易度 | 更容易出问题,因为所有修改需要应用到每一个可用的类型之上 | 易于维护,只为只需要修改一个地方 |
6. 类型参数的约束
class Simple<T>
{static public bool LessThan(T myInt1, T myInt2){return myInt1 < myInt2; // 错误写法}
}说明:LessThan方法尝试用小于运算符返回结果。但是不是所有类都实现了小于运算符,我们不能用任何类来代替T。
- 要让泛型变得更加有用,我们需要提供额外的信息让编译器知道泛型的参数可以接受那些类型,这些额外的信息叫做约束。
- 只有符合约束的类型才能替代给定的类型参数来产生构造类型。
6.1 where子句
- 约束使用where子句列出。
- 每一个有约束的类型参数都有自己的where子句。
- 如果形参有多个约束,它们在where子句中使用逗号分隔。
// where子句语法格式类型参数 约束列表↓ ↓
where TypeParam : constraint, constraint...↑ ↑
关键字 冒号注意,有关where子句的要点如下:
1. 它们在类型参数列表的关闭尖括号之后列出。
2. 它们不适用逗号或其它符号分隔。
3. 它们可以以任何次序列出。
4. where是上下文关键字,所以可以在其它上下文中使用。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// where子句约束的示例未绑定↓
class MyClass<T1, T2, T3>where T2 : Customer // T2的约束where T3 : ICcomparable // T3的约束说明:
1. T1是未绑定的类型参数。
2. T2只有Customer类型的类或从Customer派生的类才能用作类型实参。
3. T3只有实现了IComparable接口的类才能用作类型实参。
4. T2和T3之间没有分隔符。
6.2 约束类型和次序
- 共有5中类型的约束
约束类型 | 描述 |
---|---|
类名 | 只有这个类型的类或从它派生的类才能够用作类型实参 |
class | 任何引用类型,包括类、数组、委托和接口都可以用作类型实参 |
struct | 任何值类型都可以用作类型实参 |
接口名 | 只有这个接口或实现这个接口的类型才能用作类型实参 |
new() | 任何带有无参公共构造函数的类型都可以用作类型实参,这叫做构造函数约束 |
6.3 where子句约束的顺序
- where子句可以以任何次序列出。然而,where子句中的约束必须有特定的顺序。
- 最多只能有一个主约束,而且必须放在第一位。
- 可以有任意多的接口名称约束。
- 如果存在构造函数约束,则必须放在最后。
结尾
书籍:C#图解教程
著:【美】丹尼尔 · 索利斯;卡尔 · 施罗坦博尔
译:窦衍森;姚琪琳
ISBN:978-7-115-51918-4
版次:第5版
发行:人民邮电出版社
※敬请购买正版书籍,侵删请联系85863947@qq.com※
※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※