《CLR via C#》笔记:第2部分 设计类型(4)

  • 本博客所总结书籍为《CLR via C#(第4版)》清华大学出版社,2021年11月第11次印刷(如果是旧版书籍或者pdf可能会出现书页对不上的情况)
  • 你可以理解为本博客为该书的精简子集,给正在学习中的人提供一个“glance”,以及对于部分专业术语或知识点给出解释/博客链接。
  • 【本博客有如下定义“Px x”,第一个代表书中的页数,第二个代表大致内容从本页第几段开始。(如果有last+x代表倒数第几段,last代表最后一段)】
  • 电子书可以在博客首页的文档-资源归档中找到,或者点击:传送门自行查找。如有能力请支持正版。(很推荐放在竖屏上阅读本电子书,这多是一件美事)
  •  个人博客网址:《CLR via C#》笔记:第2部分 设计类型(4)-Sugar的博客,如文章转载中出现例如传送门失效等纰漏,建议直接访问原博客网址。

  • 欢迎加群学习交流:637959304 进群密码:(CSGO的拆包密码) 

目录

第十章 属性

无参属性

有参属性

调用属性刚问器方法时的性能

属性访问器的可访问性

第十一章 事件

设计要公开事件的类型

编译器如何实现事件

设计侦听事件的类型

显式实现事件

第十二章 泛型

FCL中的泛型

泛型基础结构

泛型接口

泛型委托

委托和接口的逆变和协变泛型类型实参

泛型方法

泛型和其他成员

可验证性和约束


第十章 属性

无参属性

  • 封装对类型中的数据字段的访问,好处(P202 1)
    1、可以让访问字段来执行一些额外作用(side effect)、缓存某些值或者推迟创建一些内部对象。
    2、可以以线程安全的方式访问字段。
    3、字段可能是一个逻辑字段,它的值不由内存中的字节表示,而是通过某个算法来计算获得
  • 使用get,set封装数据(P203 1),下述的get访问器方法不接受参数

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public sealed class Employee

{

    private string m_Name;

    private int m_age;

    public string Name

    {

        get{return (m_Name);}

        set{m_Name = value;}//关键字value代表新的值

    }

}

Employee e;

e.Name = "abc";

string a = e.Name;

  • 自动实现的属性(Automatically Implemented Property,AIP):public string Name{get;set;}
    使用AIP即创建了一个属性,访问该属性的任何代码实际都会调用get和set方法。
  • AIP有如下问题:(P205 2)
    1、字段声明语法可能包含初始化部分,所以要在一行代码中声明并初始化字段。但没有简单的语法初始化AIP。所以,必须在每个构造器方法中显式初始化每个AIP。
    2、运行时序列化引擎将字段名持久存储到序列化的流中。AIP的支持字段名称由编译器决定,每次重新编译代码都可能更改这个名称。因此,任何类型只要含有一个AIP,就没办法对该类型的实例进行反序列化。在任何想要序列化或反序列化的类型中,都不要使用AIP功能。
    3、调试时不能在AIP的get或set方法上添加断点,所以不好检测应用程序在什么时候获取或设置这个属性。相反,手动实现的属性可设置断点,查错更方便。
  • 对象和结合初始化器:即可以在定义时候就使用{}对其公共成员复制,并支持完成一些额外操作。(P208 2)

1

2

//如果属性的类型实现了IEnumerable或 IEnumerable<T>接口,属性就被认为是集合,而集合的初始化是一种相加(additive)操作,而非替换(replacement)操作。示例:

className c = new className{stringA = "aaa","bbb","ccc"};

  • 匿名类型:用简洁的语法来自动声明不可变的元组类型。

1

2

3

4

5

6

//方法1

var o1 = new {Name = "abc",year = 7777};

//方法2

string name = "abc";

DateTime dt = DateTime.Now;

var o2 = new{name,dt.year};

  • System.Tuple类型:从Object派生,也是匿名类型。示例:(P212 last)

有参属性

  • C#称有参数的get访问器为索引器(P214 1)
    C#使用数组风格的语法来公开有参属性(索引器)。换句话说,可将索引器看成是C#开发人员对[]操作符的重载。(P214 2)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

public sealed class BitArray

{

    //容纳了二进制位的私有字节数组

    private Byte [] m_byteArray;

    private Int32 m_numBits;

