Naming Conventions

PascalCase for types and public members, camelCase for locals and parameters, _camelCase for private fields.

Syntaxclass InvoiceService { private readonly int _accountId; public int GetTotal() { ... } }

The compiler checks almost nothing about names. It only requires that an identifier starts with a letter or an underscore and continues with letters, digits or a few connecting characters. Everything else is convention, and in .NET the convention is followed unusually closely, because the whole base class library was written to it. Code that breaks it looks foreign on sight.

The table

KindConventionExample
Class, struct, record, enum, delegate, namespacePascalCaseInvoiceService
InterfaceI then PascalCaseIRepository
Method, property, event, public fieldPascalCaseGetTotal
Constant, field or localPascalCaseMaxAttempts
Local variable, parametercamelCaselineTotal
Private or internal field_camelCase_accountId
Private static fields_camelCases_hitCount
Generic type parameterT then PascalCaseTEntity

Two that surprise people

Constants are PascalCase, not SHOUTING_CASE: write MaxAttempts. And multi-letter acronyms are written as words, which is why the library has HttpClient, XmlReader and Id rather than HTTPClient or ID.

Where the rules actually live

The underscore prefix for private fields and the s_ prefix for private static fields come from the .NET runtime team's style. They are not compiler rules. A team that wants them enforced puts naming rules in an .editorconfig file, which the compiler, the editor and dotnet format all read.

Example

Example · csharp
Invoice invoice = new Invoice(42);        // local variable: camelCase
invoice.AddLine(150, 10);
invoice.AddLine(125, 2);

Console.WriteLine(invoice.GetTotal());
Console.WriteLine(invoice.GetAccountId());
Console.WriteLine(Invoice.MaxLineCount);

class Invoice                             // type name: PascalCase
{
    public const int MaxLineCount = 10;   // constant: PascalCase, not MAX_LINE_COUNT

    private readonly int _accountId;      // private field: leading underscore
    private int _total;

    public Invoice(int accountId)         // parameter: camelCase
    {
        _accountId = accountId;
    }

    public void AddLine(int unitPrice, int quantity)   // method: PascalCase
    {
        int lineTotal = unitPrice * quantity;          // local: camelCase
        _total = _total + lineTotal;
    }

    public int GetTotal()
    {
        return _total;
    }

    public int GetAccountId()
    {
        return _accountId;
    }
}

// Output:
// 1750
// 42
// 10

When to use it

  • Make a new file look like the rest of a C# codebase on first read
  • Tell a private field from a local variable at a glance inside a long method
  • Review a pull request for the naming the compiler will never check
  • Add an .editorconfig so dotnet format enforces the house style for everyone

More examples

Interfaces, type parameters and static fields

The three prefixes answer a question a reader would otherwise have to scroll for: whether a name is an interface, a type placeholder, or state shared by every instance of the class. The s_ prefix in particular flags shared mutable state, which is where concurrency bugs live.

Example · csharp
// An interface name starts with I. A type parameter starts with T.
interface IRepository<TEntity>
{
    TEntity? Find(int id);
}

class Cache
{
    // Private static field: s_ prefix, from the .NET runtime style.
    private static int s_hitCount;

    // Private instance field: leading underscore.
    private readonly string _name;

    public Cache(string name)
    {
        _name = name;
        s_hitCount = s_hitCount + 1;
    }
}

Constants and a positional record

Constants keep PascalCase whatever their access level, which is the opposite of the habit most other ecosystems have. The record line shows the one place where a parameter is PascalCase: it becomes a public property, so it follows the member rule rather than the parameter rule.

Example · csharp
class Retry
{
    // PascalCase, including for private constants. .NET does not shout.
    public const int MaxAttempts = 3;
    private const string KeyPrefix = "retry:";
}

// The parameters of a record are its public properties, so they are PascalCase.
public record PhysicalAddress(string Street, string City, string PostalCode);

// The parameters of a class or struct are ordinary parameters: camelCase.
public class DataService(string connectionString);

Acronyms, and names that clash with keywords

Following the library's own casing for acronyms keeps your types sitting next to HttpClient and XmlReader without looking out of place. The verbatim @ identifier exists mainly for interoperating with code generated from other languages or from a JSON schema, where a field really is called class.

Example · csharp
class Document
{
    public int Id;            // not ID
    public string? XmlBody;   // not XMLBody
    public string? HttpEtag;  // not HTTPETag
}

// An identifier that collides with a keyword needs an @ prefix. The @ is not
// part of the name. Rename instead, unless you are matching another language.
class Lesson
{
    public string? @class;
}

Discussion

  • Be the first to comment on this lesson.