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

  1.7 C#, 3.2 游戏引擎技术
  • 本博客所总结书籍为《CLR via C#(第4版)》清华大学出版社,2021年11月第11次印刷(如果是旧版书籍或者pdf可能会出现书页对不上的情况)
  • 你可以理解为本博客为该书的精简子集,给正在学习中的人提供一个“glance”,以及对于部分专业术语或知识点给出解释/博客链接。
  • 【本博客有如下定义“Px x”,第一个代表书中的页数,第二个代表大致内容从本页第几段开始。(如果有last+x代表倒数第几段,last代表最后一段)】
  • 电子书可以在博客首页的文档-资源归档中找到,或者点击:传送门自行查找。如有能力请支持正版。(很推荐放在竖屏上阅读本电子书,这多是一件美事)
  • 在学习本章节之前,您可能需要了解如下知识点:
    方法与委托:传送门
    ref和out关键字:传送门
  • 欢迎加群学习交流:637959304 进群密码:(CSGO的拆包密码) 

第七章 常量和字段

常量

  • 常量是从不变化的符号。定义常量时,必须在编译时就要确定他的值。(P155 1)常量总是被视为静态成员而不是实例成员,定义常量将导致创建元数据。(P155 2)运行时不需要为常量分配任何内存,不能获取常量对的地址,也不能以引用的方式传递常量。(P155 last)更新常量必须要重新编译,而不是重新生成DLL程序集。(P156 last2)

字段

  • 字段是一种数据成员,容纳了一个值类型的实例或者对一个引用类型的引用。(P156 last2)
  • 字段存储在动态内存中,他们的值在运行时才能获取。字段可以是任何数据类型,(P157 2)
image 45 - 《CLR via C#》笔记:第2部分 设计类型(3)
public sealed class SomeType 
{
    //这是一个静态readonly字段;在运行时对这个类进行初始化时
    //它的值会被计算并存储到内存中
    public static readonly Random s_random = new Random ( ) ;
    //这是一个静态read/write字段
    private static Int32 s_numberofwrites =0;
    //这是一个实例readonly字段
    public readonly string Pathname = "Untitled" ;
    //这是一个实例read/write字段
    private system.IO.Filestream n_fs;
    public someType (String pathname) i
    //该行修改只读字段pathname.
    //在构造器中可以这样做
    this.Pathname = pathname ;
    public string DoSomething () 
    {
        //该行读写静态read/ write字段
        s _numberOfwrites = s_numberofwrites + 1;
        //该行读取readonly实例字段
        return Pathname ;
    }
}

第八章 方法

实例构造器和类(引用类型)

  • 构造器是将类型的实例初始化为良好状态的特殊方法。创建引用类型的实例时,首先为实例的数据字段分配内存,然后初始化对象的附加字段(类型对象指针和同步块索引),最后调用类型的实例构造器来设置对象的初始状态。(P161 2)
  • 构造引用类型的对象时,在调用类型的实例构造器之前,为对象分配的内存总是先被归零。没有被构造器显式重写的所有字段都保证获得0或null值。
  • 实例构造器永运不能继承。(P161 last)
  • 如果有几个已初始化的实例字段和许多重载的构造器方法,可考虑不是在定义字段时初始化,而是创建单个构造器来执行这些公共的初始化。然后,让其他构造器都显式调用这个公共初始化构造器。
using system;
internal sealed class SomeType 
{
    //不要显式初始化下面的字段
    private int32   m_x;
    private string  m_s;
    private Double  m_d;
    private Byte    m_b;
    //该构造器将所有字段都设为默认值,
    //其他所有构造器都显式调用该构造器
    public SomeType () 
    {
        m_x = 5;
        m_s ="Hi there" ;
        m_d = 3.14159;
        m_b = Oxff ;
    }
    //该构造器将所有的字段都设为默认值,然后修改m_x
    public someType (lnt32 x): this()
    {
        m_x=x;
    }
    //该构造器将所有的字段都设为默认值,然后修改m_s
    public SomeType (string s): this()
    {
        m_s= s;
    }
    //该构造器首先将所有字段都设为默认值,然后修改m_x和m_s
    public someType (Int32 x,string s): this ()
    {
        m_x=x;
        m_s =s;
    }
}

实例构造器的结构(值类型)

  • 值类型不需要定义构造器(P164 last3)C#不允许值类型带有无参构造器(P166 2)
  • internal class Point{public Point(){}}

