欢迎光临散文网 会员登陆 & 注册

第 81 讲:C# 2 之静态类

2022-01-19 21:04 作者:SunnieShine  | 我要投稿

有些时候,我们可能需要规划出一个单独的类型来存储一些静态成员。为什么这么做?举个例子,我们可能会列举一个数学操作类型,里面装下了一系列的函数专门用于表示和计算数学操作有关的东西,比如正弦函数、余弦函数、向下取整等等。

如果我们零散放置它们,显然不方便整理,于是 C# 2 为这种“工具类”单独定义了一种语法机制:静态类(Static Class)。

Part 1 类也可以修饰 static

在之前早期我们就提及过一个概念叫做工具类型。工具类型里存储的就是这类成员,它们只用来计算和执行固定功能的操作,它不用于实例化,因此这样的类型也经常被 sealed 修饰过,而构造器也经常被 private 修饰符修饰,防止外部实例化,即这样:

但经常这么书写代码也不方便,因为你每次都得自己创建私有的无参构造器,挺麻烦的。

这里说的无参构造器指的是实例构造器,而不是静态构造器。都知道静态构造器的语法其实就在构造器前面加一个 static 修饰符,并没有参数,还不能加别的访问修饰符。而这个构造器不是我们这里提及的内容。我们也没有打算说这个,因为它不是我们这里要对比的内容,所以这里我们说的无参构造器指的是无参实例构造器。

C# 2 允许静态类的机制。它的功能就是为了解决这种情况。我们只需要将类型修饰符从 sealed 改成 static,然后删除私有无参构造器即可:

是的,类型直接用 static 修饰了。下面我们来说说这种类型的相关细节,以及解决的问题。

Part 2 静态类的细节

静态类的功能就是解决和简化工具类型的语法的。下面我们来说说它们的实现细节。

2-1 只能修饰到类上

静态类静态类,顾名思义,就是静态的。是的,它只能用于类类型上,而结构、接口、委托或者枚举,都是不能带有 static 修饰符的。原因很简单:接口、委托和枚举类型没有无参构造器的概念,而结构类型的无参构造器是永远存在的,因此只有类类型的无参构造器可以自己定义,因此,静态类只针对于类类型使用。

仔细思考一下,确实是这样的,对吧?工具类型也不会用别的类型来表达,也没人考虑会用接口来做吧?要知道接口里是不能带有方法执行逻辑的;而委托和枚举就更不可能了。

2-2 静态类里没有无参构造器的概念

静态类的语法里,我们只用了一个修饰符来完成;而私有构造器直接被删除了。问题在于,这个构造器去哪里了?

答案可能让你大吃一惊。C# 做了一个大胆的决定:静态类不允许你自定义无参构造器,也不自动产生默认的无参构造器,说白了,静态类里没有无参构造器这个概念。是的,静态类你无法书写无参构造器的语法,也不允许你创建无参构造器,而在底层,C# 的静态类里也不包含任何的构造器。是的,这种类型不产生默认的无参构造器,也不会允许你创建无参构造器。原因很简单,因为静态类的诞生就是防止你去实例化的,而它的目的已经达到了:编译器自身就可以限制你使用构造器语法来实例化一个静态类的实例,所以,在底层的代码里,静态类哪怕真的没有无参构造器也不会引起任何的运行时的严重错误。

2-3 const 修饰符

C# 早就有 const 修饰符,它表示这个数据自身在编译之前就固定产生下来,并且运行时不可变动,只允许读取的特殊量。它用来防止用户在后期篡改变更对象的具体内容和数值。不过,因为 const 设计的复杂性和设计原则,const 修饰符只能修饰在基本数据类型和枚举类型里,别的任何自定义类型全部不能使用这个修饰符。

const 作为编译之前就固定下来的“常量”信息,它一定是静态存在的,因此 const 修饰的对象从不使用 static 修饰符修饰,因为它的存在就是自带静态的概念的。因此,使用 const 修饰的字段可以存在于静态类里。

这个概念之前很少说过(实际上就是没说过),因为它的条件极为苛刻,很多用户也经常将它和 static readonly 组合修饰符混淆起来,搞得完全分不清。

static readonly 可以是任何的表达式计算结果,它的存在是运行时只读,即侧重的是运行时不可修改;而 const 则指的是编译器在编译代码期间就已经固定和计算出结果了,它更侧重的是编译时常量的概念。从另一个角度来说,static readonly 的范围更广泛,因为它只是防止用户去篡改它自身的数值;但 const 性能要更好一些。

2-4 静态类是隐式密封的

这也是一句废话。静态类只是拿来给我们提供工具操作的内容,所以它自身是不含有任何的派生和继承机制的。所以,一旦你修饰了 static 到类上去,那么这个类型就无法派生出新的类型,因为,派生和继承机制是跟实例有关的概念。

2-5 静态类只能包含静态成员

这显然也是一个废话。不过,与其说它是废话,不如认为这是从工具类型里抽取出来的一条基本规定。一个工具类型本来就只能用来处理和操作一些功能,而我总不能每次还得单独实例化一下才去处理吧。所以,工具类型里肯定是不存储实例成员的。那,“不存储实例成员”不就是说“只包含静态成员”吗?

所以,静态类的目的就是为了规范化工具类型的规则,所以它肯定也只能存储静态的成员。当然,工具类型里可以包含实例成员,这不是编译器限制的,而是我们自身实现和设计数据类型的约定和约定俗成的规范;而静态类不含有实例成员,是编译器限制。相反,你往里加上实例成员肯定会受到编译器错误信息。

这也是为什么类用的是 static 这个单词来作为这个语义限制的原因——只有静态成员的类型可不就是静态类型吗?

Part 3 尽量将 Program 类静态化

C# 一直有一个比较晦涩难懂的概念:Program 类型。Program 类型不是一个固定的名字,这里想指的是给 Main 方法提供包裹的一个没有啥特殊含义的类型;而之所以叫它 Program 类型,是因为系统最初生成的项目模板代码里,包裹 Main 方法就用的是 Program 这个单词。所以我们也经常把 Program 这个词和“包裹 Main 方法的类型”这个概念绑定起来。这个类型一般不用来实例化,因为它是受到 CLR 系统自身调用,所以跟它是否能够实例化无关,因为程序第一个执行的方法就是 Main 方法,而它自身就是静态、私有的,而照样能被调用。

正是因为这个原因,包裹封装 Main 方法的类型没有任何理由去实例化或者做别的操作和行为;相反,你真的要让这个类型发展出别的操作的话,我建议你再单独创建别的类型来完成这些任务,然后一直保持 Program 类型只含 Main 方法一个成员声明。那么,既然它一般不拿来实例化,也不用于继承和派生,更不放别的成员,那么我们最安全和最合适的方式是,给 Program 类型加上 static 修饰符。是的,把它搞成静态类。

Part 4 总结

好久没有总结了。下面我们来总结一下,C# 目前对类的修饰符的所有可能情况:

  • 访问修饰符:publicinternalprotectedprivateprotected internal

  • 继承修饰符:sealedabstract

  • 静态修饰符:static

是的,这次语法拓展,我们又多了一个修饰符 static


第 81 讲:C# 2 之静态类的评论 (共 条)

分享到微博请遵守国家法律