⚠️ 修饰符搭配注意项
public
公共访问修饰符,表示被 public 修饰的成员 可以在任何地方 被访问
谨慎搭配
private、protected、internal、private protected、protected internal
- private:被 private 修饰的成员 只能在其声明的类或结构中 被访问
- protected: 被 protected 修饰的成员 只能在包含它的类和派生类中 被访问的
- internal:被 internal 修饰的成员 只能在同一程序集中的所有类 被访问
- private protected:被 private protected 修饰的成员 只能在同一程序集中包含该成员的类或者包含该成员的嵌套类 被访问
- protected internal:被 protected internal 修饰的成员 只能在同一程序集中的所有类以及派生类 被访问
⚠️:以上 访问修饰符 和 public 修饰符 声明的 访问 范围不一致,不允许 直接搭配 使用 在同一成员
private
私有访问修饰符,被 private 修饰的成员 只能在其声明的类或结构中 被访问
谨慎搭配
internal 、 protected internal
- internal:被 internal 修饰的成员 只能在同一程序集中的所有类 被访问
- protected internal:被 protected internal 修饰的成员 只能在同一程序集中的所有类以及派生类 被访问
⚠️:以上 访问修饰符 和 public 修饰符 声明的 访问 范围不一致,不允许 直接搭配 使用 在同一成员
static
静态修饰符, 用于 声明 属于类型本身而不是 特定对象 的静态成员,即 成员属于类而不是实例
静态成员 实现 情况:
- 静态字段在类加载时被初始化
- 静态方法被第一次被调用的时候
- 静态构造函数 在第一次访问 静态成员或创建类的实例时 被 调用
谨慎搭配
abstract、 sealed、 virtual、 override
- abstract 用于指示抽象成员或抽象类,必须由派生类提供具体的实现
- 静态修饰符 修饰的 成员 是属于类的,不能交由派生类进行实例化或覆盖
- sealed 用于指示类或方法不能被继承或重写
- 静态修饰符 修饰的 成员 是属于类的,使用 sealed 修饰符 来阻止 静态成员 的继承 无意义
- virtual 用于指示可以在派生类中重写的虚拟成员
- 静态修饰符 修饰的 成员 是属于类的,使用 virtual 在派生类中 重写 无意义
- override 重写基类中的虚拟方法
- 使用 override 重写 的成员是先在基类中已存在,由基类派生重写而来,与静态成员属于类的定义有所违背,故 override 与 static 不能搭配在一起
readonly
readonly 用于表示只读字段,只能在声明或构造函数中赋值
谨慎搭配
sealed、abstract、virtual、override
- sealed 用于指示类或方法不能被继承或重写
- readonly 主要用于创建常量或者在构造函数中初始化某个值,确保该值在对象的生命周期内不会被改变,sealed 通常作用于类,readonly 通常作用于类的实例字段,而不是类本身,两者在类的层次上并不适合直接搭配使用
- abstract 用于指示抽象成员或抽象类,必须由派生类提供具体的实现
- abstract 要求 派生类 提供 具体的实现 且 抽象成员 在声明时 并 不提供默认值,而 readonly 要求字段在声明时或构造函数中初始化,且值在初始化后不能被修改,故 两者不能直接搭配
- virtual 用于指示可以在派生类中重写的虚拟成员
- virtual 允许在派生类中提供新的实现,而 readonly 要求字段的值在初始化后不能被修改,若virtual 要求在派生类中可能提供新的值或实现,则两者有冲突的地方,故不能直接搭配
- override 重写基类中的虚拟方法
- override 关键字要求提供一个新的实现,这就涉及到在派生类中可能改变原有实现的情况,readonly 修饰的字段表示只读字段,初始化后不能被修改,两者有冲突的地方,故不能直接搭配
abstract
abstract 用于指示抽象成员或抽象类,必须由派生类提供具体的实现
谨慎搭配
static、sealed、readonly、virtual
- static 表示静态成员,属于类不属于实例
- 静态修饰符 修饰的 成员 是属于类的,不能交由派生类进行实例化或覆盖,故两者的搭配不合理
- sealed 用于指示类或方法不能被继承或重写
- sealed 禁止 阻止类的派生,而 abstract 却要求类提供派生提供具体的实现,故两者不能直接组合
- readonly 用于表示只读字段,只能在声明或构造函数中赋值
- abstract 要求 派生类 提供 具体的实现 且 抽象成员 在声明时 并 不提供默认值,而 readonly 要求字段在声明时或构造函数中初始化,且值在初始化后不能被修改,故 两者不能直接搭配
- virtual 用于指示虚拟成员,可以在派生类中被重写
- virtual 成员表示有默认实现的,但可以在派生类中被重写,而 abstract 成员是没有实现的,需要在派生类中提供具体的实现,故 两者有冲突不能直接搭配使用
virtual
virtual 用于指示虚拟成员,可以在派生类中被重写
谨慎搭配
static、sealed、abstract、override
- static 表示静态成员,属于类不属于实例
- static 表示成员属于类 而不是 实例,不依赖于实例,virtual 表示成员可以在派生类中被重写,故使用 virtual 在派生类中 重写静态成员 无意义
- sealed 用于指示类或方法不能被继承或重写
- sealed 关键字用于禁止方法在进一步的派生类中被重写,而 virtual 则要求 成员 可以在派生类中被重写,它们的组合是不合理的
- abstract 用于指示抽象成员或抽象类,必须由派生类提供具体的实现
- virtual 成员表示有默认实现的,但可以在派生类中被重写,而 abstract 成员是没有实现的,需要在派生类中提供具体的实现,故 两者有冲突不能直接搭配使用
- override 重写基类中的虚拟方法
- override 要求提供一个新的实现 ,virtual 成员是有默认实现的,但可以在派生类中被重写,两者的功能有某一部分重合,不能直接搭配使用
override
重写基类中的虚拟方法
谨慎搭配
static、sealed、readonly、virtual、new
- static 表示静态成员,属于类不属于实例
- 使用 override 重写 的成员是先在基类中已存在,由基类派生重写而来,与静态成员属于类的定义有所违背,故 override 与 static 不能搭配在一起
- sealed 用于指示类或方法不能被继承或重写
- sealed 关键字用于禁止方法在进一步的派生类中被重写,而 override 要求方法必须是基类中声明的虚方法,因此它们的组合是不合理的
- readonly 用于表示只读字段,只能在声明或构造函数中赋值
- override 关键字要求提供一个新的实现,这就涉及到在派生类中可能改变原有实现的情况,readonly 修饰的字段表示只读字段,初始化后不能被修改,两者有冲突的地方,故不能直接搭配
- virtual 用于指示虚拟成员,可以在派生类中被重写
- override 要求提供一个新的实现 ,virtual 成员是有默认实现的,但可以在派生类中被重写,两者的功能有某一部分重合,不能直接搭配使用
- new 关键字用于隐藏基类成员,但不是通过重写的方式
- new 关键字用于隐藏基类成员,但不是通过重写的方式,override 则是重写基类中的虚拟方法,override和 new 在语义上相互排斥,因为它们表示对于相同成员的不同行为
sealed
用于指示类或方法不能被继承或重写
谨慎搭配
static、abstract、readonly、override
- static 表示静态成员,属于类不属于实例
- sealed 表示该类或方法是终结点,不可再被继承或重写。而 static 表示类型或方法属于类型而不是实例,不依赖于实例。这两者的语义相悖
- abstract 用于指示抽象成员或抽象类,必须由派生类提供具体的实现
- sealed 禁止 阻止类的派生,而 abstract 却要求类提供派生提供具体的实现,故两者不能直接组合
- readonly 用于表示只读字段,只能在声明或构造函数中赋值
- readonly 主要用于创建常量或者在构造函数中初始化某个值,确保该值在对象的生命周期内不会被改变,sealed 通常作用于类,readonly 通常作用于类的实例字段,而不是类本身,两者在类的层次上并不适合直接搭配使用
- override 重写基类中的虚拟方法
- sealed 关键字用于禁止方法在进一步的派生类中被重写,而 override 要求方法必须是基类中声明的虚方法,因此它们的组合是不合理的
static、readonly、const 的区别
可变性:
- static 是编译时常量,仅有 static 修饰符的 静态成员可以被二次修改
- readonly 字段是运行时常量,其值只能在声明或构造函数中初始化,之后不可更改
- const 字段是编译时常量,其值在声明时就被确定,且不能更改
初始化时机:
- static 字段的值可以在类的静态构造函数中初始化,或者在字段声明时初始化,
- readonly 字段的值可以在字段声明时或在构造函数中初始化,但一旦初始化后,就不能再修改
- const 字段必须在声明时初始化,并且只能使用编译时常量,且不能更改
作用域:
- static 字段是类级别的,所有类的实例共享相同的静态字段
- readonly 字段是实例级别的,每个类的实例都有自己的只读字段,但所有实例共享相同的值
- const 字段是类级别的,所有类的实例共享相同的常量值
性能影响:
- static 字段的值存储在堆上,且在应用程序生命周期内保持不变
- readonly 字段的值存储在堆上,且在实例生命周期内保持不变
- const 字段的值在编译时被直接嵌入到使用它的地方,没有额外的字段存储
⚠️: static 和 const 可以创建静态的 字段,但 readonly 不行,readonly 仅代表创建的字段是字段,并不意味在该字段就是静态的
virtual、override
virtual 和 override 是 C# 中用于实现方法重写的关键字
- virtual 一般用于基类,表示该成员可以在派生类中被重写
- override 一般用于派生类,表示该类要对基类中的虚方法进行重写
🤔️ 思考
IoC 环境中,为什么常用 readonly 呢?
- readonly 修饰的字段 规定 只能在声明时或构造函数内赋值
- 在声明时的赋值会在 构造函数完成前会被初始化,之后其值不可变
- 在构造函数赋值 则 允许在创建对象时提供运行时确定的值,并保障这个值是在对象初始化期间设置的唯一机会
- IoC 环境中,常用 readonly 来辅助 实现 依赖注入(即在定义一个 只读 属性并由构造函数赋值)
- 在依赖注入 时,服务通常会以只读属性的形式注入到类中。使用 readonly 保证这些服务只能在构造函数或对象初始化期间设置,并且在对象的整个生命周期内不会被修改
- 使用 readonly 可以防止在对象的其他方法中意外修改这些服务或属性,这样可以确保这些服务在对象创建后保持不变性
- 在 C# 中,readonly 字段的初始化是线程安全的一个保障,一旦字段被初始化,它就无可更改,确保数据安全的同时,也有助于防止多线程并发问题的出现
- IoC 使用 readonly 实现依赖注入也与 面向对象的 封装 特性 有关
- 封装本质上就是将对象内部的状态和实现的细节隐藏在 对象之外,并只提供公共接口访问
- 在IoC中,依赖注入的目标是将组件之间的依赖关系从组件自身解耦,让IoC容器负责管理这些依赖关系。通过使用 readonly 关键字,可以将依赖项注入到对象中,并确保这些依赖项在对象创建后不再被修改。符合了封装的思想,对象内部的状态(即依赖项)受到保护,且只能通过对象的公共接口访问
ps: 本文内容仅来自个人见解,若有错误之处还望斧正