类型构造器

  • 类型构造器可以应用于接口(CLR可以C#不行),引用类型和值类型,作用为设置类型的初始状态。(P167 2),C#允许值类型定义无参的类型构造器,并在首次被访问时执行代码。类型构造器总是私有的。
    不推荐在值类型中定义类型的构造器。
  • internal struct/class A{static A(){}}

操作符重载方法

  • 示例
public sealed class Complex
{
    public static Complex operator+{Complex c1,Complex c2}{}
}
image 46 - 《CLR via C#》笔记:第2部分 设计类型(3)
一元操作符
image 47 - 《CLR via C#》笔记:第2部分 设计类型(3)
二元操作符

转换操作符方法

  • 如果源类型或目标类型不是基元类型,便一去会生成代码,要求CLR执行转换(强制转型)(P173 last 2)
  • 转换符操作重载(P174 last)

扩展方法

  • 示例(P176 last)
  • 扩展方法的规则和原则:
    1、C#只支持扩展方法,不支持扩展属性、扩展事件、扩展操作符等
    2、扩展方法(第一个参数前面有this的方法)必须在非泛型的静态类中生命
    3、C#编译器在静态类中查找扩展方法时,要求静态类本身必须具有文件作用域。
    4、多个静态类可以定义相同的扩展方法。
    5、扩展方法可能存在版本控制问题。(P178last)
  • 可以为接口类型(P179 last)委托类型(P180 2)枚举类型(P180 3)定义扩展方法,C#允许创建委托来引用一个对象上的扩展方法。(P180 last)

分部方法

  • 要根据使用工具定制类型方法
    传统方法:需要重写这个类型,然后再类型中重写方法,且类型必须是非密封的类。
    分部方法:使用partial解决同时覆盖类的行为。(P182 last)可以用于密封类、静态类以及值类型。
//改写示例
//工具生成的代码
internal class base->internal sealed partial class base
protected virtual void->partial void

//开发人员生成的代码,存储在另一个源代码文件中
internal class Derived : Base
{
    protected override void OnNameChanging(string value) 
    {
        if(string. IsNul1OrEmpty (value) )
        throw new ArgumentNullException ("value" );
    }
}
->
internal sealed partial class Derived : Base
{
    partial void OnNameChanging(string value) 
    {
        if(string. IsNul1OrEmpty (value) )
        throw new ArgumentNullException ("value" );
    }
}
  • 分部方法的规则和原则:
    1、只能在分部类或结构中声明
    2、分部方法返回的值始终为void,任何参数都不能用out修饰符来标记
    3、声明和实现必须具有完全一直的签名
    4、如果没有对应的实现部分,便不能在代码中创建一个委托来引用这个分部方法
    5、分部方法总是被视为private方法,但C#编译器进制在该方法声明前添加private

第九章 参数

可选参数和命名参数

  • 设计方法的参数时,可为部分或全部参数分配默认值。(这部分没学好的建议回炉重造 )(P185 last)
  • 在方法中为部分参数指定默认值的规则和原则:(P186 last)
    1、可为方法、构造器方法和有参数性(C#索引器)的参数指定默认值。还可为属于委托定义一部分的参数指定默认值。以后调用该委托类型的变量时刻省略实参来接受默认值。
    2、有默认值的参数必须放在没有默认值的所有参数之后。
    3、默认值必须是编译时能确定的常量值。
    4、不要重命名参数变量,否则任何调用者以传参数名的方式传递实参,他们的代码也必须修改。
    5、如果方法从模块外部调用,更改参数的默认值具有潜在的危险性。

    6、如果参数用ref或者out关键字进行了表示,就不能设置默认值。因为没有办法为这些参数传递有意义的默认值。
  • 在可选或命名参数调用方法时的规则和原则:(P187 last)
    1、实参可按任意顺序传递,但命名实参只能出现在实参列表的尾部。
    2、可按名称将实参传给没有默认值的参数,但所有必须的实参都必须传递,编译器才能编译代码。
    3、C#不允许省略逗号之间的实参。

隐式类型的局部变量

  • C#能根据初始化表达式的类型推断方法中的局部变量的类型(P188 last)

以传引用的范式想方法传递参数

  • CLR默认所有方法参数都传值。传递引用类型的对象时,对象引用(或者说指向对象的指针)被传给方法。注意引用(或指针)本身是传值的,意味着方法能修改对象,而调用者能看到这些修改。对于值类型的实例,传给方法的是实例的一个副本,意味着方法将获得它专用的一个值类型实例副本,调用者中的实例不受影响。(P190 1)
  • C#使用out或ref支持以传引用而非传值的方式传递参数。CLR不区分out和ref,它们都生成相同的IL代码,元数据也几乎一致。但C#编译器区分这两个关键字,out表明不指望调用者在调用方法之前初始化好对象,被调用的方法不能读取参数的值,而且在返回前必须向这个值写入。ref表明调用者必须在调用该方法前初始化参数的值,被调用的方法可以读取值以及/或者向值写入。(P190 last2)

向方法传递可变数量的参数

  • params关键字:只能应用于方法签名中的最后一个参数。(P195 last)
//该方式只能表示一维数组(任意类型)
static int Add(params int[] values) 
{
    int sum = 0 ;
    if(values != null) 
    {
        for (int x = 0; x < values . Length; x+十)
            sum+= values [x ] ;
    }
    return sum;
}
//使用该关键字允许如下方法调用
Add(1,2,3,4,5,6);
//应用于任意数量、任意类型数据
static int Add(params Object[] values) 

参数和返回类型的设计规范

  • 声明方法的参数类型时,应尽量指定最弱的类型,宁愿要接口也不要基类。(P197 1)

常量性

  • CLR没有提供讲方法或参数声明为常量的功能。(P198 last)

LEAVE A COMMENT