    //下面的构造器用于分配字节数组,并将所有位设为0

    public BitArray ( Int32 numBits)

    {

        //先验证实参

        if(numBits <=0)

            throw new ArgumentOutOfRangeException ("numBits must be > 0");

        //保存位的个数

        m_numBits = numBits;

        //为位数组分配字节

        m_byteArray = new Byte [ (numBits + 7)/ 8 ];

    }

    //下面是索引器(有参属性)

    public Boolean this [ Int32 bitPos]

    {

        //下面是索引器的get访问器方法

        get

        {

            //先验证实参

            if( (bitPos < 0)ll(bitPos >= m_numBits))

                throw new ArgunentOutOfRangeException ( "bitPos");

            //返回指定索引处的位的状态

            return (n_bytearray [bitPos / 8 ] 6 (1 <<(bitPos % 8) ) ) != 0;

        }

        //下面是索引器的set访问器方法

        set

        {

            if ( (bitPos < 0)ll(bitPos >= m_numBits))

            throw new ArgumentoutOfRangeException ( "bitPos",bitPos.Tostring ( ) );

            if (value)

            {

                //将指定索引处的位设为true

                m_byteArray [bitPos i / 8]=(Byte)(m_byteArray [bitPos / 8]l (l <<(bitPos $ 8) ) );

            }

            else

            {

                //将指定索引处的位设为false

                m_byteArray [bitPos / 8 ]=(Byte)(m_byteArray [bitPos / 8 ] 6~(1 <<(bitPos % 8) ) );

            }

        }

    }

}

//使用:

BitArray ba = new BitArray(14);

ba[1] = 2;

调用属性刚问器方法时的性能

  • 简单的get,set,JIT编译器会将代码内联(inline)。这样就没有性能上的损失。(P218 last)

属性访问器的可访问性

  • 有时希望为get访问器方法指定一种可访问性,为set访问器方法指定另一种可访问性。最常见的情形是提供公共get 访问器和受保护set访问器。(P219 1)

1

2

3

4

5

6

7

8

9

10

//Name属性本身声明为public属性,意味着get访问器方法是公共的,所有代码都能调用。而 set访问器方法声明为protected,只能从SomeType 内部定义的代码中调用,或者从SomeType的派生类的代码中调用。

public class a

{

    private string m_name;

    public string name

    {

        get{return m_name;}

        protected set{m_name = value;}

    }

}


第十一章 事件

  • 定义了事件成员的类型允许类型(或类型的实例)通知其他对象发生了特定的事情。定义了事件成员的类型能提供以下功能:(P221 1)类型之所以能提供事件通知功能,是因为类型维护了一个已登记方法的列表。事件发生后,类型将通知列表中所有已登记的方法。
    1、方法能等级它对事件的关注
    2、方法能注销它对事件的关注
    3、事件发生时,登记了的方法将接收到通知
  • CLR事件模型以委托为基础。委托是调用”回调方法的一种类型安全的方式。对象凭借回调方法接收它们订阅的通知。

设计要公开事件的类型

  • 根据上图示例来进行设计:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

//第一步:定义类型来容纳所有需要发送给事件通知接受者的附加信息(P222 last)

public class a

{

    private string m_name;

    public string name

    {

        get{return m_name;}

        protected set{m_name = value;}

    }

}

internal class NewMailEventArgs : EventArgs

{

    private readonly string m_from,m_to,m_subject;

    public NewMailEventArgs(string from,string to,string subject)

    {

        m_from = from;m_to = to,m_subject = subject;

    }

    public string From{get{return m_from;}}

    public string To{get{return m_to;}}

    public string Subject{get{return m_subject;}}

}

//第二步:定义事件成员-event(P223 last)

//每个事件成员都要指定一下内容:可访问性标识符;委托类型,指出要调用的方法的原型;名称(可以是有效标识符)

iinternal class MailManager

{

    public event EventHandler<NewMailEventArgs> NewMail;

}

//第三步:定义负责引发事件的方法来通知事件的登记对象(P224 last)

//类需要定义一个受保护的虚方法,引发事件时,类及其派生类中的代码会调用该方法。

internal class MailManager

{

//第三步:定义负责引发事件的方法来通知已登记的对象。

//如果类是密封的,该方法要声明为私有和非虚

    protected virtual void OnNewMail(NewMailEventArgs e)

