文档资料

Class

  • 最常见的一种引用类型
class ClassName {...}

字段 Field

  • 是类或结构体的成员,它是一个变量。
class Octopus
{
	string name;
	public int age = 10;
}

readonly

  • readonly 修饰符防止字段在构造之后被改变。
  • readonly 字段只能在声明的时候被赋值,或在构造函数里被赋值。

字段初始化

  • 字段可以可选初始化。
  • 未初始化的字段有一个默认值。
  • 字段的初始化在构造函数之前运行。
  • 同时声明多个字段:
static readonly int legs = 8, eyes = 2;

属性

属性的声明

  • 属性的声明和字段很像,但多了一个 get set 块。

属性与字段的区别

  • 尽管属性的访问方式与字段的访问方式相同,但不同之处在于,属性赋予的实现者对获取和赋值的完全控制权。这种控制允许实践者选择任意所需的内部表示,不向属性的使用者公开其内部实现细节。

使用属性

  • get 访问器会在属性被读取的时候运行,必须返回一个该属性类型的值。
  • set 访问器会在属性被赋值的时候运行,有一个隐性的该类型的参数 value,通常你会把 value 赋给一个私有字段。

方法

  • 通常有一些语句组成,会执行某个动作
    • 参数
    • 返回类型
    • void
    • ref/out

Expression-Bodied 方法

int Foo(int x) { return x * 2; }

// Expression-Bodied
int Foo(int x) => x * 2;
void Foo(int x) => Console.WriteLine(x);

方法的签名

  • 类型内方法的签名必须唯一。
  • 签名:方法名、参数类型(含顺序,但与参数名称和返回类型无关)

方法的重载 Overload

  • 类型里的方法可以进行重载(允许多个同名的方法同时存在),只要这些方法的签名不同就行。
void Foo(int x) {...}
void Foo(double x) {...}
void Foo(int x, float y) {...}
void Foo(float x, int y) {...}
  • 反例:
void Foo(int x) {...}
float Foo(int x) {...}	// Compile-time error

void Goo(int[] x) {...}
void Goo(params int[] x) {...}	// Compile-time error
  • 参数是按值传递还是按引用传递,也是方法签名的一部分。
void Foo(int x) {...}
void Foo(ref int x) {...}	// OK
void Foo(out int x) {...}	// Compile-time error

构造函数

  • classstruct 上运行初始化代码。
  • 和定义方法差不多,但构造函数的名和类型名一致,返回类型也和类型一致,并且返回类型就省略不写了。
  • C#7+ 允许单语句的构造函数写成 expression-bodied 成员的形式。

构造函数的重载

  • classstruct 可以重载构造函数,可以有多个构造函数。
  • 一个构造函数调用另一个重载构造函数时使用 this
  • 当同一个类型下的构造函数 A 调用构造函数 B 时,B 先执行。
public class Wine
{
	public decimal price;
	public int year;
	
	public Wine(decimal price)
	{
		this.price = price;
	}
	
	// Wine(price) 先执行
	public Wine(decimal price, int year) : this(price)
	{
		this.year = year;
	}
}
  • 可以把表达式传递给另一个构造函数,但表达式本身不能使用 this 引用,因为这时候对象还没初始化,所以任何方法的调用都会失败。但是可以使用 static 静态方法。
public class Wine
{
	public decimal price;
	public int year;
	
	public Wine(decimal price)
	{
		this.price = price;
	}
	
	// 报错
	public Wine(decimal price, int year) : this(price, this.GetYear())
	{
		this.year = year;
	}
	
	public int GetYear() => 1988;
}
public class Wine
{
	public decimal price;
	public int year;
	
	public Wine(decimal price)
	{
		this.price = price;
	}
	
	// 可以用
	public Wine(decimal price, int year) : this(price, Wine.GetYear())
	{
		this.year = year;
	}
	
	public static int GetYear() => 1988;
}

无参构造函数

  • 对于 class,如果你没有定义任何构造参数的话,那么 C# 编译器会自动生成一个无参 public 构造函数。
  • 但是如果你定义了构造函数,那么这个无参构造函数就不会被生成了。

构造函数和字段的初始化顺序

  • 字段的初始化发生在构造函数执行之前。
  • 字段按照声明的先后顺序进行初始化。

