Contextual Keyword As Identifier

ID

csharp.contextual_keyword_as_identifier

Severity

low

Remediation Complexity

trivial

Remediation Risk

medium

Remediation Effort

low

Resource

Naming Convention

Language

CSharp

Tags

keyword, naming, readability

Description

Reports declarations whose name is a C# contextual keyword: async, await, scoped, extension or partial by default. Contextual keywords are not reserved words, so the compiler happily accepts them as ordinary identifiers — but the meaning of the word then depends entirely on where it appears.

The rule applies to local variables, parameters, fields, constants, properties, events, enum members, methods and type declarations. Only the declaration is reported, so one badly named symbol produces one finding instead of one per reference.

Three uses that look similar are deliberately not reported, because in each of them the word is not an identifier at all:

  • value and field anywhere. Both are contextual only inside a property or event accessor, where they are the implicit parameter and so have no declaration to report; everywhere else they are ordinary, idiomatic names — .NET’s own `IValueConverter.Convert(object value, …) among them, where an interface fixes the name. Add them through contextualKeywords if your team wants them flagged.

  • partial, async and scoped written as modifiers (partial class, async Task, scoped ref). Those parse as keyword tokens, not as names.

  • await inside an async body, where it is the await operator.

Rationale

A contextual keyword used as a name forces every reader — and every tool — to reconstruct from the surrounding syntax whether the word is a keyword or an identifier. partial on its own line could be a modifier or a method call; value in an accessor could be the implicit parameter or a field that shadows it.

The names are also fragile. The set of contextual keywords grows with each language version, and a word that is contextual today may become reserved tomorrow, forcing the code to be escaped as @word or renamed under time pressure.

public int partial() { return 0; }            // FLAW — method named after a contextual keyword
public void Configure(int scoped) { }         // FLAW — parameter named after a contextual keyword
public class extension { }                    // FLAW — type named after a contextual keyword

public int Count
{
    get { return backing; }
    set { backing = value; }                  // OK — implicit set accessor parameter
}

private int value;                            // OK — not flagged by default, see above

public partial class Order { }                // OK — partial is a modifier here
public async Task LoadAsync(Task pending)     // OK — async is a modifier here
{
    await pending;                            // OK — await is an operator here
}
public void Slice(scoped ref int window) { }  // OK — scoped is a modifier here

Remediation

Rename the declaration to a word that is not a language keyword and that states what the value or member represents, for example rename the parameter scoped to window, or the type extension to FileExtension.

Escaping the name as @scoped makes it compile but keeps the same confusing word at every use site, so prefer a real rename.

Configuration

properties:
  # Contextual keywords rejected as declaration names.
  contextualKeywords: [async, await, scoped, extension, partial]

contextualKeywords holds the words rejected as declaration names. Matching is exact and case-sensitive, and a leading @ (the verbatim-identifier escape) is ignored, so @scoped matches scoped. Add entries as new contextual keywords appear in the language — value and field are recognised but off by default — or remove entries a codebase deliberately accepts.