Skip to main content

Overview

The general rule: “use Visual Studio defaults”. Using an IDE that supports the .editorconfig standard makes following these guidelines automatic.
An EditorConfig file (.editorconfig) is provided at the repository root for automatic C# formatting.

Core Principles

Brace Style

if (condition)
{
    DoSomething();
}

if (x > 0)
{
    Positive();
}
else
{
    Negative();
}
Rule 1: Use Allman style braces - each brace begins on a new line.

Single-Line Statement Blocks

Single-line statement blocks can omit braces, but must:
  • Be properly indented on its own line
  • Not be nested in other statement blocks with braces
if (condition)
    DoSomething();

// Exception: nested using statements
using (var resource1 = GetResource())
using (var resource2 = GetResource())
{
    UseResources(resource1, resource2);
}

Indentation and Spacing

Rule 2: Use four spaces for indentation (no tabs).
public class Example
{
    public void Method()
    {
        if (condition)
        {
            DoSomething();
        }
    }
}
Rule 7: Avoid more than one empty line at any time. Rule 8: Avoid spurious free spaces.
Good
if (someVar == 0)
Bad - spurious spaces
if (someVar == 0)···
Enable “View White Space” in Visual Studio (Ctrl+R, Ctrl+W) to detect trailing spaces.

Naming Conventions

Fields

Rule 3: Use _camelCase for internal and private fields with readonly where possible.
private readonly string _name;
private int _count;
Field TypePrefixExample
Private/Internal instance__camelCase
Statics_s_camelCase
Thread-statict_t_camelCase
PublicnonePascalCase

Visibility Modifiers

Rule 5: Always specify visibility, even if it’s the default. Visibility should be the first modifier.
Correct
private string _foo;
public abstract class MyClass;
public static readonly int MaxValue = 100;
Incorrect
string _foo; // Missing visibility
abstract public class MyClass; // Wrong order
readonly static int MaxValue = 100; // Wrong order

Language Features

The this. Keyword

Rule 4: Avoid this. unless absolutely necessary.
Good
public void SetName(string name)
{
    _name = name;
}
Only when needed
public void SetName(string name)
{
    this.name = name; // Only if there's a public property called 'name'
}

The var Keyword

Rule 10: Use var only when the type is explicitly named on the right-hand side.
var stream = new FileStream("file.txt", FileMode.Open);
var count = (int)GetCount();
var items = new List<string>();

Target-Typed new()

Rule 10: Target-typed new() can only be used when type is explicitly named on the left-hand side in a definition statement.
Correct
FileStream stream = new("file.txt", FileMode.Open);
List<string> items = new();
Incorrect
stream = new("file.txt", FileMode.Open); // Type specified on previous line

Language Keywords vs BCL Types

Rule 11: Use language keywords instead of BCL types.
Correct
int count = 0;
string name = "Ryujinx";
float value = 3.14f;
int parsed = int.Parse("42");
Incorrect
Int32 count = 0;
String name = "Ryujinx";
Single value = 3.14f;
Int32 parsed = Int32.Parse("42");

Naming and Documentation

Method Names

Rule 13: Use PascalCasing for all method names, including local functions.
public void ProcessData()
{
    void LocalHelper(int value)
    {
        // Local function
    }
    
    LocalHelper(42);
}

Constants

Rule 12: Use PascalCasing for all constant variables and fields.
private const int MaxRetries = 3;
public const string DefaultName = "Ryujinx";

void Method()
{
    const int BufferSize = 1024;
}
Exception: Interop code constants should match the exact name and value of the external API.

nameof() Usage

Rule 14: Use nameof(...) instead of "..." whenever possible.
Good
if (value == null)
    throw new ArgumentNullException(nameof(value));

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
Avoid
if (value == null)
    throw new ArgumentNullException("value");

Code Organization

Namespace Imports

Rule 6: Namespace imports should be at the top of the file, outside of namespace declarations.
Correct
using System;
using System.Collections.Generic;
using Ryujinx.Common;

