【1】5.1 泛型概述

1.通过泛型,你可以创建独立于特定类型(contained types)以外的方法和类,而不用为不同类型编写多份同样功能的代码,你只需要创建一个方法或者类。

2.泛型类使用泛型类型用来代替所需的特定类型。这一点能满足类型安全的需求:如果传入的类型不满足泛型类的定义,编译器就会及时提示一个错误。

3.用在接口和方法上的泛型。

4.泛型并不仅仅只由C#进行构造,它同时在CLR(Common Language Runtime)里也定义了。这使得它可以在VB里实例化同一个泛型类型,就算这个泛型类型是在C#里定义的。

【2】5.1.1 性能

1.当你对值类型使用非泛型集合类时,将会触发装箱操作,将值类型转换成引用类型,反之亦然(拆箱,将引用类型转换成值类型)。

2.将值类型转换成引用类型的操作我们称之为装箱(boxing)。当一个方法需要一个object类型的参数,而调用方却给这个参数传了一个值类型的时候,装箱操作就会自动处理,将值类型转换成引用类型。反过来,一个值类型装箱后的对象(引用类型)也可以重新转换成值类型,这个操作我们称之为拆箱(unboxing)。想使用拆箱操作,你需要使用强制转换运算符。

3.List<T>类,T指定类型,如:int,string。

【3】5.1.2 类型安全

1.你使用ArrayList类时,内部存储的是object类型,任何类都可以添加到这个数组列表里。

2.foreach语句,隐式转换。

【4】5.1.3 二进制代码的重用

System.Collections.Generic里的List<T>类分别根据int,string,MyClass类型进行了初始化:

var list = new List<int>();
list.Add(44);
var stringList = new List<string>();
stringList.Add("mystring");
var myClassList = new List<MyClass>();
myClassList.Add(new MyClass());

【5】5.1.4 代码膨胀

【6】5.1.5 命名规范

约定俗称的命名规范:

  • 泛型类型的名称以T开头。
  • 假如泛型类型可以被任何类型指代并且没什么特殊要求的话,并且只有一个泛型类型参数,那么用一个单独的字符T命名:
    public class List<T> { }
    public class LinkedList<T> { }
  • 假如对泛型类型有特别的要求,譬如它必须实现某个接口或者派生自某个类,或者使用2个以上的泛型类型,则使用能描述用途的名称作为泛型类型:
    public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
    public delegate TOutput Converter<TInput, TOutput>(TInput from);
    public class SortedList<TKey, TValue> { }

【7】5.2 创建泛型类

1.一个正常的非泛型类型的简单链表类,这个类存储了一个object类型的值,一步一步地将它转换成泛型类。

2.yield语句创建了一个枚举器(enumerator)的状态机。

3.枚举器。

4.每个处理object类型的类都可以改成泛型版本,并且,如果这些类还有各级继承,改成泛型类将会很有用,因为中间会省略很多不必要的强制转换。

【8】5.3 泛型类的功能

1.泛型类型不能赋值成null。在这种情况下就要用到default关键字了。

【9】5.3.1 默认值

1.在泛型里default则是用来初始化泛型类型为null或者0。

2.泛型类型允许实例化成值类型,null只能给引用类型赋初值。

【10】5.3.2 约束

1.如果想对泛型类型添加某些约束的话,最好使用描述性的泛型名称,就像上面我们用TDocument代替T。

2.构造函数约束只能针对无参构造函数,不可能对其它构造函数进行约束。

3.C#里where语句的一个重要限制就是无法定义泛型T需要实现某种运算符。运算符也无法在接口里定义,通过where语句仅仅能约束基础类,接口和无参构造函数。

【11】5.3.3 继承

1.继承的时候,泛型类型必须重复指定,也就是泛型参数得是一样的。又或者明确定义了指定的泛型类型是某个具体类型,这种情况下,派生类可以不是泛型类。如下所示:

public abstract class Calc<T>
{
public abstract T Add(T x, T y);
public abstract T Sub(T x, T y);
}
public class IntCalc: Calc<int>
{
public override int Add(int x, int y) => x + y;
public override int Sub(int x, int y) => x — y;
}

【12】5.3.4 静态成员

1.泛型类中可以声明静态成员,它们只会在相同泛型实例类之间共享。不同的泛型实例类之间互不影响。

StaticDemo<string>.x = 4;
StaticDemo<int>.x = 5;
Console.WriteLine(StaticDemo<string>.x); // 4

【13】5.4 泛型接口

1.新的IComparable<in T>则基于泛型类型。

2.泛型版本的接口IComparable<in T>,则再也不用将Object强制转换成Person了。无需强制转换。

【14】5.4.1 协变和逆变

1.逆变(Contra-Variance,原书中是covariant协变式)。

2.协变(Covariance,原书是contra-variant逆变式)。

【15】5.4.2 泛型接口的协变

