六、类型和成员基础(Type and Member Basics)

CLR #类型 #成员

1. 类型的基本成员

在 C# 中,一个类型(class 或 struct)可以定义以下成员:

  1. 常量(Constant) - 只读,编译期确定
  2. 字段(Field) - 存储数据,可以是 staticinstance
  3. 构造函数(Constructor) - 初始化实例或类型
  4. 方法(Method) - 操作逻辑,支持 静态/实例/虚拟
  5. 运算符重载(Operator Overload) - 定义 +, - 等运算符
  6. 转换运算符(Conversion Operator) - 隐式/显式转换
  7. 属性(Property) - 访问字段的封装方式
  8. 事件(Event) - 观察者模式,通知机制
  9. 嵌套类型(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

应用场景

  • 工具类(如 MathConsole
  • 只包含静态方法的 Helper

5. Partial 类(分部类)

作用

  1. 多人协作开发:不同文件定义同一个类
  2. 代码生成工具(如 WinForms 设计器)
  3. 逻辑分离(如 DataLayerBusinessLayer
// 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.revision
    
    • 1.2.3.4
    • major(大版本) - 重大变更
    • 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️⃣ virtualoverride 的工作原理?

解析

  • 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 classsingleton 有什么区别?

解析

特性 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 (多态调用)
posted @ 2025-08-26 10:06  世纪末の魔术师  阅读(21)  评论(0)    收藏  举报