public 构造函数

  • 构造函数可以不是 public 的。
public class Wine
{
	Wine()
	{
	}
	
	public static Wine GetInstance()
	{
		return new Wine();
	}
}

静态构造函数

  • 静态构造函数用来初始化静态成员。
  • 静态构造函数,每个类型执行一次。
  • 非静态构造函数,每个实例执行一次。
  • 一个类型只能定义一个静态构造函数。
    • 必须无参
    • 方法名与类型一致
class Test
{
	static Test()
	{
		Console.WriteLine("Type initialized");
	}
}
  • 在类型使用之前的一瞬间,编译器会自动调用类型的静态构造函数。
    • 实例化一个类型。
    • 访问类型的一个静态成员。
  • 只允许使用 unsafeextern 修饰符。
  • 如果静态构造函数抛出了未处理的异常,那么这个类型在该程序的剩余生命周期内将无法使用了。

静态字段与静态构造函数的初始化顺序

  • 静态字段的初始化器在静态构造函数被调用之前的一瞬间运行。
class Program
{
	static void Main()
	{
		Console.WriteLine(Foo.x);	// 3
	}
}

class Foo
{
	public static Foo instance = new Foo();	// 这里调用了构造函数,x 还没有被初始化,所以输出 0
	public static int x = 3;
	
	Foo()
	{
		Console.WriteLine(x);	// 0
	}
}
  • 如果类型没有静态构造函数,那么静态字段初始化器在类型被使用之前的一瞬间执行,或者更早,在运行时突发奇想的时候执行。
  • 静态字段的初始化顺序与他们的声明顺序一致。
class Foo
{
	public static int x = y;	// x = 0
	public static int y = 3;	// y = 3
}

静态类

  • 类也可以是静态的。
  • 其成员必须全是静态的。
  • 不可以有子类。
  • 例如
    • System.Console
    • System.Math

析构函数

  • Finalizer 是 class 专有的一种方法。
  • 在 GC 回收未引用对象的内存之前运行。
  • 其实就是对 objectFinalize() 方法重写的一种语法。

Deconstructor (C#7)

  • C#7 引入了 deconstructor 模式
  • 作用基本和构造函数相反,它会把反赋给一堆变量。
  • 方法名必须是 Desconstruct,有一个或多个 out 参数。
  • Deconstruct 可以被重载。
  • Deconstruct 可以是扩展方法。
class Rectangle
{
	public readonly float width, height;
	
	public Rectangle(float width, float height)
	{
		this.width = width;
		this.height = height;
	}
	
	public void Deconstruct(out float width, out float height)
	{
		width = this.width;
		height = this.height;
	}
}

class Test
{
	public static void Main(string[] args)
	{
		var rect = new Rectangle(3, 4);
		
		// Deconstruction
		// 写法一
		(float width, float height) = rect;	// Deconstruction
		// 写法二
		var (width, height) = rect;
		// 写法三
		float width, height;
		(width, height) = rect;
		// 写法四
		rect.Deconstruct(out float width, out var height);
		// 写法五
		float width, height;
		rect.Deconstruct(out width, out height);

		
		Console.WriteLine(width + ", " + height);	// 3, 4
	}
}

析构函数

对象初始化器

索引器

  • 索引器提供了一种可以访问封装的列表值或字典值的 class/struct 的元素的一种自然的语法。
  • 语法很像使用数组实用的语法,但是这里的所有参数可以是任意类型的。
  • 索引器和属性拥有同样的修饰符。
  • 实现索引器需要定义一个 this 属性,并通过中括号指定参数。
  • 一个类型可以声明多个索引器,它们的参数类型可以不同。
  • 一个索引器可以有多个参数。
public string this [int arg1, string arg2]
{
	get {...};
	set {...};
}
  • 如果不写 set 访问器,那么这个索引器就是只读的。
  • 在 C#6 以后,也可以使用 expression-bodied 的写法。
public string this[int wordNum] => words[wordNum];

nameof 操作符 (C#6)

  • nameof 操作符会返回任何符号(类型、成员、变量。。。)的名字(string 类型)。
  • 利于重构
int count = 123;
string name = nameof(count);	// name is "count"
string name = nameof(StringBuilder.Length);	// name is "Length"