    {

//出于线程安全的考虑,现在将对委托字段的引用复制到一个临时变量中(P225 1)

        EventHandler<NewMailEventArgs > temp = Volatile.Read(ref NewMail);

//任何方法登记了对事件的关注,就通知它们

        if(temp != null) temp(this,e);

    }

}

//第四步:定义方法将输入转化为期望事件(P226 4)

internal class MailManager

{

    public void SimulateNewMail(string from,string to,string subject)

    {

        NewMailEventArgs e = new NewMailEventArgs(from,to,subject);

//调用虚方法通知对象事件已经发生

//如果没有类型重写该方法,我们的对象将通知事件的所有登记对象

        OnNewMail(e)

    }

}

编译器如何实现事件

  • C#编译器会把event事件转化为3个构造:1、一个被初始化为null的私有委托字段 2、一个公共add_xxx方法 3、一个公共remove_xxx方法(P226 1)

设计侦听事件的类型

  • 定义一个类型来使用另一个类型提供的事件(P228 last)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

internal sealed class Fax

{

    public Fax(MailManager mm)

    {

//向MailManager的NewMail事件登记回调方法

        mm.NewMail +=Faxmsg;

    }

//触发事件时将调用这个方法

    private void FaxMsg(object sender,NewMailEventArgs e)

    {

        console.log.....

    }

//注销事件侦听

    public void Unregister(MailManager mm)

    {

        mm.NewMail -= FaxMsg;

    }

}

显式实现事件

  • 通过显示实现事件来高效率地实现提供大量事件的类
    代码:(P231)(注意一下应该是排版问题,代码中的EventSet类是包含之后所有函数到结尾的,但其他函数并没有缩进导致看起来和EventSet类同级了)
  • 委托(delegate)的使用:传送门
  • 博客参考:传送门(原文代码中用了线程等内容,会在后续章节中讲述,目前看不太明白的可以看这篇博客)

第十二章 泛型

  • 泛型(generic)是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即“算法重用”。(P233 1)即该算法没有设定要操作什么数据类型,因此可以广泛的应用于不同类型的对象,在使用时指定算法要操作的具体数据类型。(P233 2)
  • CLR允许创建泛型引用类型和泛型值类型,不允许创建泛型枚举类型。
    CLR允许创建泛型接口和泛型委托。(P233 3)
  • 泛型的优势:1、源代码保护 2、类型安全 3、更清晰的代码 4、更佳的性能(P235 1)

FCL中的泛型

  • 示例(P237 last)

泛型基础结构

  • 开放类型和封闭类型:具有泛型类型参数的类型仍然是类型,CLR同样会为它创建内部的类型对象。这一点适合引用类型(类)、值类型(结构)、接口类型和委托类型。然而,具有泛型类型参数的类型称为开放类型,CLR禁止构造开放类型的任何实例。这类似于CLR禁止构造接口类型的实例。代码引用泛型类型时可指定一组泛型类型实参。为所有类型参数都传递了实际的数据类型,类型就成为封闭类型。CLR允许构造封闭类型的实例。(P239 1)
  • 泛型类型和继承:泛型也是类型,可以从任何类型派生。指定类型的实参不影响继承层次结构(用于判断强制类型是否被允许)。(P240 last)
  • 泛型类型同一性:如果有一类继承自泛型,并分别实例化两个对象,那么泛型和类的对象在使用==符号判等时返回的结果为FALSE。可以使用using指令来化简泛型封闭类型,例

1

using DateTimeList = system.collections.Generic.List<System.DateTime>;

  • 代码爆炸:使用泛型类型参数的方法在进行JIT编译时,CLR获取方法的IL,用指定的类型实参替换,然后创建恰当的本机代码(这些代码为操作指定数据类型“量身定制”)。这正是你希望的,也是泛型的重要特点。但这样做有一个缺点:CLR要为每种不同的方法/类型组合生成本机代码。我们将这个现象称为代码爆炸。它可能造成应用程序的工作集显著增大,从而损害性能。(P243 1)
    优化:相同类型实参调用只需组合编译一次。(P243 2)CLR判定所有引用类型实参都完全相同则可以代码共享。(P243 3)

