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
public class DataService
{
public IWorkerQueue WorkerQueue { get; init; }
public void StartEventProcessing() => WorkerQueue.Start();
}Avoid
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
public bool Accept(int retryCount, bool isValid)
{
int attempts = retryCount;
return isValid;
}Avoid
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
private IWorkerQueue _workerQueue;
private static readonly TimeSpan s_timeout;
private const string DefaultQueue = "inbox";Avoid
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
public interface IRepository<TSession>
{
TSession Open();
}Avoid
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
int messageCount = queue.Count;
for (int i = 0; i < messageCount; i++)
{
Process(queue[i]);
}Avoid
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
var reader = new StreamReader(path);
List<int> scores = GetScores();
var total = (long)count;Avoid
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
namespace MyApp.Services;
public class OrderService
{
}Avoid
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
// BankAccount.cs
public class BankAccount
{
}Avoid
// 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
public int Age => _age;
public double Mean => (High + Low) / 2.0;
public override string ToString() => $"{First} {Last}";Avoid
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
public Employee(string name, string alias)
{
this.name = name;
this.alias = alias;
}Avoid
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
if (items.Count == 0)
{
return;
}Avoid
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
using System.Collections.Generic;
using Octokit;
namespace CoolStuff.AwesomeFeature;Avoid
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
private readonly int _daysInYear = 365;Avoid
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
bool canShip = inventory.Count > 0
&& customer.IsVerified;Avoid
bool canShip = inventory.Count > 0 &&
customer.IsVerified;Corpus: Common C# code conventions · Code style formatting rule IDE0055