C#idiomatic C#
naming-and-styleidioms

Idioms

Naming and style

Naming, layout, and file structure are the parts of C# the compiler never checks, so the conventions worth keeping are the ones with a reason behind them.

C# enforces almost nothing about how code looks. What a codebase settles is what the framework, the templates, and the analyzers already assume: a casing that marks the kind of a name, var where the right side names the type, one namespace and one type per file, and a layout the formatter can hold.

Name types and public members in PascalCaseIDE1006

Type names, namespace names, and public members are PascalCase: DataService, WorkerQueue, StartEventProcessing. The default naming style applies Pascal case to classes, structs, enumerations, properties, methods, and events at every accessibility, and IDE1006 is the rule that reports a name which breaks the style. Positional record parameters take Pascal case as well, because each parameter becomes a public property.

Preferred

csharp
public class DataService
{
    public IWorkerQueue WorkerQueue { get; init; }
    public void StartEventProcessing() => WorkerQueue.Start();
}

Avoid

csharp
public class dataService
{
    public IWorkerQueue workerQueue { get; init; }
    public void startEventProcessing() => workerQueue.Start();
}

Corpus: Identifier names · Code-style naming rules

Hold parameters and locals in camelCase

A method argument or a local variable is camelCase: retryCount, isValid. The conventions lean on case to separate the kinds of name, so parameters, locals, and the non-constant fields that stay inside the type all take the lowercase first letter. Primary constructor parameters follow the same rule on class and struct types; on a record they take Pascal case, because they become public properties.

Preferred

csharp
public bool Accept(int retryCount, bool isValid)
{
    int attempts = retryCount;
    return isValid;
}

Avoid

csharp
public bool Accept(int RetryCount, bool IsValid)
{
    int Attempts = RetryCount;
    return IsValid;
}

Corpus: Identifier names · Common C# code conventions

Put an underscore in front of a private field

A private or internal field that is not a constant is camelCase behind a leading underscore: _workerQueue. In an editor that completes on _, the prefix lists the members of the current object and nothing else, which is what keeps the field distinct from the parameter that carries the same word. Static fields take s_, thread-static fields take t_, and a field declared const keeps PascalCase.

Preferred

csharp
private IWorkerQueue _workerQueue;
private static readonly TimeSpan s_timeout;
private const string DefaultQueue = "inbox";

Avoid

csharp
private IWorkerQueue workerQueue;
private static readonly TimeSpan timeout;
private const string defaultQueue = "inbox";

Corpus: Identifier names · Code-style naming rules

Prefix interfaces with I and type parameters with T

An interface takes a capital I in front of a Pascal case name: IWorkerQueue. That prefix is the default naming style, so a caller separates the contract from an implementation without opening a file. Type parameters follow the same idea: a descriptive name starts with T, as in TSession, and a lone T is the form for a case where one letter is all the meaning there is.

Preferred

csharp
public interface IRepository<TSession>
{
    TSession Open();
}

Avoid

csharp
public interface Repository<Session>
{
    Session Open();
}

Corpus: Identifier names · Code-style naming rules

Write a name out unless the short form is universal

A name favors clarity over brevity, so an abbreviation or acronym belongs in an identifier only where the short form is widely known and accepted, and a single letter is kept for a simple loop counter. Everywhere else the short form is paid for by each reader while it saves the writer a few keystrokes, which is a trade the conventions decline.

Preferred

csharp
int messageCount = queue.Count;
for (int i = 0; i < messageCount; i++)
{
    Process(queue[i]);
}

Avoid

csharp
int msgCnt = queue.Count;
for (int idx = 0; idx < msgCnt; idx++)
{
    Process(queue[idx]);
}

Corpus: Identifier names

Let the right side name the typeIDE0007

var is for the declaration whose type is already written in the expression: a new operator, an explicit cast, or a literal. Where the type is not on the right, write the type, and do not assume a method name states it. The rule carries the three places as separate options: csharp_style_var_when_type_is_apparent, csharp_style_var_for_built_in_types, and csharp_style_var_elsewhere.

Preferred

csharp
var reader = new StreamReader(path);
List<int> scores = GetScores();
var total = (long)count;

Avoid

csharp
var reader = OpenReader(path);
var scores = GetScores();

Corpus: Common C# code conventions · var preferences (IDE0007 and IDE0008)

Declare the namespace for the file, not for a blockIDE0161

A file that declares a single namespace uses the file-scoped form: namespace MyApp.Services; followed by the types, with no braces and no extra level of indentation. It applies to the whole file, only one such declaration is allowed, and the language guide and the analyzers recommend it for new code; IDE0161 reports the block form once csharp_style_namespace_declarations is set to file_scoped. A block-scoped namespace remains for the rare file that declares more than one.

