C# readonly是否有运行时开销?

C# readonly是否有运行时开销?,c#,performance,readonly,immutability,C#,Performance,Readonly,Immutability,出于某种原因,我总是假设readonly字段有与之相关的开销,我认为这是CLR跟踪readonly字段是否已初始化。这里的开销是一些额外的内存使用,以跟踪状态,并在分配值时进行检查 也许我之所以这样认为是因为我不知道一个readonly字段只能在构造函数内部或字段声明本身内部初始化,如果没有运行时检查,您将无法保证它不会在各种方法中被多次赋值。但现在我知道了,它可以很容易地由C#编译器进行静态检查,对吗?是这样吗 另一个原因是,我读到,readonly的使用对性能有“轻微”的影响,但他们从未对此

出于某种原因,我总是假设
readonly
字段有与之相关的开销,我认为这是CLR跟踪
readonly
字段是否已初始化。这里的开销是一些额外的内存使用,以跟踪状态,并在分配值时进行检查

也许我之所以这样认为是因为我不知道一个
readonly
字段只能在构造函数内部或字段声明本身内部初始化,如果没有运行时检查,您将无法保证它不会在各种方法中被多次赋值。但现在我知道了,它可以很容易地由C#编译器进行静态检查,对吗?是这样吗

另一个原因是,我读到,
readonly
的使用对性能有“轻微”的影响,但他们从未对此进行过讨论,我也找不到有关此主题的信息,因此我提出了问题。除了运行时检查之外,我不知道还有什么其他性能影响

第三个原因是我看到
readonly
作为
initonly
保存在编译后的IL中,那么如果
readonly
只不过是C#编译器保证字段不会被分配到构造函数或声明之外,那么该信息出现在IL中的原因是什么呢

另一方面,我发现您可以通过反射设置
readonly int
的值,而不需要CLR抛出异常,如果
readonly
是运行时检查,这应该是不可能的


所以我的猜测是:“ReadOnlynes”只是一个编译时特性,有人能证实/否认这一点吗?如果是,该信息包含在IL中的原因是什么

您必须从与访问修饰符相同的角度来看待它。访问修饰符存在于IL中,但它们真的是运行时检查吗?(1) 我不能在编译时直接分配私有字段,(2)我可以使用反射来分配它们。到目前为止,它似乎没有运行时检查,比如只读

但是让我们检查一下访问修饰符。请执行以下操作:

  • 使用公共类C创建程序集A.dll
  • 创建引用.dll的程序集B.exe。B.exe使用C类
  • 构建这两个程序集。运行B.exe工作正常
  • 重建A.dll,但将C类设置为内部。替换B.exe目录中的A.dll
  • 现在,运行B.exe会引发运行时异常

    IL中也存在访问修饰符,对吗?那么他们的目的是什么?这样做的目的是,引用.Net程序集的其他程序集需要知道它们允许访问什么和不允许访问什么,包括编译时和运行时

    Readonly在IL中似乎也有类似的用途。它告诉其他程序集是否可以写入特定类型的字段。然而,readonly似乎不像我上面的示例中的访问修饰符那样具有相同的运行时检查。readonly似乎是一种编译时检查,在运行时不会发生。请在此处查看性能示例:


    同样,这并不意味着IL是无用的。IL首先确保发生编译时错误。请记住,当您构建时,您不是根据代码,而是根据程序集进行构建。

    如果您使用的是标准的实例变量,readonly将执行与普通变量几乎相同的操作。添加的IL成为编译时检查,但在运行时几乎被忽略

    如果您使用的是静态只读成员,则情况会有所不同

    由于静态只读成员是在静态构造函数期间设置的,因此JIT“知道”存在一个值。没有额外的内存-readonly只是防止其他方法设置此值,但这是编译时检查

    因为JIT知道这个成员永远不会改变,所以它在运行时会被“硬编码”,所以最终的效果就像有一个常量值一样。不同之处在于,在JIT时间内,它将花费更长的时间,因为JIT编译器需要做额外的工作来硬连接只读值。(不过,这将非常快。)


    Marcus Hegee对此有一个相当好的解释。

    即使readonly在编译时生效,仍有必要将数据存储在程序集中(即IL)。CLR是一种通用语言运行时-用一种语言编写的类可以被其他语言使用和扩展

    由于CLR的每个编译器都不知道如何读取和编译其他语言,为了保留
    readonly
    字段的语义,这些数据需要存储在程序集中,以便其他语言的编译器尊重这些数据


    当然,字段标记为
    readonly
    这一事实意味着JIT可以做其他事情,比如优化(例如,内联使用值)等,而不管您使用反射来更改字段的值,创建IL来修改相应构造函数外部的
    initonly
    字段(实例或静态,取决于字段类型),将导致无法验证的程序集。

    任何其他答案尚未提及的一个重要问题是,当访问只读字段或任何属性时,使用数据的副本即可满足请求。如果所讨论的数据是具有超过4-8字节数据的值类型,则此额外复制的成本可能会降低有时可能很重要。请注意,当结构从16字节增长到17字节时,成本会大幅上升,但如果不经常复制的话,结构在许多应用程序中可能会相当大,并且仍然比类快。例如,如果一个应用程序的类型应该表示三维空间中三角形的顶点。简单的工具