C# sealed密封 追本溯源

一、简介

在 C# 中,sealed 关键字用于定义一个密封类或者密封方法,也就是不能被继承的类。这样做的目的是为了限制类的继承,确保类的行为和实现不会被改变。

此外,密封类也有助于提升代码的安全性和可维护性。由于密封类不能被继承,开发者可以更好地控制类的行为,避免不必要的继承和重写带来的风险。

**sealed**类是 C# 提供的一个强大工具,它帮助开发者更好地控制类的设计和行为。

(过多基础知识介绍就不在此赘述了,今天探讨的问题点是 sealed密封类是否会带来性能上的提升,这是一个非常有意思的话题。

二、类从元数据到 CLR 运行时

1) 编译器输出:源代码 → IL + 元数据(Assembly / PE)

C#语言被编译成 IL(中间语言)并写入 PE(Portable Executable)文件中。同时生成 元数据(Metadata):类型定义(TypeDef)、字段(FieldDef)、方法(MethodDef)、参数、属性、接口实现、常量、引用到其他模块(TypeRef/MemberRef/AssemblyRef)等。

1.1)Roslyn

Roslyn简介:Roslyn = C# / VB 的"编译器 + 代码分析框架",而且本身就是用 C# 写的。在 .NET 中它不仅负责把代码编译成 IL,还把"编译过程本身"开放出来让你可以读语法树、编辑语法树、生成代码、做分析、做重写。

在 Roslyn / 编译器语境里:**使用编译器自身的能力来编译或生成编译器本身。**这种方式称之为自举。自举的意义在于见证语言的完整性、减少依赖。

1.2)核心类MetadataWriter

1.2.1)PopulateTypeDefTableRows

PopulateTypeDefTableRows方法会把中所有的类型写入 TypeDef 表 ,并为每个类型生成 Metadata 所需的行数据。(MetadataWriter还包含很多方法,PopulateTypeDefTableRows只会把类型写入TypeDef 类型引用表中。还有其他的PopulateMethodDefTableRows()、PopulateCustomAttributeTableRows()等方法,会写入对应的方法和特性引用表中,最终这些引用表合成Metadata。就不在此详细讲解了。因为实在是有太多东西了。。。。。。)

1.2.2)WriteMetadataAndIL

WriteMetadataAndIL会构建所有生成的Metadata,通过emit生成IL。把Metadata和IL拼成最终的二进制块,交给 PEWriter 写入 PE 文件。最终我们将得到.dll或者.exe文件。

2)方法引用的内联和展开

2.1)内联(inlining)

内联:方法调用在最终生成的 IL 或 JIT 机器码中直接替换成方法体的代码,不再有调用指令。常用于小方法,提高性能。

2.2)展开(普通调用 / 不内联)

调用保留 call 指令,执行时需要跳转到被调用方法,维护调用栈和参数。

2.3)call和callvirt

call:编译期直接确定方法地址,调用点直接跳转到方法体,不需要虚表查找。

callvirt:编译期只知道方法 slot(在 vtable 或接口表),运行时根据对象类型查找实际实现。

2.4)抽象类抽象方法

我们来写一个小的demo:

cs 复制代码
namespace ConsoleApp1
{
    public abstract class AbstractTest
    {
        public abstract void Test();
    }

    class AbstractClass : AbstractTest
    {
        public override void Test()
        {
            Console.WriteLine("Hi");
        }
    }

    public class ConsoleWriteLine
    {
        void Test(AbstractClass abs)
        {
            abs.Test();
        }
    }
}

通过ILSpy我们可以看到,ConsoleWriteLine这个类在调用抽象方法时使用的是callvirt。而callvirt会先去vtable表中去检索,找到虚方法或者抽象方法的具体实现地址在哪里。

3)sealed密封类

3.1)虚拟化**(Virtualization)**

虚拟化:在 C# / CLR 里主要指方法调用的多态机制,也就是 运行时动态分派(dynamic dispatch),允许子类重写父类方法。让调用者在编译期无法确定具体方法体,而是在运行时根据对象类型决定调用哪个实现。

虚拟化的实现方法是-->虚方法表(vtable)+ callvirt 指令。关键目的是支持多态。

3.2)去虚拟化(Devirtualization)

如果对象类型在运行期唯一可确定,例如 sealed 类-->JIT 可以去掉虚拟化机制。这样对比虚拟化的优势是,不在进行vtable表检索,减少调用性能下降。

4)RunTime中的sealed

4.1)RunTime

我们可以看到Runtime会把sealed密封类标记为 "CORINFO_FLG_FINAL"

检测到是:sealed密封类;static静态类;不是抽象类,就会停止检索。Runtime JIT会对其进行优化。

相关推荐
唐叔在学习几秒前
Python移动端应用消息提醒开发实践
开发语言·python
暴力求解2 分钟前
C++ ---string类(三)
开发语言·c++
Pocker_Spades_A7 分钟前
Python快速入门专业版(五十七)——POST请求与模拟登录:从表单分析到实战(以测试网站为例)
开发语言·python
道清茗21 分钟前
【RH294知识点汇总】第 3 章 《 管理变量和事实 》1
开发语言·python
星空椰22 分钟前
JavaScript基础:运算符和流程控制
开发语言·javascript·ecmascript
Halo_tjn24 分钟前
Java 接口的定义重构学生管理系统
java·开发语言·算法
阿Y加油吧30 分钟前
栈的经典应用:从「有效括号」到「寻找两个正序数组的中位数」深度解析
开发语言·python·算法
xiaotao13137 分钟前
阶段零:Python 安装与虚拟环境(venv / Conda)
开发语言·人工智能·python·conda
dr_yingli40 分钟前
fMRI(4-1)统计分析报告生成器说明
开发语言·matlab
m0_716765231 小时前
数据结构--顺序表的插入、删除、查找详解
c语言·开发语言·数据结构·c++·学习·算法·visual studio