1.假如泛型类型由out关键字修饰的话,泛型接口就是协变的。这意味着泛型类型T只能用在返回值上。如下所示,接口IIndex是T协变并且根据一个只读索引返回T类型的值:

public interface IIndex<out T>
{
T this[int index] { get; }
int Count { get; }
}

假如我们在上面这个接口里定义了T类型的参数方法,就会得到一个编译错误:

public interface IIndex<out T>
{
void Test(T t); // 类型参数“T”必须是在“IIndex<T>.Test(T)”上有效的 逆变式。“T”为 协变。
}

2.假如要在IIndex里定义可读写的索引器,泛型类型T既要做参数又要当返回值,这里就不能用协变了。泛型类型不能添加任何out或者in的修饰。

3.RectangleCollection.GetRectangles返回了一个实现了IIndex<Rectangle>接口的RectangleCollection实例

【16】5.4.3 泛型接口的逆变

1.泛型接口也可以是逆变的,如果它是用in关键字修饰的话。在这种情况下,接口仅允许泛型类型T作为方法参数使用:

public interface IDisplay<in T>
{
void Show(T item);
}

ShapeDisplay类实现了接口IDisplay<Shape>并且使用Shape类作为泛型参数:

public class ShapeDisplay: IDisplay<Shape>
{
public void Show(Shape s) => Console.WriteLine($"{s.GetType().Name} Width: {s.Width}, Height:{s.Height}");
}

2.

  • 协变,很外向(out修饰)很和谐,子类无伤转换为父类,非常和谐。
  • 逆变,很内向(in)很拧巴,父类别扭地转换为子类。

【17】5.5 泛型结构

1.跟类很相似,结构体也可以是泛型的,除了不能继承之外与泛型类几乎没有区别。在本小节中你将看到泛型结构体Nullable<T>在.NET Framework里是如何定义的。

2.Nullable<T>的泛型参数必须为结构体。

3.可空类型修饰符(?)int? 表示可空的整形,DateTime? 表示可为空的时间。

4.三元(运算符)表达式(?:):x?y:z 表示如果表达式x为true,则返回y;如果x为false,则返回z,是省略if{}else{}的简单形式。

5.通过联结运算符??来将一个可空类型赋值给非空类型变量。如下所示,当x1是null时,y1的值将会是0:

int? x1 = GetNullableType();
int y1 = x1 ?? 0;

【18】5.6 泛型方法

1.泛型方法甚至可以在非泛型类里定义。

2.因为C#编译器在调用Swap方法时可以获取到参数类型,所以你在调用这个方法的时候可以省略泛型类型的声明,这个方法也可以像非泛型方法那样调用:

int i = 4;
int j = 5;
Swap(ref i, ref j);

无声明T

【19】5.6.1 泛型方法示例

1.通过调用接口的集合类的属性来实现foreach

2.完成后的静态方法AccumulateSimple可以这么调用:

decimal amount = Algorithms.AccumulateSimple(accounts);

【20】5.6.2 带约束的泛型方法

我们第二个版本的Accumulate方法允许接收实现了IAccount接口的类型。就像你在前面看到的那样,泛型类型可以通过where子句进行限制。

具体示例还不太清楚。

【21】5.6.3 带委托的泛型方法

调用Accumulate方法的时候,必须制定每个泛型参数,因为编译器无法自动识别出泛型参数的类型。

Action其实就是没有返回值的delegate。

Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托。

多研究

【22】5.6.4 泛型方法的特化

1.重载泛型方法,来定义特定类型的专有操作(define specializations for specific types)。

2.在编译的时候,编译器会选择最匹配的版本。假如传递过来的是int类型的参数,编译器会优先选择指定了int类型参数的方法。如果是其他类型的参数,编译器再考虑选择相应的泛型方法。

【23】5.7 小结

本章介绍了CLR中非常重要的特性:泛型。通过泛型类你可以创建类型无关的类,通过泛型方法可以允许类型无关的方法。接口,结构体,委托都可以定义成泛型。泛型使得新的编程方式成为可能。你已经了解了算法(algorithms),尤其是操作(actions)和声明(predicates),是如何通过不同的类进行实现的——并且它们都是类型安全的。泛型委托使得算法可以从集合里解耦(make it possible to decouple algorithms from collections)。

多研究

参考网址:https://www.cnblogs.com/zenronphy/p/ProfessionalCSharp7Chapter5.html#52-%E5%88%9B%E5%BB%BA%E6%B3%9B%E5%9E%8B%E7%B1%BB