泛型接口

  • 没有泛型接口,每次用非泛型接口(如IComparable)来操纵值类型都会发生装箱,而且会失去编译时的类型安全性。这将严重制约泛型类型的应用范围。因此,CLR提供了对泛型接口的支持。引用类型或值类型可指定类型实参实现泛型接口。也可保持类型实参的未指定状态来实现泛型接口。(P243 last)

泛型委托

  • CLR支持泛型委托,目的是保证任何类型的对象都能以类型安全的方式传给回调方法。此外,泛型委托允许值类型实例在传给回调方法时不进行任何装箱。(17章会详细讲述)

委托和接口的逆变和协变泛型类型实参

  • 委托的每个泛型类型参数都可标记为协变量(convariant)或逆变量(contravariant)。利用这个功能,可将泛型委托类型的变量转换为相同的委托类型(但泛型参数类型不同)。泛型类型参数可以是:不变量,逆变量(泛型类型参数可以从一个类更改为它的某个派生类,用in标记),协变量(泛型类型参数可以从一个类更改为它的某个基类,用out标记)。

泛型方法

  • 定义泛型类、结构或接口时,类型中定义的任何方法都可引用类型指定的类型参数。类型参数可作为方法参数、方法返回值或方法内部定义的局部变量的类型使用。然而,CLR还允许方法指定它自己的类型参数。这些类型参数也可作为参数、返回值或局部变量的类型使用。(P247 1)
  • 泛型方法和类型推断:C#编译器支持调用泛型方法时进行类型推断。(P248 last3)类型可以定义多个方法,让其中一个方法接受具体数据类型,让另一个接受泛型类型参数。(P249 2)

泛型和其他成员

  • 在C#中,属性、索引器、事件、操作符方法、构造器和终结器本身不能有类型参数。但它们能在泛型类型中定义,而且这些成员中的代码能使用类型的类型参数。(P249 last2)

可验证性和约束

  • 约束的作用:限制能指定成泛型实参的类型数量。通过限制类型的数量,可以对那些类型执行更多操作。(P250 last3)
    约束可应用于泛型类型的类型参数,也可应用于泛型方法的类型参数(如下所示)。CLR不允许基于类型参数名称或约束来进行重载,只能基于元数(类型参数个数)对类型或方法进行重载。(P251 3)

1

2

3

4

5

6

7

//C#的 where关键字告诉编译器,为T指定的任何类型都必须实现同类型(T)的泛型lComparable 接口。

//有了这个约束﹐就可以在方法中调用CompareTo,因为已知IComparable<T>接口定义了CompareTo。

private static T Min<T>(T o1,T o2) where T : IComparable<T>

{

    if(o1.CompareTo(o2) < 0) return o1;

    else return o2;

}

  • 主要约束:类型参数可以指定零个或者一个主要约束。主要约束可以是代表非密封类的一个引用类型。不能指定以下特殊引用类型:System.Object ,System.Array,System.Delegate ,System.MulticastDelegate,System.ValueType,System.Enum或者System.Void。(P252 4)
    可以这是class和struct为主要约束(P252 last3)
  • 次要约束:类型参数可以指定零个或者多个次要约束,次要约束代表接口类型。这种约束向编译器承诺类型实参实现了接口。由于能指定多个接口约束,所以类型实参必须实现了所有接口约束(以及主要约束,如果有的话)。第十三章将详细讨论接口约束。(P25 3)
  • 构造器约束:类型参数可指定零个或一个构造器约束,它向编译器承诺类型实参是实现了公共无参构造器的非抽象类型。注意,如果同时使用构造器约束和 struct约束,C#编译器会认为这是一个错误,因为这是多余的;所有值类型都隐式提供了公共无参构造器。(where T :new())(P254 2)
  • 其他可验证性问题:(P254 last)
    1、泛型类型变量的转型:将泛型类型的变量转型为其他类型是非法的,除非转型为与约束兼容的类型。
    2、将泛型类型变量设置为默认值:将泛型类型变量设为null是非法的,除非将泛型类型约束成引用类型。
    3、将泛型类型变量与null进行比较:无论泛型类型是否被约束,使用–或!=操作符将泛型类型变量与null进行比较都是合法的:
    4、两个泛型类型变量相互比较:如果泛型类型参数不能肯定是引用类型,对同一个泛型类型的两个变量进行比较是非法的。
    5、泛型类型变量作为操作数使用:会出大量问题,建议不用。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值