Skip to content

Latest commit

 

History

History
289 lines (200 loc) · 11.7 KB

csharp-coding-guidelines.md

File metadata and controls

289 lines (200 loc) · 11.7 KB

C# Coding Guidelines

Design

The design section covers rules that require inter-file or semantic analysis, or that cannot be resolved automatically with a tool.

The C# coding guidelines in Pine are stricter than Microsoft's general recommendations to achieve greater transparency and readability.

Avoid the Intransparency of Infix Operators

Usages of the operators == and != with primitive types (e.g., int, string) on one side and a literal or null on the other side must be replaced with pattern-matching syntax using is and is not.

Background:

  • C# allows overriding these operators, so that surprising behavior could be hidden in there.

Formatting

The formatting section covers rules that do not require analysis beyond a single file and can be applied automatically.

The basis for formatting is dotnet format, which in turn applies rules we have specified in the .editorconfig files in the repository.

Compatibility with dotnet format: Our formatting must always be stable with respect to dotnet format. That means running dotnet format on already-formatted code must not change anything. We never want to format in a way that would be undone or altered by a subsequent dotnet format run. Some formatting concerns may be deferred entirely to dotnet format or its internal implementation (Formatter.Format).

The only reason we have additional rules beyond dotnet format is that we have not yet found a way to make dotnet format apply these changes. In particular, dotnet format does not normalize indentation in most cases (due to Roslyn's SuppressFormattingRule, which intentionally preserves the developer's existing layout choices). Our extra rules address the gaps where dotnet format is too lenient.

Consistent Layout of List Items

Items in argument lists, parameter lists, and tuples must be distributed in one of two ways: Either all items on the same line or each item starting a new line. If the last item is not on the same line as the first, the items must be placed so that each starts on a new line.

For multiline collection expressions, the closing bracket must be placed on a new line after the last item. For argument lists and parameter lists, the closing parentheses must be placed on the same line as the last item.

For argument lists in the multiline form, the first argument must be separated from the opening parens with a line break. For named arguments whose expression spans multiple lines, the expression must start on a new line after the parameter name and colon.

For multiline collection expressions, each multiline element must start on a new line, and the next element must start on a new line after the comma ending the multiline element. Preserve the original line grouping only between adjacent single-line elements. When moving collection elements off the opening bracket's line in an argument, indent them one level deeper than the bracket, matching Visual Studio editor indentation. For array, collection, and object initializers, preserve the original line grouping: items that shared a line stay grouped, including items following a multiline item on its closing line. When an initializer continues an expression starting on the foreach header line, its braces align with the foreach keyword, and its items are indented one level deeper, matching Visual Studio editor indentation. If the source expression already starts on a separate, indented line, the initializer braces align with that expression instead.

In a foreach header, the in keyword stays on the same line as the end of the loop variable, including deconstruction patterns. When wrapping the source expression, break after in, not before it, unless a comment or preprocessor directive requires preserving a line break. Member-access continuations of the source expression are indented one level deeper than the foreach keyword, including deconstructing and await foreach loops. If the source expression already starts on its own indented line, continuations align with it instead.

For example, the following code:

int[] alfa =
    [1, 2,
    3];

var beta =
    func(1, 2, 3);

var gamma =
    func(1, 2,
    3);

decl =
    alfa(beta(
        gamma(delta(a, b))));

Must be formatted to:

int[] alfa =
    [
    1, 2,
    3
    ];

var beta =
    func(1, 2, 3);

var gamma =
    func(
        1,
        2,
        3);

decl =
    alfa(
        beta(
            gamma(delta(a, b))));

Initializer on New Line

In declaration statements, the initializer may be placed on the same line. The initializer must never start on the same line as the equals sign if the initializer expression itself spans multiple lines.

For example, the following code:

var firstItemRange = getItemRange(separatedList.First);
var commentsAfterOpen = commentQueries.GetOnRowBetweenColumns(
    containerRange.Start.Row,
    containerRange.Start.Column,
    firstItemRange.Start.Column);

Must be formatted to:

var firstItemRange = getItemRange(separatedList.First);

var commentsAfterOpen =
    commentQueries.GetOnRowBetweenColumns(
        containerRange.Start.Row,
        containerRange.Start.Column,
        firstItemRange.Start.Column);

Line Breaks Depending on Line Length

When a line exceeds a length of 120 columns, line breaks must be inserted at the following locations:

  • Before a declaration initializer.
  • Before an expression body.
  • Before the dot in a member access expression.
  • Before the expression part of a switch expression arm.
  • In a conditional expression (splitting it to multiple lines as described in the conditional expression rule below).

Also, list forms such as argument lists, parameter lists, or object initializer lists must be switched to a multiline layout if the containing line exceeds the length threshold.

