Skip to main content
The osu! project follows strict code style guidelines enforced through EditorConfig and code analysis tools. This ensures consistency across the codebase and makes code easier to read and maintain.

File Encoding and Line Endings

C# Files (.cs)

end_of_line = crlf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

Project Files (.csproj, .props, .targets)

charset = utf-8-bom
end_of_line = crlf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

YAML Files (.yaml, .yml)

insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

License Header

All C# source files must include the following license header:
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

Naming Conventions

The project uses strict naming conventions enforced at the error or warning level.

Public and Protected Members

Use PascalCase for public, internal, protected, and protected internal members:
public class GameplayScreen
{
    public int CurrentCombo { get; set; }
    protected void UpdateScore() { }
    public string PlayerName;
}
Violating this rule will produce an error during build.

Private Members

Use camelCase for private properties, methods, fields, and events:
private int currentScore;
private string userName;
private void calculateTotal() { }

Local Functions

Use camelCase for local functions:
public void ProcessData()
{
    int result = calculateSum();
    
    int calculateSum()
    {
        return 42;
    }
}

Constants and Static Readonly Fields

Private constants and static readonly fields use all_lower with underscores:
private const int max_combo = 1000;
private static readonly string default_player_name = "Guest";
Public constants and static readonly fields use ALL_UPPER with underscores:
public const int MAX_PLAYERS = 16;
public static readonly string DEFAULT_RULESET = "osu";

Code Formatting

Indentation

switch (value)
{
    case 1:
        DoSomething();
        break;
    case 2:
    {
        // Block content is not indented relative to case
        DoSomethingElse();
        break;
    }
    default:
        DoDefault();
        break;
}
  • Case contents are indented
  • Case contents when using blocks are not indented
  • Labels are indented one less than current
  • Switch labels are indented

New Lines and Braces

Open braces on new lines (Allman style):
public void Method()
{
    if (condition)
    {
        DoSomething();
    }
    else
    {
        DoSomethingElse();
    }
}

public class MyClass
{
    // Class content
}
Control flow keywords require new lines:
try
{
    DoSomething();
}
catch (Exception ex)
{
    Handle(ex);
}
finally
{
    Cleanup();
}

Spacing

1

After casts

No space after cast:
int value = (int)someDouble;
2

In inheritance clauses

Space before and after colons:
public class Derived : BaseClass
3

Control flow statements

Space after keywords:
if (condition)
while (running)
for (int i = 0; i < 10; i++)
4

Method declarations and calls

No space between method name and opening parenthesis:
public void MethodName(int param)
MethodName(42);

Using Directives

using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Rulesets;
System directives are sorted first, followed by other namespaces in alphabetical order.

C# Language Styles

this. Qualification

Do not use this. qualification unless absolutely necessary:
// Good
int value = someField;
SomeMethod();

// Bad
int value = this.someField;
this.SomeMethod();

Type Names

Use predefined type names for locals, parameters, and members:
// Good
int count = 0;
string name = "player";

// Bad
Int32 count = 0;
String name = "player";

var Usage

Use var when type is apparent:
var player = new Player();
var score = GetScore();
Do not use var for built-in types:
// Good
int count = 5;
string name = "test";

// Bad
var count = 5;
var name = "test";
Use var elsewhere (silent suggestion):
var result = CalculateComplexValue();

Expression Bodies

Use expression bodies for:
  • Accessors (warning level)
  • Indexers (warning level)
  • Operators (warning level)
  • Properties (warning level)
  • Local functions (silent suggestion)
public int Value => backingField;
public string Name
{
    get => name;
    set => name = value;
}
public static int operator +(MyClass a, MyClass b) => a.Value + b.Value;
Do not use expression bodies for:
  • Constructors
  • Methods (silent suggestion)
public void ComplexMethod()
{
    // Multiple statements
    DoSomething();
    DoSomethingElse();
}

Pattern Matching

Prefer pattern matching over traditional type checks:
// Good
if (obj is Player player)
{
    player.UpdateScore();
}

// Avoid
if (obj is Player)
{
    Player player = (Player)obj;
    player.UpdateScore();
}

Null Checking

Use null-coalescing and null-propagation operators:
// Null-coalescing
string name = player?.Name ?? "Unknown";

// Null-propagation
player?.UpdateScore();

// Null-coalescing assignment
playerName ??= "Guest";

Modern C# Features

Prefer:
  • Object initializers (warning)
  • Collection initializers (warning)
  • Inferred tuple names (warning)
  • Inferred anonymous type member names (warning)
  • Auto-properties (warning)
  • Compound assignments (warning)
  • Static local functions (warning)
  • Simple using statements (silent)
Avoid:
  • Index operator ^ (silent)
  • Range operator .. (silent)
  • Switch expressions (none)
  • Primary constructors (avoid)

Namespace Declarations

Use block-scoped namespaces:
namespace osu.Game.Screens.Menu
{
    public class MainMenu
    {
        // Class content
    }
}
File-scoped namespaces are not used in this project.

Code Analysis Tools

The project uses multiple tools to enforce code style and quality.

Running Code Analysis

1

Restore tools

dotnet tool restore
2

Run file sanity checks

dotnet CodeFileSanity
3

Run InspectCode

# Linux/macOS
./InspectCode.sh

# Windows
.\InspectCode.ps1
This runs JetBrains InspectCode and generates a report at inspectcodereport.xml.
4

Check for warnings

dotnet nvika parsereport inspectcodereport.xml --treatwarningsaserrors
This parses the report and treats warnings as errors.

dotnet format

You can also use dotnet format to automatically fix formatting issues:
dotnet format
Run code analysis before submitting any pull request. This is especially important for first-time contributors, as CI will not run automatically until a maintainer approves it.

Modifier Order

Modifiers should appear in the following order:
public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async
Example:
public static readonly string DEFAULT_NAME = "osu!";
protected virtual void OnUpdate() { }
private async Task LoadDataAsync() { }

Build docs developers (and LLMs) love