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

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

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

目录

第五章 基元类型、引用类型、值类型

编程语言的基元类型

引用类型和值类型

值类型的装箱和拆箱

对象哈希码

Dynamic基元类型

第六章 类型和成员基础

类型的各种成员

类型的可见性

成员的可访问性

静态类

分部类、结构和接口

组件、多态和版本控制


第五章 基元类型、引用类型、值类型

编程语言的基元类型

  • 基元类型(primitive type):编译器直接支持的数据类型,基元类型直接映射到Framework类库(FLC)中存在的类型。(P100 last)在不丢失精度或者数量级的情况下C#允许基类型隐式转换.(P102 1)

  • checked和unchecked:C#允许程序员自己决定如何处理溢出。一种方法时使用/checked+编译器开关。或者使用checked和unchecked操作符(例如int a = checked(a+0x7f))和语句(例如checked{int a;int b;}。(P103 5)

引用类型和值类型

  • FCL(.NETFrameworkClassLibrary)大多数类型都是引用类型,程序中用的多的还是值类型
  • 知识点:1.内存必须从托管堆分配。2.堆上分配的每个对象都有一些额外成员,这些成员必须初始化。3.对象中的其他字节(为字段而设)总是设为零。4.从托管堆分配对象时,可能强制执行一次垃圾回收。(P106 2)
  • 值类型:使用如Int32值时如果每次都进行一次内存分配,则会影响性能。因此CLR提供值类型的轻量级类型。其实例一般在线程栈上分配,代表值类型实例的变量中不返指向实例的指针,变量中包含实例的本身字段。值类型的实例不受垃圾回收期的控制。(P106 last3)
  • 值类型和引用类型的区别:(P107 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

//引用类型(因为'class ')

class someRef i public Int32 ;}

//值类型(因为'struct ' )

struct someval i public int32 x;}

//union因为公用地址,所以同时只能有一个成员被赋值

static void valueTypeDemo ()

{

        someRef r1 = new SomeRef ( ) ;//在堆上分配

        someval vl = new Someval ();//在栈上分配

        r1.x = 5;//提领指针

        v1.x = 5;//在栈上修改

        console.writeLine (rl.x) ;//显示"5"

        console.writeLine (v1.x) ;//同样显示"5"

        someRef r2 = rl;//只复制引用(指针)

        someval v2 = vl ;//在栈上分配并复制成员

        r1.x =8 ;//r1.x和r2.x都会更改

        vl.x = 9;//v1.x会更改,v2.x不变

        Console.writeLine (r1.x) ;//显示"8"

        Console.writeLine(r2.x);//显示"8"

        Console.writeLine (v1.x) ;//显示"9”

        Console.writeLine (v2.x) ;//显示"5"

}

  • 值类型与引用类型的区别:(P109 3)
    1、值类型对象有两种表现形式:未装箱和已装箱(见下文)
    2、值类型从System.ValueType派生
    3、不应在值类型中引入任何新的虚方法,因为不能将值类型作为基类型来定义新的值类型或者新的引用类型。
    4、值类型变量赋值给另一个值类型变量,会执行逐字段复制。引用给引用只复制内存地址。
    5、若定义类型的一个实例方法不再活动,为它们分配的存储会被释放,而不是等待垃圾回收。因为未装箱的值类型不会在堆上分配。

值类型的装箱和拆箱

  • 值类型相比引用类型,不作为对象在托管堆中分配,不被垃圾回收,也不通过指针进行引用。
  • 装箱机制:值类型转换成引用类型需要使用装箱机制。如下代码中的Add原型获取的是Object参数,即对托管堆上的一个对象的引用(或指针)来作为参数,而要传入p就需要进行装箱:
    1、在托管堆中分配内存
    2、值类型的字段复制到新分配的堆内存
    3、返回对象地址(P111 last3)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

struct Point

{

    public int x,y;

}

public sealed class Program

{

    public static void main()

    {

        ArrayList a = new ArrayList();

        Point p;

        for(int i = 0 ; i < 10 ; i++)

        {

            p.x = p.y = i;

//Add方法原型:public virtual int Add(Object value)

            a.Add(p);//让他给装起来了(X)

        }

    }

}

  • 拆箱机制:拆箱不是直接将装箱过程倒过来,拆箱的代价要比装箱低很多,拆箱是获取指针的过程,指针指向的是已装箱实例中的未装箱部分。拆箱不需要复制任何字节(但一般拆箱完成后会进行赋值操作,即字段复制)。对对象进行拆箱时,只能转型为最初未装箱的值类型。(P113 2)
    例如要把上述ArrayList中的值再传回给新的Point,需要进行拆箱操作:
    1、获取已装箱Point对象中的各个Point字段的地址(即拆箱)
    2、将字段包含的值从堆复制到基于栈的值类型实例中(P112 last3)
  • 拆装箱代码性能优化:(P113 1)重点在于规避多次拆装箱,如果能直接引用就直接引用。
    定义自己的类时,可将类中的方法定义为泛型(通过类型约束将类型参数限制为值类型),这样方法就可获取任何值类型而不必装箱。(P117 last2)
    将值类型的未装箱实例转型为类型的某个接口时要对实例进行装箱,因为借口变量必须包含对堆对象的引用(P118 last1)详细示例(P120)
  • 在进行对象比较时的同一性和相等性:(P124 2)同一性指两个引用是否指向同一个对象,相等性指两个引用指向的对象是否类型一样且内容相等。
    定义自己值类型时应重写判等方法,从而提高自己类型的实例进行相等性比较的性能。因为CLR的反射机制慢。编写规则要符合自反,对称,可传递,一致性四个特征。(P125 last)

对象哈希码

  • System.Object提供了虚方法GetHashCode,能够获取任意对象的Int32哈希码。
  • 如果重写了Equals判等方法,必须重写GetHashCode方法。(P126 3)