namespace Ryujinx.Graphics.Gpu.Shader
{
    public class ShaderCache
    {
    }
}

Field Placement

Rule 15: Fields should be specified at the top within type declarations.
public class Example
{
    private readonly int _capacity;
    private static readonly string s_version = "1.0";
    
    public Example(int capacity)
    {
        _capacity = capacity;
    }
    
    public void Process()
    {
    }
}

Control Flow

Single-Statement if

Rule 18: When using a single-statement if:
// Never use single-line form
if (source == null) throw new ArgumentNullException("source");

Labels and goto

Rule 17: When using labels, indent one less than the current indentation.
void Method()
{
    if (condition)
    {
    retry:
        if (TryOperation())
            return;
        
        if (ShouldRetry())
            goto retry;
    }
}

Special Rules

File-Specific Styles

Rule 9: If a file differs from these guidelines, the existing style in that file takes precedence.
// If this file uses m_member instead of _member,
// continue using m_member for consistency
private int m_count;

Type Design

Rule 19: Make internal and private types static or sealed unless derivation is required.
internal sealed class HelperClass
{
}

private static class Constants
{
}

XML Documentation

Rule 20: Use XML docs for:
  • Interfaces
  • Classes/methods with sufficient scope or complexity
/// <summary>
/// Memory cache of shader code.
/// </summary>
class ShaderCache : IDisposable
{
    /// <summary>
    /// Default flags used on the shader translation process.
    /// </summary>
    public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;
}

Magic Numbers

Rule 21: Define magic numbers as named constants.
Good
private const int CurrentAge = 56;
private const int RetireAge = 68;

for (int i = CurrentAge; i < RetireAge; i++)
{
}
Avoid
for (int i = 56; i < 68; i++) // What do these numbers mean?
{
}
This may be ignored for trivial or syntactically common statements.

Non-ASCII Characters

Rule 16: Use Unicode escape sequences (\uXXXX) instead of literal non-ASCII characters.
Correct
string copyright = "\u00A9 2024 Ryujinx";
Avoid
string copyright = "© 2024 Ryujinx"; // May get garbled

Complete Example

From src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs:
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.Threed;
using Ryujinx.Graphics.Gpu.Engine.Types;
using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

namespace Ryujinx.Graphics.Gpu.Shader
{
    /// <summary>
    /// Memory cache of shader code.
    /// </summary>
    class ShaderCache : IDisposable
    {
        /// <summary>
        /// Default flags used on the shader translation process.
        /// </summary>
        public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode;

        private readonly struct TranslatedShader
        {
            public readonly CachedShaderStage Shader;
            public readonly ShaderProgram Program;

            public TranslatedShader(CachedShaderStage shader, ShaderProgram program)
            {
                Shader = shader;
                Program = program;
            }
        }
        
        /// <summary>
        /// Processes the queue of shaders that must save their binaries to the disk cache.
        /// </summary>
        public void ProcessShaderCacheQueue()
        {
            // Check to see if the binaries for previously compiled shaders are ready, and save them out.

            while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave))
            {
                ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false);

                if (result != ProgramLinkStatus.Incomplete)
                {
                    if (result == ProgramLinkStatus.Success)
                    {
                        _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary());
                    }

                    _programsToSaveQueue.Dequeue();
                }
                else
                {
                    break;
                }
            }
        }
    }
}

Enforcement Tools

EditorConfig

The .editorconfig file at the repository root automatically enforces many of these rules in compatible IDEs.

dotnet format

Run before committing:
dotnet format
This tool automatically fixes style violations.
All PRs must pass dotnet format checks in CI. Run this locally before pushing.

IDE Support

IDEEditorConfig Support
Visual Studio 2022+Built-in
Visual Studio CodeExtension required
JetBrains RiderBuilt-in
Visual Studio for MacBuilt-in

Next Steps

PR Guide

Submit your first pull request

Testing

Write unit tests

Building

Build Ryujinx from source

Build docs developers (and LLMs) love