19

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

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

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

第三个原因是我看到它readonly在编译后的 IL 中保留为initonly,那么如果readonlyC# 编译器保证该字段永远不会分配给构造函数还是声明?

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

所以我的猜测是:“只读”只是一个编译时特性,任何人都可以确认/否认这一点吗?如果是,那么将这些信息包含在 IL 中的原因是什么?

4

4 回答 4

16

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

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

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

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

于 2009-05-27T01:06:26.953 回答
7

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

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

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

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

Marcus Hegee 的Expert C++/CLI对此有相当好的解释。

于 2009-05-27T01:11:06.990 回答
4

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

于 2012-04-23T17:45:23.877 回答
3

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

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

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

于 2009-05-27T01:12:08.937 回答