Dynamic基元类型

  • C#是类型安全的编程语言,所有表达式都解析成类型的实例,编译器生成的代码只执行对该类型有效的操作。
  • 类型安全语言优势:1、大部分代码错误都能在编译时检测到 ,确保代码在尝试执行前是正确的。2、编译出更小、更快的代码,在编译时进行更多预设,在生成的IL和元数据中落实预设。(P127 last)
  • C#编译器允许将表达式的类型标记为dynamic,以方便开发人员使用反射或者其他组件通信。可以使用dynamic表达式/变量调用成员,调用时编译器生成特殊IL代码来表述所需的操作,这类代码称为payload(有效载荷)。运行时payload代码根据dynamic表达式/变量引用的对象的实际类型来决定具体执行的操作。(P128 3)payload代码使用了运行时绑定器(runtime binder)的类。(P131 last3)
  • dynamic必须访问对象的实例成员,因为dynamic变量必须引用对象。(P132 last)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

//代码示例

internal static class DynamicDemo

{

    public static void Main()

    {

        dynamic value;

        for(int demo = 0; demo < 2; demo++)

        {

            value = (demo == 0) ? (dynamic)5 : (dynamic)"A";

            value = value + value;

            M(value);

        }

    }

    private static void M(int n) { Console.WriteLine("M(int):" + n); }

    private static void M(string s) { Console.WriteLine("M(string):" + s; }

}

//结果

M(Int32):10

M(string) :AA

  • 使用dynamic代替传统方法,在target上调用Contains方法并传入实参arg。(P131 2)

1

2

3

dynamic targer = "A";

dynamic arg = "ff";

Boolean result = target.Cotains(arg);


第六章 类型和成员基础

类型的各种成员

  • 类型的各种成员:常量,字段,实例构造器,类型构造器,方法,操作符重载,转换操作符,属性,事件,类型。(P135)

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

using system;

public sealed SomeNestedType

{

    //嵌套类

    private class SomeNestedType{}

    //常量,只读和静态可读/可写字段

    private const int a = 1;

    private readonly string b= "2";

    private static int c = 3;

    //类型构造器

    static D(){}

    //实例构造器

    public D(int x){}

    public D(){}

    //实例方法和静态方法

    private string E(){return null;}

    public static void Main(){}

    //实例属性

    public int F

    {

        get{return 0;}

        set{}

    }

    //实例有参属性(索引器)

    public int this[string s]

    {

        get{return 0;}

        set{}

    }

    //实例事件

    public event EventHandler event;

}

类型的可见性

  • 定义文件范围类型:public不仅对定义程序集中所有代码可见,还对其他程序集中的代码课件。internal类型仅对定义程序集中代码可见。(P138 1)
  • 友元程序集(friend assembly):在工具类型为internal时,仍允许其他外部程序集访问这些类型。(P138 last 2)当程序集确认友元之后,友元程序集就能访问该程序集中的所有internal类型,以及这些类型的internal成员。(P138 last)

成员的可访问性

  • 如果没有显示声明成员可访问性,编译器通常默认选择private。
  • 派生类型重写基类型定义的成员时,C#编译器要求原始成员和重写成员具有相同的可访问性。从基类派生时,CLR允许放宽单不允许收紧成员的可访问性。因为CLR承诺派生类总能转型为基类,并获取对基类的访问权。(P140 last)

静态类

  • 静态类:永远不需要实例化的类,用于组合一组相关的成员。(P141 2)
  • C#对于静态类的限制(P141 3)
    1、静态类必须直接从基类System.Object派生。继承只适用于对象,不能创建静态类的实例。
    2、静态类不能实现任何接口,只有使用类的实例时,才可调用类的接口方法。
    3、静态类只能定义静态成员(字段、方法、属性、事件)
    4、静态类不能作为字段、方法参数或局部变量使用

分部类、结构和接口

  • partial关键字告诉C#编译器:类、结构或接口的定义源代码可能要分散到一个或者多个源代码文件中。将类型源代码分散到多个文件的原因:1、源代码控制 2、在同一个文件中将类或结构分解成不同的逻辑单元 3、代码拆分(P142 1)

组件、多态和版本控制

  • 组件软件编程(Component Softerware Programming),允许应用程序包含许多不同公司生成的代码。(可以理解为插件,譬如Odin,dotween),组件的特点(P143 last2)。
  • 将一个组件(程序集)中定义的类型作为另一个组件(程序集)中的一个类型的基类使用时,便会发生版本控制问题。显然,如果基类的版本(被修改得)低于派生类,派生类的行为也会改变,这可能造成类的行为失常。在多态情形中,由于派生类型会重写基类型定义的虚方法,所以这个问题显得尤其突出。(P144 last2)
  • C#提供的5个能够影响组件版本控制的关键字:

  • 静态方法:在类型上执行操作。 非静态方法:在类型的实例上执行操作。
    CLR允许类型定义多个同名方法,只要每个方法都有一组不同的参数或者不同的返回类型。(P145 3)
  • 设计类型时应尽量减少虚方法数量:1、调用速度比非虚方法慢 2、JIT编译器不能内嵌(inline)虚方法,进一步影响性能 3、让组件版本控制变得更脆弱 4、定义基类型时,经常要提供一组重载的简便方法(convenience method)。如果希望这些方法是多态的,最好的办法就是使最复杂的方法成为虚方法,使所有重载的简便方法成为非虚方法。(P147 last)
  • 密封类优于非密封类:1、版本控制 2、性能 3、安全性和可预见性(P148 last)
    补充:密封类可以用来限制扩展性,如果密封了某个类,则其他类不能从该类继承;如果密封了某个成员,则派生类不能重写该成员的实现
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值