ReadOnly field initialized with a literal, where Const would do
ID |
vbnet.performance.use_literals_where_appropriate |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
medium |
Remediation Effort |
low |
Resource |
Performance |
Language |
VB.NET |
Description
Reports a ReadOnly field - instance or Shared - whose initializer is a single literal: a
number, a string, a character or True/False. Such a field holds a compile-time constant, so
it can be declared Const. Fields initialized with anything the compiler cannot evaluate -
DateTime.UtcNow, TimeSpan.FromSeconds(30), New Dictionary(…), an expression referring to
another member - are not reported, and neither is a field already declared Const.
Rationale
ReadOnly and Const are enforced at different times, and the difference costs something on
every use. A ReadOnly field is a real field: the value is written by a constructor - the
instance constructor for an instance field, the type initializer for a Shared one - and every
read is a field load through a reference. An instance ReadOnly field also occupies space in
every object of the type, so a value that is the same for all instances is stored once per
instance. A Const has no field and no storage at all; the compiler substitutes the value at
each use site, which removes the load and lets the optimizer fold it into surrounding
arithmetic and branch decisions. Declaring the field Const also makes the guarantee stronger
and states it to the reader: ReadOnly says "not reassigned after construction", which leaves
open the possibility that a constructor computes it, whereas Const says the value is fixed
and identical everywhere, and the compiler will reject any attempt to make it otherwise.
The following code illustrates the pattern detected by this rule:
Public Class RetryPolicy
' FLAGGED: ReadOnly field initialized with a literal, where Const would do
Private ReadOnly MaxAttempts As Integer = 3
' FLAGGED: ReadOnly field initialized with a literal, where Const would do
Private Shared ReadOnly ReferencePrefix As String = "INV-"
' FLAGGED: ReadOnly field initialized with a literal, where Const would do
Protected ReadOnly BackoffFactor As Double = 1.5
' FLAGGED: ReadOnly field initialized with a literal, where Const would do
Private ReadOnly TraceEnabled As Boolean = False
' FLAGGED: ReadOnly field initialized with a literal, where Const would do
Private ReadOnly FieldSeparator As Char = ";"c
Remediation
Change ReadOnly to Const and keep the initializer as it is. Const members are implicitly
Shared, so drop Shared if it is present, and remove any assignment to the field from a
constructor - a Const cannot be assigned there.
One case deserves thought before converting. Because the compiler inlines a Const at every
use site, the value is copied into any other assembly that references it, and those callers
keep the old value until they are recompiled. For a field on a public surface consumed by
assemblies you do not rebuild together, that is a binary-compatibility hazard, and ReadOnly
is the right choice - it is read from your assembly at run time, so a rebuild of yours alone
updates every caller. Convert freely for Private, Protected and Friend fields, and for
public ones only where the whole solution is built together or the value is genuinely fixed
forever.
' Before: a field, initialized at construction, loaded on every read
Private ReadOnly MaxAttempts As Integer = 3
Private Shared ReadOnly ReferencePrefix As String = "INV-"
' After: no field, no storage, the value substituted at each use
Private Const MaxAttempts As Integer = 3
Private Const ReferencePrefix As String = "INV-"