No Public Const Member

ID

csharp.no_public_const_member

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

CSharp

Tags

api-design, binary-compatibility, code_smell

Description

Reports const members that are externally visible — public, protected or protected internal. private protected constants are not reported, nor are const locals inside a method body, nor constants declared in a type that is not itself externally visible: without a cross-assembly consumer there is nothing to break.

Rationale

A const is a compile-time value. The compiler copies the literal into every place that reads it, including places in other assemblies, so the value becomes part of those assemblies the moment they are built. Publishing a corrected version of the declaring assembly changes nothing for them: they keep using the old number until each one is rebuilt, and a mixed deployment then runs two different values for the same named constant. static readonly is read from the declaring assembly at run time, so updating that one assembly updates every consumer.

Keep const for values that are part of the type’s identity and can never change — a mathematical constant, a protocol magic number fixed by a specification — and for private/internal values whose only consumers are recompiled together with the declaration.

public class Limits
{
    public const int MaxRetries = 3;                     // FLAW - inlined into every consumer
    protected const string Prefix = "cache:";            // FLAW - same, for derived types elsewhere

    internal const int BatchSize = 50;                   // OK - no cross-assembly consumer
    private const int Slack = 2;                         // OK
    public static readonly int Timeout = 30;             // OK - read at run time

    public int Compute()
    {
        const int localFactor = 7;                       // OK - a local const has no consumers
        return localFactor * MaxRetries;
    }
}

Remediation

Change the member to public static readonly and keep the same initialiser. Two cases need a second look: an attribute argument, a switch case label or a default parameter value requires a compile-time constant, so those call sites will not compile against a static readonly field and need a different design (for example a static property plus an overload). If the value truly cannot change, keep the const and treat it as part of the published contract.