求知若饥,虚心若愚
类
封装、继承、多态
- 字段(公有字段一般用属性,私有字段命名要加_或m_,与局部变量区分)
- 方法
- 属性
- PascalCase命名
- 由于setter函数所以类内部使用也尽量用属性而不是属性对应的私有字段
- 属性不允许作为ref或out参数,因为可能是虚字段(只有表达式)或只读
- 属性内部机制,CIL代码会生成对应的set/get函数,属性是CIL的显式构造
- 构造函数
- new操作符返回对实例化好的对象的一个引用(构造函数没有返回类似的引用是因为new先从内存管理器获取空白内存区域,然后将地址作为内存引用传给构造函数,类似隐式的this,构造后new操作符返回内存的引用)
- 对象初始化器,用{}包裹,逐一对可访问的字段或属性赋值
- 构造函数链,支持调用其他重载构造函数,使用:this()
- 终结器
- 垃圾回收时,先筛选到终结队列,执行完并去除引用后再回收
- 解构函数
- 将成员变量输出回其他变量中,c#7.0配合元组可隐式调用(a,b,c,d) = instance,当然还是需要手动实现Deconstruct函数
- 静态成员、静态函数、静态构造函数(少用,耗性能)
- 静态类,字段、属性、函数都是静态的,不可实例化和扩展
- 扩展方法
- 为已封装好的类提供语法糖,作为对象的"实例方法",但查看CIL代码发现还是普通静态方法调用的。(unity中常用来扩展常用类的方便方法,建议积累)
- 若扩展方法的签名和被扩展类型中现有的签名匹配,则会被已有的覆盖,不会调用扩展的
- 常量字段,const,自动为静态字段,但不能加static,限制字面值
- readonly,只能用于字段,构造函数或者初始化时赋值,每个实例的值可以不同,不限制字面值(优先使用只读自动实现的属性)
- 分布类和分布方法,partial
继承
- 重写基类
- 使用virtual/override关键字,与c++不同的是,c#不支持隐式重写,必须注明这两个关键字
- 重写成员会使“运行时”调用派生得最远的实现
- c++构造期间不调用虚方法(毕竟子类成员都没构造),调用的是基类的实现,但c#不一样,保证了统一按照调用派生得最远的实现这个规则(可能在继承结构中有字段、属性未初始化),应该避免出现这种情况。
- new/override,对于virtual标记的方法子类重写必须标记这两个之一,不标记默认为new,且会抛出warning
- sealed密封,可修饰类或成员,修饰后不可继承/重写
- base字段访问基类成员,用法与this类似,还可用:base()调用基类构造
- 抽象类,无法实例化的类,abstract,常与接口配合做基类,还可定义抽象成员,由派生类实现
- 所有类都从System.Object派生,所以都有一些公用的方法(Equals/GetHashCode/GetType/ReferenceEquals/ToString/Finalize/MemberwiseClone)
- is操作符能验证基础类型,c#7.0后还新增了模式匹配语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22if(data is string text && text.length > 0)
{
data = Encrypt(text);
}
else if(data is null)
{
throw new ArgumentNullException(nameof(data));
}
// 等价于以下写法
if(data is string)
{
string text = (string) data;
if(text.length > 0)
{
data = Encrypt(text);
}
}
else if(data == null)
{
throw new ArgumentNullException(nameof(data));
} - switch语句也增加了模式匹配,以支持类等复杂类型,需要加额外的筛选语句
1
2
3
4
5
6switch(storage)
{
case UsbKey usbkey when usbkey.IsPluggedIn:
break;
case ...
} - as操作符,如可转会尝试转换,不可转返回null,不会引发异常。但由于7.0后is增加了模式匹配,相比之下有判断基础类型和空检查的功能,所以is优于as(前提是有模式匹配)