readonly是否有任何运行时开销?

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

也许我假设这是因为我不知道readonly字段只能在构造函数内或字段声明本身内初始化,并且没有运行时检查,你将无法保证它不被多次分配各种方法。 但是现在我知道了,它可以很容易地被C#编译器静态检查,对吧? 那是这样的吗?

另一个原因是我读过readonly的使用对性能有轻微的影响,但他们从未涉及过这个主张,因此无法找到有关此主题的信息,因此我提出了问题。 我不知道运行时检查可能会对其他性能产生什么影响。

第三个原因是我看到readonly在编译的IL中保存为initonly ,所以如果readonly仅仅是C#编译器保证该字段永远不会分配给IL,那么这个信息在IL中的原因是什么?在构造函数或声明之外?

另一方面,我发现你可以通过reflection设置readonly int的值,而CLR不会抛出exception,如果readonly是运行时检查,这是不可能的。

所以我的猜测是:’readonlyness’只是一个编译时function,任何人都可以确认/否认这个吗? 如果是,那么这些信息被包含在IL中的原因是什么?

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

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

  1. 使用公共类C创建程序集A.dll
  2. 创建一个引用A.dll的程序集B.exe。 B.exe使用C类。
  3. 构建两个程序集。 运行B.exe工作正常。
  4. 重建A.dll但将C类设置为内部。 替换B.exe目录中的A.dll。

现在,运行B.exe会引发运行时exception。

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

Readon似乎在IL中具有类似的目的 。 它告诉其他程序集是否可以写入特定类型的字段。 但是, readonly 似乎没有访问修饰符在上面的示例中显示的相同运行时检查。 看起来readonly是一个编译时检查,并不会在运行时发生。 看一下这里的性能示例: 只读性能与const 。

同样,这并不意味着IL无用。 IL确保首先发生编译时错误。 请记住,在构建时,不要构建代码,而是构建程序。

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

如果您使用静态只读成员,事情会有所不同……

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

JIT知道这个成员永远不会改变,它在运行时会被“硬编码”,所以最后的效果就像拥有一个const值。 不同之处在于它在JIT时间本身需要更长的时间,因为JIT编译器需要做额外的工作来将readonly的值硬连接到位。 (但这会非常快。)

Marcus Hegee的专家C ++ / CLI对此有一个相当不错的解释。

任何其他答案尚未提及的一个重要点是,当访问只读字段时,或者访问任何属性时,使用数据副本满足请求。 如果所讨论的数据是具有超过4-8字节数据的值类型,则此额外复制的成本有时可能很大。 请注意,虽然结构从16个字节增长到17个时成本会大幅上升,但结构可能会相当大,并且仍然比许多应用程序中的类更快, 如果它们不经常复制的话 。 例如,如果假设一个类型表示三维空间中三角形的顶点。 一个简单的实现将是一个包含结构的结构,每个点有三个float ; 可能总共36个字节。 如果点和每个点内的坐标是可变的公共字段,则可以快速轻松地访问someTriangle.P1.X ,而不必复制除顶点1的Y坐标之外的任何数据。另一方面,如果P1是在属性或readonly字段中,编译器必须将P1复制到临时结构,然后从中读取X

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

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

当然,字段标记为readonly的事实意味着JIT可以执行其他操作,例如优化(例如,值的内联使用)等。无论您使用reflection来更改字段的值,创建修改IL的事实相应构造函数之外的initonly字段(实例或静态,取决于字段类型)将导致无法validation的程序集。