field
是class的成员,是一个成员
readonly修饰符
- 防止字段在构造之后被改变
- readonly字段只能在声明的时候被赋值,或在构造函数里被赋值
字段初始化
- 字段可以初始化
- 未初始化的字段有一个默认值
- 字段的初始化在构造函数之前运行
overload
类型里的方法可以进行重载(允许多个同名的方法同时存在),只要这些方法的签名不同就行
void Foo (int x) {}
void Foo(double x) {}
void Foo(int x, double y) {}
构造函数
- 运行class或struct的初始化代码
- 和方法差不多,方法名和类型一致,返回类型也和类型一致,但不写了
- C#7,允许单语句的构造函数写成expression-bodied成员的形式
public class Panda
{
string name;
public Panda(string n) => name = n;
}
构造函数的重载
- class和struct可以重载构造函数
- 调用重载函数时使用this
- 当同一个类型下的构造函数A调用构造函数B的时候,B先执行
-
可以把表达式传递给另一个构造函数,但表达式本身不能使用this引用,因为这时候对象还没有被初始化,所以对象上任何方法的调用都会失败。但是就可以时候用static 方法
image.png
public class Wine
{
public decimal Price;
public int Year;
public Wine(decimal price)
{
Price = price;
}
public Wine(decimal price, int year) : this(price)
{
Year = year;
}
}
public Wine (decimal price, DateTime date): this(price, Wine.GetYear())
{
Date = date
}
public static int GetYear(){return 1998;}
构造函数和字段的初始化顺序
- 字段的初始化发生在构造函数执行之前
- 字段按照声明的前后顺序进行初始化
非pulic的构造函数
- 构造函数可以不是public的
class Test
{
public static void Main(string[] args)
{
var wine = Wine.CreateInstance();
}
}
public class Wine
{
Wine(){}
public static Wine CreateInstance(){
return new Wine();
}
}
Deconstructor
- 作用基本和构造函数相反,它会把字段反赋给一堆变量
- 方法名必须是Deconstruct,有一个或者多个out参数
- Deconstruct 可以被重载
class Rectangle
{
public readonly float Width, Height;
public Rectangle(float width, float height){
Width = width;
Height = height;
}
public void Deconstruct(out float width, out float height){
width = Width;
height = Height;
}
}
class Test
{
public static void Main(string[] args){
var rect = new Rectangle(3,4);
//rect.Deconstruct(out var width, out var height);
var (width, height) = rect;
Console.WriteLine(width + " " + height); //3,4
}
}
- Deconstruct可以使用扩展方法
public static class Extension
{
public static void Deconstruct(this Rectangle rect, out float width, out float height)
{
width = rect.Width;
height = rect.Height;
}
}
...
var rect = new Rectangle(3,4);
Extensions.Deconstruct(rect, out var width, out var height);
...
对象初始化器
- 对象任何可访问的字段/属性在构建之后,可通过对象初始化器直接为其进行设定值
public class Bunny
{
public string Name;
public bool LikesCarrots;
public bool LikesHumans;
public Bunny () {}
public Bunny(string n) {Name = n;}
}
...
Bunny b1 = new Bunny{ Name ="Bo, LikesCarrots = true, LikesHumans = false};
Bunny b2 = new Bunny("Bo") { LikesCarrots = true, LikesHumans = false}
如果不适用初始化器,上例中的构造函数也可以使用可选参数
public Buuny(string name, bool likesCarrots = false, bool likesHumans = false)
{
Name = name;
LikesCarrots = likesCarrots;
LikesHumans = likesHumans
}
可选参数方式
- 优点: 可以让Bunny类的字段/属性只读
- 缺点: 每个可选参数的值都被嵌入到了calling site, C#会把构造函数的调用翻译成
Bunny b1 = new Bunny ("BO", true, false);
This 引用
public class Panda
{
public Panda Mate;
public void Marry (Panda partner)
{
Mate = partner;
partner.Mate = this;
}
}
- this 引用可以让你把字段与本地变量或参数区分开
- 只有class/struct的非静态成员才可以使用this
属性 Property
- 从外边来看,属性和字段很像。 但是从内部看,属性含有逻辑,就像方法一样
- 属性的声明和字段很像,但是多了一个get set 块
public class Stock
{
decimal currentPrice;
public decimal CurrentPrice
{
get { return currentPrice;}
set { currentPrice = value;}
}
}
属性的get set
- get/set 代表属性的访问器
- get访问器会在属性被读取的时候运行,必须返回一个该属性类型的值
- set访问器会在属性被赋值的时候运行,有一个隐式的该类型的参数value,通常你会把value赋给你个私有字段
- 尽管属性的访问方式与字段的访问方式相同,但不同之处在于,属性赋予了实现者对获取和赋值完全控制权。这种控制允许实现者选择任意所需的内部表示,不向属性的使用者公开其内部实现细节
只读和计算的属性
- 如果属性只有get访问器,那么它是只读的
- 如果只有set访问器,那么它是只写的
- 属性通常拥有一个专用的幕后字段,这个幕后字段用来存储数据
自动属性
- 属性最常见的一种实践就是:getter和setter只是对private field进行简单直接的读写
- 自动属性声明就告诉编译器来提供这种实现
public class Stock
{
...
public decimal CurrentPrice { get; set; }
}
- 编译器会自动生成一个私有的幕后字段,其名称不可引用
- set访问器也可以是private或protected
- 从c#6开始,你可以为自动属性添加属性初始化器
public decimal CurrentPrice {get; set;} =123;
- 只读的自动属性也可以使用(只读自动属性也可以在构造函数里被赋值)
public int Maxinum {get;} = 999;
- get set访问器可有不同的访问级别, 典型用法,public get, internal/private set
public class Foo
{
private decimal x;
public decimal x {
get { return x;}
private set {x = Math.Round(value, 2)}
}