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-"

Configuration

This detector does not need any configuration.