Preferred

csharp
namespace MyApp.Services;

public class OrderService
{
}

Avoid

csharp
namespace MyApp.Services
{
    public class OrderService
    {
    }
}

Corpus: Namespaces and using directives · Namespace declaration preferences (IDE0160 and IDE0161)

Keep one type per file, named after the type

A file holds one type and is named after it: BankAccount.cs for BankAccount, DailyTemperature.cs for DailyTemperature. The tutorials introduce a class by creating the file for it, because a file that grows with the type stays easy to find as the program grows. The conventions name one case for splitting a definition across files, and it is the partial type.

Preferred

csharp
// BankAccount.cs
public class BankAccount
{
}

Avoid

csharp
// Account.cs
public class BankAccount
{
}

Corpus: Classes and objects tutorial · Use record types tutorial · Partial Classes and Members

Give a one-line body an expression bodyIDE0022

A member whose entire body is one expression reads better after an arrow: public int Age => _age;. The language allows the form on methods, properties, indexers, operators, constructors, finalizers, and accessors, and the style rules prefer it where the expression stays on one line; a body that needs statements keeps its braces.

Preferred

csharp
public int Age => _age;
public double Mean => (High + Low) / 2.0;
public override string ToString() => $"{First} {Last}";

Avoid

csharp
public int Age
{
    get { return _age; }
}

Corpus: IDE0022: Use expression body for methods · IDE0025: Use expression body for properties · The lambda operator

Qualify with `this.` only where it disambiguatesIDE0003

this. earns its place where it changes what a name means: a member hidden behind a parameter or a local of the same name, and a call that hands the current object to another method. Everywhere else it is noise, which the analyzer options state as defaults: the four dotnet_style_qualification_* options for fields, properties, methods, and events are all false, with IDE0003 to remove a qualifier and IDE0009 to require one. The rule surfaces in the IDE and not on build.

Preferred

csharp
public Employee(string name, string alias)
{
    this.name = name;
    this.alias = alias;
}

Avoid

csharp
public void Reset()
{
    this.count = 0;
}

Corpus: The this keyword · IDE0003 and IDE0009: Member-access qualification preferences

Brace every control-flow bodyIDE0011

A control-flow body is written as a block even when it holds one statement. csharp_prefer_braces defaults to true, and false is the value that permits the bare form. Brace placement comes from the same convention set: the open brace takes its own line, which the docs layout calls Allman and csharp_new_line_before_open_brace defaults to as all.

Preferred

csharp
if (items.Count == 0)
{
    return;
}

Avoid

csharp
if (items.Count == 0)
    return;

Corpus: IDE0011: Add braces · Common C# code conventions · C# formatting options

Place using directives outside the namespace, System firstIDE0065

A using directive goes above the namespace declaration, where the imported namespace means its fully qualified self. Inside a namespace the same directive resolves against the enclosing namespaces first, so using Azure; in CoolStuff.AwesomeFeature matches CoolStuff.AwesomeFeature.Azure or CoolStuff.Azure before the global one, and a dependency that later adds a matching name changes what the untouched file means. Sorting is part of the block: System directives first and alphabetically, the default of dotnet_sort_system_directives_first.

Preferred

csharp
using System.Collections.Generic;
using Octokit;

namespace CoolStuff.AwesomeFeature;

Avoid

csharp
namespace CoolStuff.AwesomeFeature;

using Octokit;
using System.Collections.Generic;

Corpus: IDE0065: 'using' directive placement · Common C# code conventions · .NET formatting options

Order modifiers the same way every timeIDE0036

Modifiers read in a fixed sequence so that the same declaration looks the same in every file. IDE0036 enforces the order set in csharp_preferred_modifier_order, whose default runs public, private, protected, internal, file, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, required, volatile, async. With the rule off, no order is preferred.

Preferred

csharp
private readonly int _daysInYear = 365;

Avoid

csharp
readonly private int _daysInYear = 365;

Corpus: IDE0036: Order modifiers

Take layout limits from a written conventionIDE0055

The language fixes neither the length of a line nor the place a brace sits, so the limits come from a convention that is written down and enforced rather than argued over per change. The docs layout uses four spaces for indentation, writes one statement per line, caps a sample line at 65 characters for readability on small screens, and breaks a wrapped line before a binary operator. The formatting options all share rule IDE0055, so one severity setting covers indentation, spaces, and new lines.

Preferred

csharp
bool canShip = inventory.Count > 0
    && customer.IsVerified;

Avoid

csharp
bool canShip = inventory.Count > 0 &&
    customer.IsVerified;

Corpus: Common C# code conventions · Code style formatting rule IDE0055