C# Style
This section defines stylistic, structural, and formatting standards for C# source code.
Naming Conventions
Naming conventions are not covered in this section.
See Naming Conventions.
Formatting
Intricate uses .editorconfig to enforce code-styling. In Visual Studio, the code formatter can be run by pressing the hotkey chord Ctrl+K, Ctrl+D on an open file.
Info
All formatting rules denoted by a * are automatically applied by .editorconfig or Visual Studio.
Indentation
- Use 4 spaces per indentation level.*
- Do not use tab characters.
- Continuation lines should be indented once with respect to the current indentation level.*
Tip
Pressing the tab key will add 4 spaces instead of a tab character for indentation when .editorconfig is enabled.
Line Length
- Soft limit: 130 characters
- Split long expressions across multiple physical lines if required.
- Break long expressions at logical boundaries (e.g. in-between parameters of a method call.)
Whitespace and Newlines
- Use exactly one blank line between:
- Method definitions
- Class members grouped by purpose
- Logical blocks of code grouped by purpose
- Do not leave trailing whitespace.*
- Use exactly one space after commas and semicolons inside parameter lists and other constructs.*
- Use exactly one space before and after binary operators.*
- Example:
a + b,x == 3,value * 2 - No spaces for:
- Indexing:
arr[i] - Unary operators:
-x,!flag,~mask,++i,i--
- Indexing:
- Example:
- Insert a final newline at the end of source files.*
Warning
More than one blank line should never be used anywhere other than in-between the using directives and the namespace declaration.
Braces
- Use the Allman style*:
public void Foo()
{
if (true)
{
// Block-bodies covering multiple lines must always have the opening brace on a newline.
}
}
- Omit the braces for single-line control-flow blocks:
- Empty function bodies should be defined as:
Control Flow Blocks
- Do not inline control-flow blocks:
else,catch,finallyandcasemust always appear on its own line.- Prefer ternary expressions over simple
if-elseblocks.
File Structure
File Layout
- Order elements in the following sequence unless justified otherwise:
- File header, version history or copyright (if applicable)
- Using directives
- Two blank lines
- Namespace declaration
- Enum declaration(s)
- Class/struct/record/interface declaration(s) (in order of dependency)
Type layout:
- Constants
- Nested Types
- Constructors (
static→public→internal→protected→private) - Destructor/Finalizer
- Public methods
- Overrides (including
ToString,Equals,GetHashCode) - Interface methods
- Conversion operators
- Operator overloads
- Overrides (including
- Public properties
- Public events
- Public static readonly fields
- Public static methods
- Internal/protected methods
- Private methods
- Private static methods
- Instance variables (
public→internal→protected→private) - Private properties (rare, avoid using)
Using Directives
- Place
usingstatements outside of namespaces. - Group and sort (alphabetical):
- Our project's namespaces
- External package/submodule namespaces
- System namespaces
- Insert two blank lines after the final
usingdirective before the namespace declaration.
Comments
Documentation
Use XML documentation comments for:
- Public types
- Public and protected members
Note
We may switch to Doxygen comments soon.
Commenting Style
See: Comments.
Code Style
Modifier Order
Field modifiers should appear in the following order:
// This ordering is enforced by .editorconfig and can be auto-applied from Visual Studio hints
public, private, protected, internal, static, extern, new, virtual, abstract, sealed, partial, override, readonly, unsafe, volatile, async
Expression Syntax
- Use expression-bodied members for trivial property getters and simple inline methods:
public int Count => m_Count;
public float Mass
{
get => Bindings.RigidBody_GetMass(m_NativeID);
set => Bindings.RigidBoody_SetMass(m_NativeID, value);
}
public bool Awake() => Bindings.RigidBody_Awake(m_NativeID);
- Use block-bodies for everything else.
Pattern Matching
- Favor pattern matching over explicit type casting when checking types.
public class Entity
{
/* ... */
// Explicit type casting in a block-bodied member
public override bool Equals(object other)
{
if (other is null)
return false;
if (other is not Entity)
return false;
return m_NativeID == ((Entity)other).m_NativeID;
}
// Pattern matching in an expression-bodied member
public override bool Equals(object other) => obj is Entity entity && (m_NativeID == entity.m_NativeID);
}
Immutability
- Prefer
readonlywhere applicable. - Avoid modifying method parameters unless it's an
outorrefparameter.
LINQ
- Using LINQ is encouraged for clarity and expressiveness, but not required.
- Avoid deeply nested and "clever" LINQ chains - keep it readable.
Unsafe
- Minimize the
unsafescope as far as possible. - Avoid declaring methods and types as
unsafe.
// Avoid declaring the method as unsafe
public void CopyToNative(ReadOnlySpan<byte> data, nint dst)
{
ulong sizeBytes = data.Length; // Unsafe is not needed for this
// Rather open the unsafe block here
unsafe
{
fixed (byte* ptr = data)
Buffer.MemoryCopy(ptr, dst.ToPointer(), sizeBytes);
}
}
- Avoid passing and storing raw pointers, use
nintinstead.
Source Control Expectations
- Changes must adhere to this style guide.
- Automated formatting tools should be run prior to commit.
Note
In-future we may implement CI linting and formatting.