# C# Maintainability Standards · CS-01

## Standard Metadata

- Area: C#
- Owner: Backend Steering group
- Last reviewed: 2026-09-09
- Next review due from: 2026-12-01
- Enforcement: CodeAnalysis / Rosyln Analyser / code review
- Status: Active

## Methods do not exceed 10 statements · CS-01.1 · **MUST**

A method that requires more than 10 statements is simply doing too much or has too many responsibilities. It also requires the human mind to analyze the exact statements to understand what the code is doing. Break it down in multiple small and focused methods with self-explaining names, but make sure the high-level algorithm is still clear.

**Roslyn Analyzer Rule [ACL1002](https://github.com/audaciaconsulting/Audacia.CodeAnalysis/blob/master/dotnet-roslyn/analyzers/Audacia.CodeAnalysis.Analyzers/README.md#acl1002---methods-should-not-exceed-a-predefined-number-of-statements)**

---

## Make all members `private` and types `internal` by default · CS-01.2 · **MUST**

To make a more conscious decision on which members to make available to other classes first restrict the scope as much as possible. Then carefully decide what to expose as a public member or type.

---

## Avoid conditions with double negatives · CS-01.3 · **SHOULD**

Although a property like `customer.HasNoOrders` make sense, avoid using it in a negative condition like this:

```csharp
bool hasOrders = !customer.HasNoOrders;
```

Double negatives are more difficult to grasp than simple expressions, and people tend to read over the double negative easily.

**Roslyn Analyzer Rule [AV1502](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1502-avoid-conditions-with-double-negatives-)(partial)**

---

## Name assemblies after their contained namespace · CS-01.4 · **SHOULD**

All DLLs should be named according to the pattern `<Company>.<Component>.dll` where `<Company>` refers to your company's name and `<Component>` contains one or more dot-separated clauses. For example `Dnb.Web.Controls.dll`.

As an example, consider a group of classes organized under the namespace `Dnb.Web.Binding` exposed by a certain assembly. According to this guideline, that assembly should be called `Dnb.Web.Binding.dll`.

**EXCEPTION**:
If you decide to combine classes from multiple unrelated namespaces into one assembly, consider suffix to the assembly with `Core`, but do not use that suffix in the namespaces. For instance, `Dnb.Consulting.Core.dll`.

**Roslyn Analyzer Rule [AV1505](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1505-name-assemblies-after-their-contained-namespace-)**

---

## Name a source file to the type it contains · CS-01.5 · **SHOULD**

Use Pascal casing for naming the file and don’t use underscores.

**Roslyn Analyzer Rule [SA1649](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1649.md)**

---

## Limit the contents of a source code file to one type · CS-01.6 · **SHOULD**

Make sure that one source file can be responsible for fully or partially contributing to one class.

**EXCEPTION**:
Nested types can, for obvious reasons, be part of the same file.

**Roslyn Analyzer Rule [SA1402](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1402.md)**

---

## Name a source file to the logical function of the partial type · CS-01.7 · **SHOULD**

When using partial types and allocating a part per file, name each file after the logical part that part plays. For example:

```csharp
// In MyClass.cs
public partial class MyClass
{
    ...
}

// In MyClass.Designer.cs
public partial class MyClass
{
    ...
}
```

---

## Don't use **magic** numbers · CS-01.8 · **MUST**

Don't use literal values, either numeric or strings, in your code other than to define symbolic constants. For example:

```csharp
public class Whatever
{
    public static readonly Color PapayaWhip = new Color(0xFFEFD5);
    public const int MaxNumberOfWheels = 18;
}
```

Strings intended for logging or tracing are exempt from this rule. Literals are allowed when their meaning is clear from the context, and not subject to future changes, For example:

```csharp
mean = (a + b) / 2;                         // okay
WaitMilliseconds(waitTimeInSeconds * 1000); // clear enough
```

If the value of one constant depends on the value of another, do attempt to make this explicit in the code.

```csharp
public class SomeSpecialContainer
{
    public const int MaxItems = 32;
    public const int HighWaterMark = 3 * MaxItems / 4; // at 75%
}
```

**Note**:
An enumeration can often be used for certain types of symbolic constants.

**Roslyn Analyzer Rule [ACL1001](https://github.com/audaciaconsulting/Audacia.CodeAnalysis/blob/master/dotnet-roslyn/analyzers/Audacia.CodeAnalysis.Analyzers/README.md#acl1001---variable-declarations-should-not-use-a-magic-number)**

---

## Declare and initialize variables as late as possible · CS-01.9 · **SHOULD**

Avoid the C and Visual Basic styles where all variables have to be defined at the beginning of a block, but rather define and initialize each variable at the point where it is needed.

---

## Assign each variable in a separate statement · CS-01.10 · **MUST**

Don't use confusing constructs like the one below.

```csharp
var result = someField = GetSomeMethod();
```

**Roslyn Analyzer Rule [RCS1081](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1081/)**

---

## Favour Object and Collection Initialisers over separate statements · CS-01.11 · **SHOULD**

Avoid

```csharp
var startInfo = new ProcessStartInfo("myapp.exe");
startInfo.StandardOutput = Console.Output;
startInfo.UseShellExecute = true;
```

Instead, use [Object Initialisers](http://msdn.microsoft.com/en-us/library/bb384062.aspx).

```csharp
var startInfo = new ProcessStartInfo("myapp.exe")
                {
                    StandardOutput = Console.Output,
                    UseShellExecute = true
                };
```

Similarly, instead of adding items to collection or dictionary individually

```csharp
var countries = new List<string>();
countries.Add("Netherlands");
countries.Add("United States");
```

Use collection or [dictionary initialisers](http://msdn.microsoft.com/en-us/library/bb531208.aspx).

```csharp
var countries = new List<string>
                {
                    "Netherlands",
                    "United States"
                };
```

---

## Don't make explicit comparisons to `true` or `false` · CS-01.12 · **MUST**

It is usually bad style to compare a `bool` type expression to `true` or `false`. For example:

```csharp
while (condition == false)                    // wrong; bad style
while (condition != true)                     // also wrong

while (condition)                             // OK
```

**EXCEPTION**:
Explicit comparisons to `true` or `false` are necessary if the value being compared is a nullable `bool`.

---

## Don't change a loop variable inside a `for` or `foreach` loop · CS-01.13 · **MUST**

Updating the loop variable within the loop body is generally considered confusing. It is even worse that the loop variables are modified in more than one place in the loop. Consider `break` or `continue` instead, to change the loop variables.

```csharp
for (int index = 0; index < 10; ++index)
{
    if (some condition)
    {
        index = 11; // Wrong! Use 'break' or 'continue' instead.
    }
}
```

**Roslyn Analyzer Rule [AV1530](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1530-dont-change-a-loop-variable-inside-a-for-loop-)**

---

## Avoid nested loops · CS-01.14 · **SHOULD**

A method that nests loops is more difficult to understand than one with only a single loop. In fact, in most cases having nested loops can be replaced with a much simpler LINQ query that uses the `from` keyword twice or more to join the data.

**Roslyn Analyzer Rule [AV1532](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1532-avoid-nested-loops-)**

---

## Always add a block after keywords such as `if`, `else`, `while`, `do`, `for` and `foreach` · CS-01.15 · **MUST**

Please note that this also avoids possible confusion in statements of the form:

```csharp
// The wrong way:
if (b1) if (b2) Foo(); else Bar(); // which 'if' goes with the 'else'?

// The right way:
if (b1)
{
    if (b2)
    {
        Foo();
    }
    else
    {
        Bar();
    }
}
```

**EXCEPTION**:
This can be omitted in the case of an `if` statement that is performing a null argument check, e.g. the following is ok:

```csharp
if (arg == null) throw new ArgumentNullException(nameof(arg));
```

**Roslyn Analyzer Rule [ACL1006](https://github.com/audaciaconsulting/Audacia.CodeAnalysis/blob/master/dotnet-roslyn/analyzers/Audacia.CodeAnalysis.Analyzers/README.md#acl1006---code-block-does-not-have-braces)**

---

## Always add a default block after the last `case` in a `switch` statement · CS-01.16 · **MUST**

Add a descriptive comment if the default block is supposed to be empty. Moreover, if that block is not supposed to be reached, throw an `InvalidOperationException` to detect future changes that may fall through the existing cases. This ensures better code, because all paths the code can travel has been thought about.

```csharp
void Foo(string answer)
{
    switch (answer)
    {
        case "no":
            Console.WriteLine("You answered with No");
            break;
        case "yes":
            Console.WriteLine("You answered with Yes");
            break;
        default:
            // Not supposed to end up here.
            throw new InvalidOperationException("Unexpected answer " + answer);
    }
}
```

**Roslyn Analyzer Rule [AV1536](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1536-always-add-a-default-block-after-the-last-case-in-a-switch-statement-)**

---

## Be reluctant with multiple return statements · CS-01.17 · **SHOULD**

One entry, one exit is a sound principle and keeps control flow readable. However, if the method is very small may actually improve readability over some central boolean flag that is updated at various points.

---

## Don't use `if`-`else` statements instead of a simple (conditional) assignment · CS-01.18 · **SHOULD**

Express your intentions directly. For example:

```csharp
// Bad practice
bool pos;
if (val > 0)
{
    pos = true;
}
else
{
    pos = false;
}

// Preferred practice
bool pos = (val > 0); // initialisation
```

Or this can be another alternative:

```csharp
// Avoid
string result;
if (someString != null)
{
    result = someString;
}
else
{
    result = "Unavailable";
}
return result;

// Instead
return someString ?? "Unavailable";
```

---

## Encapsulate complex expressions in a method or property · CS-01.19 · **MUST**

Consider the following example:

```csharp
if (member.HidesBaseClassMember && (member.NodeType != NodeType.InstanceInitializer))
{
    // do something
}
```

In order to understand what this expression is about, you need to analyse its exact details and all the possible outcomes. Obviously, you could add an explanatory comment on top of it, but it is much better to replace this complex expression with a clearly named method like:

```csharp
if (NonConstructorMemberUsesNewKeyword(member))
{
    // do something
}

private bool NonConstructorMemberUsesNewKeyword(Member member)
{
    return member.HidesBaseClassMember &&
           member.NodeType != NodeType.InstanceInitializer;
}
```

You still need to understand the expression if you are modifying it, but the calling code is now much easier to grasp.

---

## Call the most overloaded method from other overloads · CS-01.20 · **SHOULD**

This guideline only applies to overloads that are intended for providing optional arguments. Consider for example:

```csharp
public class MyString
{
    private string someText;

    public MyString(string text)
    {
        this.someText = text;
    }

    public int IndexOf(string phrase)
    {
        return IndexOf(phrase, 0, someText.Length);
    }

    public int IndexOf(string phrase, int startIndex)
    {
        return IndexOf(phrase, startIndex, someText.Length - startIndex );
    }

    public virtual int IndexOf(string phrase, int startIndex, int count)
    {
        return someText.IndexOf(phrase, startIndex, count);
    }
}
```

The class `MyString` provides three overloads for the `IndexOf` method, but two of them simply call the one with the most parameters.

**Note**:
The same rule can apply to class constructors; implement the most complete overload and call that one from the other overloads using the `this()` operator.

**Note**:
The parameters with the same name should appear in the same position in all overloads.

**Important**:
If you also want to allow derived classes to override these methods, define the most complete overload as a `protected virtual` method that is called by all overloads.

**Roslyn Analyzer Rule [AV1551](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1551-call-the-more-overloaded-method-from-other-overloads-)**

---

## Don't allow methods and constructors with more than 4 parameters · CS-01.21 · **MUST**

If you end up with a method with more than four parameters, use a structure or class for passing multiple arguments such as explained in the [Specification](http://en.wikipedia.org/wiki/Specification_pattern) design pattern. In general, the fewer the number of parameters, the easier it is to understand the method. Additionally, unit testing a method with many parameters requires many scenarios to test.

**Roslyn Analyzer Rule [ACL1003](https://github.com/audaciaconsulting/Audacia.CodeAnalysis/blob/master/dotnet-roslyn/analyzers/Audacia.CodeAnalysis.Analyzers/README.md#acl1003---dont-declare-signatures-with-more-than-a-predefined-number-of-parameters)**

---

## Avoid using `ref` or `out` parameters · CS-01.22 · **MUST**

They can make code less understandable and might cause people to introduce bugs. Prefer returning compound objects instead.

**EXCEPTION**:
`TryParse` is a common pattern that uses an `out` parameter appropriately.

**Roslyn Analyzer Rule [AV1562](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1562-dont-use-ref-or-out-parameters-)**

---

## Avoid methods that take a `bool` flag  · CS-01.23 · **MUST**

Consider the following method signature:

```csharp
public Customer CreateCustomer(bool platinumLevel) {}
```

On first sight this signature seems perfectly fine, but when calling this method you will lose this purpose completely:

```csharp
Customer customer = CreateCustomer(true);
```

Often, a method taking such a flag is doing more than one thing and needs to be refactored into two or more methods. An alternative solution is to replace the flag with an enumeration.

**Roslyn Analyzer Rule [AV1564](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1564-avoid-signatures-that-take-a-bool-parameter-)**

---

## Don't use parameters as temporary variables · CS-01.24 · **SHOULD**

Never use a parameter as a convenient variable for storing temporary state. Even though the type of your temporary variable may be the same, the name usually does not reflect the purpose of the temporary variable.

**Roslyn Analyzer Rule [AV1568](https://github.com/bkoelman/CSharpGuidelinesAnalyzer/blob/master/docs/Overview.md#av1568-dont-use-parameters-as-temporary-variables-)**

---

## Prefer using pattern matching with the `is` operator over using the `as` operator · CS-01.25 · **MUST**

Using the `is` operator and pattern matching is a more concise way of safely type-casting an object. So the first code snippet is preferred over the second.

```csharp
// Good
if (obj is MyClass myClass)
{
    // Do stuff
}

// Bad
var myClass = obj as MyClass;
if (myClass != null)
{
    // Do stuff
}
```

---

## Don't comment out code · CS-01.26 · **MUST**

Never check-in code that is commented-out, but instead use a work item tracking system to keep track of some work to be done. Nobody knows what to do when they encounter a block of commented-out code. Was it temporarily disabled for testing purposes? Was it copied as an example? Should I delete it?

---

## Allow gaps between numeric values for enums · CS-01.27 · **SHOULD**

When defining the numeric values for an `enum` you should use 100, 200, etc. to allow for additional values to be added in between in future.

**IMPORTANT** This is a **MUST** when the `enum` has a natural order between its members, e.g. it represents a status of some kind that the system transitions through.

For example:
```csharp
public enum Status
{
    None = 0,
    Draft = 100,
    InProgress = 200,
    Complete = 300
}
```

**EXCEPTION**:
If the `enum` is a set of flags (i.e. can be treated as a bit field, decorated with the `[FlagsAttribute]`) then the numeric values must be powers of two.

---

## Enable nullable reference types · CS-01.28 · **MUST**

For example, add the below to every .csproj at or above .NET Core 3 or .NET Standard 2.1. For existing codebases, this is something to work towards rather than mandated (and therefore a **SHOULD**), and can be introduced on a project-by-project basis if needed.

```xml
<nullable>enable</nullable>
```

---

## Prefer switch expressions · CS-01.29 · **MUST**

For example, rather than this old-style `switch` statement:
```csharp
switch (item)
{
    case Item.None:
        return 0;
    case Item.One:
        return 1;
    default:
        return 2;
}
```

prefer a `switch` expression:
```csharp
return item switch
{
    Item.None => 0,
    Item.One => 1,
    _ => 2,
};
```

**Roslyn Analyzer Rule [IDE0066](https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0066)**

---

## Use using declarations · CS-01.30 · **MUST**

For example, rather than an old-style `using` statement like this:
```csharp
using (var stream = new MemoryStream())
{
    // do stuff
}
```

prefer a `using` declaration:
```csharp
using var stream = new MemoryStream();
// do stuff
```

**Roslyn Analyzer Rule [IDE0063](https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0063)**
