声明
本文内容来自微软 MVP solenovex 的视频教程——真会C#?- 第3章 创建类型,大致和第 4 课—— class - 常量、静态构造函数和类、终结器、局部类和方法、nameof 对应。可在 GitHub 中查看 C# 视频教程的配套PPT
本文主要包括以下内容:
- 常量
- 静态构造函数
- 静态类
- 终结器(Finalizers)
- 局部类型
- 局部方法
- nameof
常量
一个值不可以改变的静态字段,在编译时值就已经定下来了。任何使用常量的地方,编译器都会把这个常量替换为它的值。常量的类型可以是内置的数值类型、bool、char、string 或 enum。使用 const 关键字声明,声明的同时必须使用具体的值来对其初始化。
public class Test
{
public const string Message = "Hello World";
}
常量与静态只读字段
常量比静态只读字段更严格:
可使用的类型
字段初始化的语义上
常量是在编译时进行值的估算的。
public static double Circumference (double radius)
{
return 2 * System.Math.PI * radius;
}
// is compiled to:
public static double Circumference (double radius)
{
return 6.2831853071795862 * radius;
}
注意:当值有可能改变,并且需要暴露给其它 Assembly 的时候,静态只读字段是相对较好的选择
public const decimal ProgramVersion = 2.3;
如果 Y Assembly 引用了 X Assembly 并且使用了这个常量,那么在编译的时候,2.3 这个值就会被固化于 Y Assembly 里。这意味着,如果后来X重编译了,这个常量变成了 2.4,如果 Y 不重新编译的话,Y 将仍然使用 2.3 这个值,直到 Y 被重新编译,它的值才会变成 2.4。静态只读字段就会避免这个问题的发生。
本地常量
方法里可以有本地的常量。
static void Main()
{
const double twoPI = 2 * System.Math.PI;
...
}
静态构造函数
静态构造函数,每个类型执行一次,非静态构造函数,每个实例执行一次。一个类型只能定义一个静态构造函数。必须无参,方法名与类型一致。
class Test
{
static Test() { Console.WriteLine ("Type Initialized"); }
}
在类型使用之前的一瞬间,编译器会自动调用类型的静态构造函数,实例化一个类型
,访问类型的一个静态成员。静态构造函数只允许使用 unsafe 和 extern 修饰符。
注意:如果静态构造函数抛出了未处理的异常,那么这个类型在该程序的剩余生命周期内将无法使用了。
初始化顺序
静态字段的初始化器在静态构造函数被调用之前的一瞬间运行,如果类型没有静态构造函数,那么静态字段初始化器在类型被使用之前的一瞬间执行,或者更早,在运行时突发奇想的时候执行。静态字段的初始化顺序与它们的声明顺序一致。
class Foo
{
public static int X = Y; // 0
public static int Y = 3; // 3
}
class Program
{
static void Main() { Console.WriteLine (Foo.X); } // 3
}
class Foo
{
public static Foo Instance = new Foo();
public static int X = 3;
Foo() { Console.WriteLine (X); } // 0
}
静态类
类也可以是静态的,其成员必须全是静态的。不可以有子类。例如:
System.Console
System.Math
终结器(Finalizers)
Finalizers 是 class 专有的一种方法。在 GC 回收未引用对象的内存之前运行,其实就是对 object 的 Finalize() 方法重写的一种语法。
class Class1
{
~Class1()
{
...
}
}
// 编译器生成:
protected override void Finalize()
{
...
base.Finalize();
}
// C# 7可以这样写:
~Class1() => Console.WriteLine ("Finalizing");
局部类型
允许一个类型的定义分布在多个地方(文件)。典型应用:一个类的一部分是自动生成的,另一部分需要手动写代码。
// PaymentFormGen.cs - auto-generated
partial class PaymentForm { ... }
// PaymentForm.cs - hand-authored
partial class PaymentForm { ... }
每个分布的类都必须使用 partial 来声明,下面这个例子就会报错:
partial class PaymentForm {}
class PaymentForm {}
每个分布类的成员不能冲突,不能有同样参数的构造函数。各分布类完全靠编译器来进行解析:每个分布类在编译时必须可用,且在同一个Assembly里。如果有父类,可以在一个或多个分布类上指明,但必须一致。每个分布类可以独立的实现不同的接口,编译器无法保证各分布类的字段的初始化顺序。
partial-class.png局部方法
局部类型可以有局部方法,自动生成的分布类里可以有局部方法,通常作为“钩子”使用,在另一部分的局部方法里,我们可以对这个方法进行自定义。
partial class PaymentForm // In auto-generated file
{
...
partial void ValidatePayment (decimal amount);
}
partial class PaymentForm // In hand-authored file
{
...
partial void ValidatePayment (decimal amount)
{
if (amount > 100)
...
}
}
局部方法由两部分组成:定义 和 实现。定义部分通常是生成的,实现部分通常是手动编写的。局部方法只有定义,没有实现,那么编译的时候该方法定义就没有了,调用该方法的代码也没有了。这就允许自动生成的代码可以自由的提供钩子,不用担心代码膨胀。局部方法必须是 void
,并且隐式 private
的。
nameof
nameof 操作符会返回任何符号(类型、成员、变量…)的名字(string),利于重构。
int count = 123;
string name = nameof (count); // name is "count"
string name = nameof (StringBuilder.Length); // Length
nameof (StringBuilder) + "." + nameof (StringBuilder.Length);
参考
Constants (C# Programming Guide)
Static Constructors (C# Programming Guide)
Static Classes and Static Class Members (C# Programming Guide)
Finalizers (C# Programming Guide)
网友评论