六、类型和成员基础(Type and Member Basics)
CLR #类型 #成员
1. 类型的基本成员
在 C# 中,一个类型(class 或 struct)可以定义以下成员:
- 常量(Constant) - 只读,编译期确定
- 字段(Field) - 存储数据,可以是
static或instance - 构造函数(Constructor) - 初始化实例或类型
- 方法(Method) - 操作逻辑,支持 静态/实例/虚拟
- 运算符重载(Operator Overload) - 定义
+,-等运算符 - 转换运算符(Conversion Operator) - 隐式/显式转换
- 属性(Property) - 访问字段的封装方式
- 事件(Event) - 观察者模式,通知机制
- 嵌套类型(Nested Type) - 在类内部定义类
2. 可见性(Type Visibility)
类型的可见性(Visibility) 决定了它可以在哪些地方被访问:
public class PublicType {} // 任何地方可访问
internal class InternalType {} // 仅当前程序集可访问
class DefaultType {} // 默认 internal
public:可被所有程序集访问internal:仅当前程序集内可访问(默认)private(嵌套类型可用):仅定义的类内部可访问
✅ 最佳实践
- 默认使用
internal,避免过度暴露 - 只有真正需要跨程序集访问时才使用
public
3. 成员访问修饰符
C# 提供以下 成员访问控制:
| 修饰符 | 说明 | 适用范围 |
|---|---|---|
private |
仅本类内部可访问 | 最严格,推荐 |
protected |
仅派生类可访问 | 继承关系 |
internal |
仅同一程序集可访问 | 组件内共享 |
protected internal |
继承 + 同一程序集 | 常用于库 |
public |
任何地方都可访问 | 最开放,谨慎使用 |
4. 静态类(Static Class)
特点
- 不能被实例化
- 只能包含
static成员 - 不能继承,也不能被继承
- 自动标记
sealed
public static class MathHelper {
public static double Square(double x) => x * x;
}
Console.WriteLine(MathHelper.Square(5)); // 输出 25
✅ 应用场景
- 工具类(如
Math,Console) - 只包含静态方法的
Helper类
5. Partial 类(分部类)
作用
- 多人协作开发:不同文件定义同一个类
- 代码生成工具(如 WinForms 设计器)
- 逻辑分离(如
DataLayer和BusinessLayer)
// File1.cs
public partial class User {
public string Name { get; set; }
}
// File2.cs
public partial class User {
public int Age { get; set; }
}
✅ 注意
partial仅影响代码组织,CLR 仍然视为同一个类- 必须在同一程序集、同一命名空间内
6. 组件化 & 版本控制
现代软件开发 依赖组件化(Component-Based Programming):
-
组件 = 程序集(Assembly)
-
.NET 版本号格式
:
major.minor.build.revision1.2.3.4major(大版本) - 重大变更minor(小版本) - 兼容更新build(构建号) - 修复revision(修订号) - 细微调整
程序集版本控制
[assembly: AssemblyVersion("1.2.3.4")]
- 组件更新时必须保证向后兼容
sealed避免错误继承override避免破坏继承层次
7. 多态(Polymorphism)和虚方法
C# 提供 5 个关键字影响版本控制
| 关键字 | 作用 | 适用场景 |
|---|---|---|
abstract |
强制子类实现 | 抽象基类 |
virtual |
允许子类重写 | 需扩展的 API |
override |
重写基类方法 | 继承 |
sealed |
禁止继承 | 确保安全 |
new |
隐藏基类方法 | 需特别处理 |
✅ 虚方法调用
public class Base {
public virtual void Show() => Console.WriteLine("Base");
}
public class Derived : Base {
public override void Show() => Console.WriteLine("Derived");
}
Base obj = new Derived();
obj.Show(); // 输出 "Derived"
- 虚方法调用动态绑定
- 避免
new隐藏,优先使用override
8. IL 层面方法调用
CLR 提供 两种调用方式:
| 指令 | 适用范围 | 特点 |
|---|---|---|
call |
静态/非虚方法 | 直接调用 |
callvirt |
虚方法 | 确保多态(包括 null 检查) |
示例
Object o = new Object();
o.GetHashCode(); // `callvirt` (因为 GetHashCode 是虚方法)
IL_000b: ldloc.0
IL_000c: callvirt instance int32 System.Object::GetHashCode()
✅ 优化建议
- 非虚方法使用
call提高性能 - 仅在需要多态时使用
virtual
💡 面试题
1️⃣ internal vs private vs protected internal 的区别?
✅ 解析:
private(私有):仅当前类可访问,最严格internal(程序集内部):仅同一程序集内可访问protected(受保护):仅派生类可访问protected internal(程序集内部受保护):同程序集内 & 继承类可访问
🔹 代码示例:
class BaseClass {
private int privateValue = 1;
internal int internalValue = 2;
protected int protectedValue = 3;
protected internal int protectedInternalValue = 4;
}
class DerivedClass : BaseClass {
void Test() {
Console.WriteLine(protectedValue); // ✅ 可访问
Console.WriteLine(protectedInternalValue); // ✅ 继承类可访问
}
}
2️⃣ sealed 关键字的作用?什么时候使用?
✅ 解析:
sealed限制类的继承,防止错误扩展。- 提高 JIT 优化性能,因为 JIT 不需要检查是否有子类覆盖。
🔹 示例:
sealed class FinalClass {
public void Show() => Console.WriteLine("Can't inherit me!");
}
// ❌ 下面的代码会报错:
// class SubClass : FinalClass { } // Error: 不能继承密封类
✅ 适用场景
- 工具类(如
Math) - 安全性考虑(防止滥用继承)
- 性能优化(JIT 编译器可内联优化)
3️⃣ call vs callvirt 指令的区别?为什么 callvirt 甚至用于非虚方法?
✅ 解析:
call(直接调用):用于 静态方法 & 非虚方法,性能更高callvirt(虚方法调用):用于 虚方法,并额外进行null检查
🔹 示例
class Test {
public void NonVirtualMethod() { }
public virtual void VirtualMethod() { }
}
Test obj = new Test();
obj.NonVirtualMethod(); // `call`
obj.VirtualMethod(); // `callvirt`
🔹 IL 代码
call instance void Test::NonVirtualMethod() // 直接调用
callvirt instance void Test::VirtualMethod() // 进行虚方法查找
🚨 C# 默认对非虚方法也使用 callvirt(空引用检查)
Test obj = null;
obj.NonVirtualMethod(); // 运行时报错(NullReferenceException)
4️⃣ virtual 和 override 的工作原理?
✅ 解析:
virtual:定义可被子类重写的方法override:子类重写基类方法- 虚方法表(VTable):CLR 维护的方法指针表,支持动态调用
🔹 示例
class Base {
public virtual void Show() => Console.WriteLine("Base");
}
class Derived : Base {
public override void Show() => Console.WriteLine("Derived");
}
Base obj = new Derived();
obj.Show(); // "Derived"(多态调用)
🔹 VTable 机制
graph TD
A["Base::Show()"] -->|重写| B["Derived::Show()"]
C["Base obj = new Derived()"] -->|动态绑定| B
5️⃣ 为什么要使用 abstract ?和 interface 有什么区别?
✅ 解析:
| 特性 | abstract 类 |
interface |
|---|---|---|
| 成员 | 可有 字段 + 方法实现 | 只能有 方法定义 |
| 构造函数 | 支持 | ❌ 不支持 |
| 多重继承 | 不支持(单继承) | 支持(多接口) |
| 使用场景 | 代码复用 | 行为定义 |
🔹 示例
abstract class Animal {
public abstract void Speak(); // 仅定义,子类必须实现
}
class Dog : Animal {
public override void Speak() => Console.WriteLine("Woof!");
}
interface IAnimal {
void Speak(); // 只定义,不提供实现
}
class Cat : IAnimal {
public void Speak() => Console.WriteLine("Meow!");
}
6️⃣ static class 和 singleton 有什么区别?
✅ 解析
| 特性 | static class |
singleton |
|---|---|---|
| 实例化 | ❌ 不允许 | ✅ 只允许一个实例 |
| 存储方式 | 完全静态 | 通常用 private static |
| 继承 | ❌ 不允许 | ✅ 可以继承接口 |
| 适用场景 | 工具类(如 Math) |
全局唯一实例(如 Logger) |
🔹 示例
public static class Utils {
public static void Log(string message) => Console.WriteLine(message);
}
public sealed class Singleton {
private static readonly Singleton _instance = new Singleton();
public static Singleton Instance => _instance;
private Singleton() {} // 私有构造函数
}
🔹 面试题总结
| 知识点 | 核心考点 |
|---|---|
| 访问控制 | private / internal / protected / public |
| 静态类 | 不能实例化,只能包含 static 成员 |
| 组件化 & 版本控制 | sealed 防止继承,override 保护 API |
| 虚方法调用 | call(直接调用) vs callvirt(多态 + null 检查) |
| 抽象 vs 接口 | abstract 可有字段 & 代码实现,interface 仅定义行为 |
🔹 总结
| 概念 | 核心要点 |
|---|---|
| 类型成员 | 常量、字段、方法、属性、事件、嵌套类型 |
| 可见性 | public / internal / private |
| 静态类 | 只能包含 static 成员 |
| Partial 类 | 允许多人开发,代码拆分 |
| 版本控制 | sealed 避免错误继承,override 避免破坏继承层次 |
| IL 指令 | call (静态调用) vs callvirt (多态调用) |
点赞鼓励下,(づ ̄3 ̄)づ╭❤~
作者:世纪末的魔术师
出处:https://www.cnblogs.com/Firepad-magic/
Unity最受欢迎插件推荐:点击查看
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号