这一篇,我们深入理解一下 C# 中的类。
类的成员
上篇我们说过了 2 个类成员: 字段和方法。这一篇我们介绍除了事件和运算符之外的成员。
成员修饰符的顺序
成员声明语句有下列部分组成: 核心声明,可选的修饰符和可选的特性(attribute)。
[特性] [修饰符] 核心声明;
我们之前说过的 public 和 private 都是修饰符,还有 static 和 const 也都是修饰符。
实例成员
成员可以关联到一个类的实例,也可以关联到类的整体,默认情况下,成员被关联到一个实例,这些成员称为实例成员。
class MyClass { public int a; }
class Program { static void Main() { var cls1 = new MyClass(); var cls2 = new MyClass(); cls1.a = 10; cls2.a = 20; Console.WriteLine("{0} -- {1}", cls1.a, cls2.a); } }
|
静态成员
除了实例成员,类还可以拥有静态成员。静态成员被所有实例共享,访问内存同一位置,使用 static 修饰符将成员声明为静态。
class MyClass { int Mem1; static int Mem2; } class Program { static void Main() { var cls1 = new MyClass(); var cls2 = new MyClass(); cls1.Mem1 = 10; MyClass.Mem2 = 1; cls2.Mem1 = 20; MyClass.Mem2 = 2; Console.WriteLine("{0} -- {1}", cls1.Mem1 , cls2.Mem1 ); Console.WriteLine(MyClass.Mem2); } }
|
类外访问静态成员
与实例成员一样在类外使用点运算符来访问静态成员,只不过是使用类名来访问。
静态函数成员不能访问实例成员,他们可以访问其他静态成员。
class X { public static int A; public static void PrintA() { Console.WriteLine(A); } }
|
除了常量和索引器,其他成员都可以是静态成员。
成员常量
如同本地常量一样,只是成员常量被声明在类中:
class MyClass { const int IntVal = 5; }
|
成员常量表现得很像静态值,他们对每个实例都是可见的,但是他们没有自己的存储位置,只是在编译期被替换。类似于 C 语言的 #define。
class MyClass { public const double PI = 3.14156; }
class Program { static void Main() { Console.WriteLine(MyClass.PI); } }
|
注意: 虽然成员常量表现得像静态量,但是不能使用 static 修饰符修饰。
属性
C# 中的属性非常像 Swift 中的计算属性。他们在使用时非常像字段,但是和字段不同的是,他们是函数成员,可以执行代码。
int MyVal { get { }
set { }
}
|
set 访问器拥有一个隐式的名为 value 的值参数,与属性类型相同,返回值为 void。
get 访问器没有参数,有一个与属性相同类型的返回值。
我们要注意: 属性本身没有任何存储,通常要有一个关联字段作为存储。
private int _intVal; public int IntVal { set { _intVal = value; }
get { return _intVal; } }
|
我们还可以设置只读属性(只有 get 方法)或者只写属性(只有 set 方法),但是两个访问器至少要定义一个,否则编译器会报错。
C# 还提供了自动属性实现,不需要提供关联字段,也不需要提供访问器的方法体,get 和 set 后直接跟分号。
class MyClass { public int IntVal {get; set;} }
|
属性也可以被声明为静态,与静态字段一样,使用类名访问静态属性。
实例构造函数
构造函数是一个特殊的方法,他在创建类实例时执行,用于初始化实例状态。通常声明为 public。
它的特点是:
class MyClass { public MyClass() { } }
|
构造函数也可以被重载,带有不同的参数:
class Class1 { int Id; string Name; public Class1() { Id = 1; Name="1ess"; } public Class1(int Id) { this.Id = Id; Name="1ess"; } public Class1(string Name) { Id = 1; this.Name=Name; } }
|
如果我们没有为类提供构造函数,那么编译器会隐式提供一个无参构造函数,方法体为空。
如果我们显示提供了一个构造函数,那么就不会为我们创建一个隐式的无参构造函数。
class Class2 { int Id; string Name; public Class2(int Id) { this.Id = Id; Name="1ess"; } public Class2(string Name) { Id = 1; this.Name=Name; } }
class Program { static void Main() { Class2 cls2 = new Class2(); } }
|
静态构造函数
这一点与很多语言不同,构造函数也可以是静态的,用来初始化静态成员。调用时机是:
静态构造函数的特点是:
- 名称必须与类名相同
- 不能有返回值
- 只能有一个静态构造函数
- 不能有参数
- 不能有访问修饰符
class Class1 { static Class1() { } }
|
在静态构造函数不能访问实例成员,我们也不能显示的调用静态构造函数。
对象初始化语句
对象初始化语句扩展创建语法,在表达式的尾部放置一组成员初始化语句,允许我们在创建实例时,设置字段和属性值。
该语法有两种形式:
new TypeName(ArgList) { FieldOrProp = InitVal, FieldOrProp = InitVal, ...}; new TypeName { FieldOrProp = InitVal, FieldOrProp = InitVal, ...};
|
要注意: 初始化列表中的字段和属性都是可访问的(public),并且初始化语句执行于构造方法之后。
this 关键字
this 是对当前实例的引用,只能被用于:
this 的目的用于区分成员和本地变量:
class Class1 { int a; public void Say(int a) { this.a = a; } }
|
不推荐参数和字段同名。
分部类和分部类型
类的声明可以分割成几个分部类声明:
- 每个分部类都含有一些类成员声明
- 每个局部声明必须标记为 partial class
partial class MyClass { int a; }
partial class MyClass { int b; }
|
除了分部类,还有分部结构和分部接口,我们之后说。
分部方法
分部方法是声明在分部类的不同部分的方法。
分部方法两部分如下:
分部方法的特征:
- 声明部分和实现部分的返回值和方法签名必须一致,并且,返回值必须是 void
- 不能有访问修饰符修饰,也就是分部方法隐式私有
- 不能有输出参数
- 方法声明和方法实现之前要有 partial 修饰
partial class MyClass1 { partial void Add(int a, int b); public void PrintSum(int a, int b) { Add(a, b); } }
partial class MyClass1 { partial void Add(int a, int b) { Console.WriteLine($"a + b = {a + b}"); } }
class Program { static void Main() { var cls = new MyClass1(); cls.PrintSum(5, 6); } }
|