In automated formatting, these line breaks must be inserted starting from the outermost syntax node, only as far as necessary to meet the line length limit, so that the layout of inner expressions is preserved to the degree possible.

Binary Expression Chains

When a chain of binary operators (such as || or &&) exceeds the line length limit, the chain must be wrapped so that continuation lines are at the same indent level as the first operand. Since binary operators are left-associative, chains form nested left-recursive trees; when breaking, all continuation operands must remain at a consistent indent rather than cascading deeper with each break.

For example, the following code:

var putOnNewLine =
    node.Expression is ThrowExpressionSyntax || SpansMultipleLines(node) || node.Pattern is DiscardPatternSyntax || node.Pattern is DeclarationPatternSyntax;

Must be formatted to:

var putOnNewLine =
    node.Expression is ThrowExpressionSyntax || SpansMultipleLines(node) || node.Pattern is DiscardPatternSyntax ||
    node.Pattern is DeclarationPatternSyntax;

Spacing between Statements

If a statement spans multiple lines, it must be separated from previous and following statements by at least one empty line. If a statement spans multiple lines, it must be separated from following comments by at least one empty line.

Spacing between Declarations

Type declarations, method declarations and member declarations must be separated by at least one empty line.

Conditional Expression

If a conditional expression spans multiple lines, each subexpression and token must be placed on their own line.

For example, the following code:

count < 13
    ? 17
    : 21

Must be formatted to:

count < 13
?
17
:
21

Switch Expression

  • If a switch expression arm spans multiple lines, it must be separated from other arms by at least one empty line.
  • If a switch expression arm spans multiple lines, the expression must start on a new line after the pattern and arrow.
  • If the pattern syntax of a switch expression arm is DiscardPattern, it must always be separated from other arms by at least one empty line.
  • If a switch expression arm contains a throw expression, the expression must be placed on a new line after the pattern and arrow.

Switch Statement

Every section of a switch statement must be separated from other sections by at least one empty line.

Return Statement

If a return statement spans multiple lines, the expression must start on a new line after the return keyword;

Braces for Multi-Line Statements

For any statement in an if, else, using, or await using body that spans multiple lines, including line breaks introduced by formatting, the statement must be wrapped in a block (braces { }). This includes nested using statements: an inner using statement with its body spans multiple lines and therefore requires braces in the outer body. If the header of an if, using, or fixed statement spans multiple lines, its body must be wrapped in a block, even when the body contains only a single-line statement. This also applies to else if and await using statements, and to headers made multiline by formatting. The header extends from the statement's keyword through its closing parenthesis, excluding preceding comments and the body. Single-line statements may remain without braces when the header is also single-line.

For example, the following code:

if (condition)
    CallMethod(
        arg1,
        arg2);
else
    OtherMethod(
        arg1,
        arg2);

Must be formatted to:

if (condition)
{
    CallMethod(
        arg1,
        arg2);
}
else
{
    OtherMethod(
        arg1,
        arg2);
}

Braces for Lock Statements

The body of a lock statement must always be a block in braces, even when it contains only a single-line statement.

Loop Statements

The body of a for, foreach, while, or do loop must be wrapped in a block in braces when either the header or the body statement spans multiple lines, including line breaks introduced by formatting. Single-line body statements (including empty statements) may remain without braces when the header is also single-line, consistent with if statements. This also applies to await foreach and deconstructing foreach loops. Existing blocks are preserved.

Loop headers exclude preceding comments and the body. For a do loop, the trailing while (...) clause is the header for this rule.

In a do loop, the while keyword must start on the line immediately after the body statement or its closing brace, unless comments intervene. Preserve intervening comments and preprocessor directives.

When braces are required, if an unbraced loop or using body contains internal conditional-compilation directives, or a conditional-compilation boundary separates its header from its body, the formatter rejects automatic brace insertion rather than risk changing statement boundaries or variable scope in another configuration. Add explicit braces in each configuration before formatting such code.

Comments Before Content on a Line

If a comment appears before other content on the same line, the comment must be moved to a separate line above that content.

For example, the following code:

/* comment */ int x = 1;
// comment
string y = "hello";

Must be formatted to:

/* comment */
int x = 1;
// comment
string y = "hello";

Trailing Comments

Trailing // comments on a code line are preserved with exactly one space separating the code from the //. No blank line is added or removed between the trailing comment and the next line (e.g. a closing brace).

For example, the following code must remain unchanged:

if (x < 0)
{
    x = -x; // negate
}

Indentation

Indentation must only depend on the content. That means, existing indent before formatting must never influence the resulting indentation after formatting.

File

A file must end with one empty line.