C#高级编程第11版 - 第五章 索引的更多相关文章

  1. C#高级编程第11版 - 第六章 索引

    [1]6.2 运算符 1.&符在C#里是逻辑与运算.管道符号|在C#里则是逻辑或运算.%运算符用来返回除法运算的余数,因此当x=7时,x%5的值将是2. [2]6.2.1 运算符的简写 1.下 ...

  2. C#高级编程第11版 - 第三章 索引

    [1]3.1 创建及使用类 1.构造函数:构造函数的名字与类名相同: 使用 new 表达式创建类的对象或者结构(例如int)时,会调用其构造函数.并且通常初始化新对象的数据成员. 除非类是静态的,否则 ...

  3. C#高级编程第11版 - 第七章 索引

    [1]7.1 相同类型的多个对象 1.假如你需要处理同一类型的多个对象,你可以使用集合或者数组. 2.如果你想使用不同类型的不同对象,你最好将它们组合成class.struct或者元组. [2]7.2 ...

  4. C#高级编程第11版 - 第四章 索引

    [1]4.2 继承的类型 1.C#不支持类的多继承,但它支持一个接口继承自多个接口. 2.单继承:单继承允许一个类继承自另外一个基类,C#支持. 3.多级继承:多级继承允许创建一个类继承自它的父类,而 ...

  5. 【转】apue《UNIX环境高级编程第三版》第一章答案详解

    原文网址:http://blog.csdn.net/hubbybob1/article/details/40859835 大家好,从这周开始学习apue<UNIX环境高级编程第三版>,在此 ...

  6. 2019-1-17 前言 C#高级编程(第11版)

    C#已更新为更快的速度.主要版本7.0是2017年3月发布,次要版本7.1和7.2很快发布在2017年8月和2017年12月.通过项目设置,您可以与每个应用程序一起分发,是开源的,不可用仅适用于Win ...

  7. C#高级编程第11版 - 第八章 索引

    [1]8.1 引用方法 1.委托是指向方法的.NET地址变量. 2.委托是类型安全的类,定义了返回类型和参数类型.委托类不单单只包含一个方法引用,它也可以保存多个方法的引用. 3.Lambda表达式直 ...

  8. C#高级编程第11版 - 第九章 索引

    [1]9.1 System.String 类 String类中关键的方法.如替换,比较等. [2]9.1.1 构建字符串 1.String类依然有一个缺点:因为它是不可变的数据类型,这意味当你初始化一 ...

  9. C#高级编程第11版 - 第二章 索引

    [1]2.1.1 Hello,World! 1. using static System.Console; // ... WriteLine("Hello World!"); 提前 ...

随机推荐

  1. 【进程/作业管理】篇章一:Linux进程及其管理(进程管理类工具)----pstree、ps、top、htop、kill、(killall、pkill、pgrep、pidof)

    主要讲解进程管理类命令及工具的使用:pstree.ps.top.htop.kill.(killall.pkill.pgrep.pidof) pstree 以树状图的方式展现进程之间的派生关系,显示效果 ...

  2. 由innodb锁引起的数据库相关

    innodb 锁的问题 1.事务 原子性:要么成功,要么失败 一致性:前后数据保持一致状态 隔离性:多个事务并行,相互不影响 持久性:事务提交之后,对数据的影响是永久性的,即使故障也可以保持. 2.并 ...

  3. 96. Unique Binary Search Trees1和2

    /* 这道题的关键是:动态表尽量的选取,知道二叉搜索树中左子树的点都比根节点小,右子树的点都比根节点大 所以当i为根节点,左子树有i-1个点,右子树有n-i个点,左右子树就可以开始递归构建,过程和一开 ...

  4. 【陪你系列】5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub https://github.com/rongweihe/MoreT ...

  5. 【Linux】中默认文本编辑器 vim 的入门与进阶

    Linux 基本操作 vim 篇 vim 简介 vim 是 Linux 上最基本的文本编辑工具,其地位像是 Windows 自带的记事本工具,还要少数的 Linux 系统自带 leafpad 编辑器, ...

  6. Turtlebot3入门教程-系统-SBC软件设置(ubuntu20.04)

    本文针对如何在树莓派3上安装ubuntu20.04系统和软件进行讲解 树莓派3接上显示屏和鼠标后,开机后继续安装软件包 详细步骤如下: (1)系统安装 (2)ROS安装 (3)TurboBot3依赖的 ...

  7. Salesforce 大数据量处理篇(一)Skinny Table

    本篇参考:https://developer.salesforce.com/docs/atlas.en-us.salesforce_large_data_volumes_bp.meta/salesfo ...

  8. 一文讲尽门面日志slf4j和log4j、log4j2、logback依赖jar引用关系

    公众号Mac代码分割阅读链接 前言 之前都是使用SparkStreaming开发,最近打算学习一下Flink,就从官网下载了Flink 1.11,打算搞一个客户端,将程序提交在yarn上.因为Flin ...

  9. mysql事务-简介

    mysql事务 问题 概要 storage engine必须支持事务 事务根据隔离级别的不同,不同事务之间有不同的可见性 begin 或者 start transaction, 显式开启事务:comm ...

  10. [不止于代码]Unraid基本使用速记

    1.Unraid简介 Unraid是一个虚拟机系统,类似于VM.PVE,但又区别于前二者.通过Unraid的Dokcer可以快速构建类Nas及虚拟机环境,也可虚拟黑群晖使用,可以使用磁盘阵列,保护你的 ...