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

时间:2009-05-27 00:17:19

标签: c# performance readonly immutability

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

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

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

第三个原因是我看到readonly在编译的IL中保留为initonly,那么如果readonly是什么,这个信息在IL中的原因是什么? C#编译器保证不会将字段分配给构造函数或声明之外的其他内容吗?

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

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

4 个答案:

答案 0 :(得分:16)

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

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

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

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

    Readonly似乎在IL 中有类似的用途。它告诉其他程序集是否可以写入特定类型的字段。但是, readonly 似乎在上面的示例中进行了与访问修饰符相同的运行时检查。看起来readonly是一个编译时检查,并不会在运行时发生。请在此处查看效果示例:Read-only performance vs const

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

答案 1 :(得分:6)

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

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

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

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

Marcus Hegee的

Expert C++/CLI对此有一个相当不错的解释。

答案 2 :(得分:4)

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

答案 3 :(得分:3)

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

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

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