在C#中,一个类型内部可以定义多种成员:常量、字段、实例构造器、类型构造器(静态构造器)、方法、操作符重载、转换操作符、属性、事件、类型。
类型的可见性有public和internal(默认)两种,前者定义的类型对所有程序集中的所有类型都可见,后者定义的类型只对同一程序集内部的所有类型可见:
public class PublicClass { } //所有处可见 internal class ExplicitlyInternalClass { } //程序集内可见 class ImplicitlyInternalClass { } //程序集内可见(C#编译器默认设置为internal)
成员的可访问性(按限制从大到小排列):
- Private只能由定义成员的类型或嵌套类型中方法访问
- Protected只能由定义成员的类型或嵌套类型或派生类型中方法访问
- Internal 只能由同程序集类型中方法访问
- Protected Internal 只能由定义成员的类型或嵌套类型或派生类型或同程序集类型中方法访问(注意这里是或的关系)
- Public 可由任何程序集中任何类型中方法访问
在C#中,如果没有显式声明成员的可访问性,编译器通常默认选择Private(限制最大的那个),CLR要求接口类型的所有成员都是Public访问性,C#编译器知道这一点,因此禁止显式指定接口成员的可访问性。同时C#还要求在继承过程中派生类重写成员时,不能更改成员的可访问性(CLR并没有作这个要求,CLR允许重写成员时放宽限制)。
静态类
永远不需要实例化的类,静态类中只能有静态成员。在C#中用static这个关键词定义一个静态类,但只能应用于class,不能应用于struct,因为CLR总是允许值类型实例化。
C#编译器对静态类作了如下限制:
- 静态类必须直接从System.Object派生
- 静态类不能实现任何接口(因为只有使用类的一个实例才能调用类的接口方法)
- 静态类只能定义静态成员(字段、方法、属性、事件)
- 静态类不能作为字段、方法参数或局部变量使用
- 静态类在编译后,会生成一个被标记为abstract和sealed的类,同时编译器不会生成实例构造器(.ctor方法)
分部类、结构和接口
C#编译器提供一个partial关键字,以允许将一个类、结构或接口定义在多个文件里。
在编译时,编译器自动将类、结构或接口的各部分合并起来。这仅是C#编译器提供的一个功能,CLR对此一无所知。
常量就是代表一恒定数据值的符号,比如我们将圆周率3.12415926定义成名为PI的常量,使代码更容易阅读。而且常量是在编译时就代入运算的(常量就是一个符号,编译时编译器就会将该符号替换成实际值),不会造成任何性能上的损失。但这一点也可能会造成一个版本问题,即假如未来修改了常量所代表的值,那么用到此常量的地方都要重新编译(我个人认为这也是常量名称的由来,我们应该将恒定不变的值定义为常量,以免后期改动时产生版本问题)。下面的示例也验证了这一点,Test1和Test2方法内部的常量运算在编译后,就已经运算完成。
从上面示例,我们还能看出一点:常量key和value编译后是静态成员,这是因为常量通常与类型关联而不是与实例关联,从逻辑上说,常量始终是静态成员。但对于在方法内部定义的常量,由于作用域的限制,不可能有方法之外的地方引用到这个常量,所以在编译后,常量被优化了。
字段字段是一种数据成员,在OOP的设计中,字段通常是用来封装一个类型的内部状态,而方法表示的是对这些状态的一些操作。
在C#中字段可用的修饰符有
- Static 声明的字段与类型关联,而不是与对象关联(默认情况下字段与对象关联)
- Readonly 声明的字段只能在构造器里写入值(可以通过反射修改)
- Volatile 声明的字段为易失字段(用于多线程环境)
这里要注意的是将一个字段标记为readonly时,不变的是引用,而不是引用的值。示例:
class ReadonlyField { //chars 保存的是一个数组的引用 public readonly char[] chars = new char[] { 'A', 'B', 'C' }; void Main() { //以下改变数组内存,可以改成功 chars[0] = 'X'; chars[1] = 'Y'; chars[2] = 'Z'; //以下更改chars引用,无法通过编译 chars = new char[] { 'X', 'Y', 'Z' }; } }
CLR支持两种属性:无参属性和有参属性(C#中称为索引器)。
面向对象设计和编程的重要原则之一就是数据封装,这意味着字段(封装对象的内部状态)永远不应该公开。因此,CLR提供属性机制来访问字段内容(VS中输入propfull加两次Tab会为我们自动生成字段和属性的代码片断)。
下面的示例中,Person对象内部有一个表示年龄的字段,如果直接公开这个字段,则不能保存外部不会将age设置为0或1000,这显然是没有意义的(也破坏了数据封装性),所以通过属性,可以在操作字段时,加一些额外逻辑,以保证数据的有效性。
class Person { //Person对象的内部状态 private int age; //用属性来安全地访问字段 public int Age { get { return age; } set { if (value > 0 && value关注打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录