Contributing code style - rebaserHEAD/NexusForever GitHub Wiki
How upstream's C# looks, from the file down to the line. Each rule names its source: written on upstream's code style page, editor from Source/NexusForever.sln.DotSettings, review when reviewers ask for it, or habit when nearly all of the code does it without anything saying so. Which source wins when they disagree is on Contributing.
Files
The file itself
Rule
Source
Note
CRLF line endings
written
.gitattributes enforces it for *.cs
A newline at the end
written, editor
British English in identifiers, comments and commit messages
written, review
Initialise, Customisation, Neighbour. Names taken from the client's tables or from .NET keep their own spelling: DyeColorRamp, Serialize
UTF-8, usually with a byte-order mark
habit
Four spaces, no tabs
habit
One top-level type per file, named after it
habit
Nested types are the exception, below
About 120 characters a line
habit
A soft limit; nothing enforces it
Namespaces and usings
Rule
Source
Note
Block-scoped namespaces
habit
A handful of files in the newer services are file-scoped
The namespace is the project plus the folder path
habit, review
Folder names are singular
using directives outside the namespace, in one block with no blank lines
habit
Then one blank line, then namespace
System first, then alphabetical
habit
Aliases (using X = ...;) go last. No global using, no using static
Implicit usings aren't repeated
habit
Most projects switch ImplicitUsings on and leave out using System; and friends. The projects that don't write them out
No unused usings
review
Types and members
Declarations
Rule
Source
Note
Explicit access modifiers,private included
habit
sealed only on Singleton<T> managers
habit
Other classes are left open
Member order: constants and static fields, instance fields (readonly first), properties, constructors, interface members, other members, nested types
editor
The usual drift is a backing field placed straight after its property
No this. qualifier
editor
Except where a parameter shares the field's name, as in a constructor: this.log = log;
A constructor initializer on its own line
editor
: base(movementManager) under the parameter list
Attributes on their own line, on parameters too in practice
written, editor
The written rule allows a parameter attribute inline, but every [Parameter] on a command sits on its own line
Nested types
Three kinds of type are nested, and little else:
Nested type
Where it goes
The save mask
An enum at the top of the class whose changes it tracks
Message sub-structures
Inside the message model that carries them
Command subcategories
Inside the command category
The dependency injection region
A class with injected services wraps its fields and constructor in a region. The parameters go one per line, even when there's only one, and the assignments are aligned. From ClientChatHandler:
A constructor that takes an owner object instead of services, such as a manager built with (IPlayer owner, CharacterModel model), has no region. Regions aren't used for anything else.
Locals and expressions
Rule
Source
Note
Explicit types, with var only for new
habit
The editor setting asks for explicit types outside built-ins. The code goes slightly further: var x = new Thing() is common, and everything else is typed, foreach and out included
Block-bodied methods, one-liners included
habit
Expression bodies (=>) are for properties and accessors
== null and != null
habit
is null is rare upstream
$"..." interpolation
habit
Never string.Format
Collection expressions for new collections
habit, review
List<uint> Ids { get; } = [];
switch statements more than switch expressions
habit
LINQ at load time, loops on the tick
habit
Index building at start-up uses LINQ; Update bodies almost never do
The Async suffix on new asynchronous methods
habit
Formatting
Braces
Rule
Source
Allman braces
written
No braces around a single-line body of if, else, for or foreach
written, editor
In an if/else, only a branch spanning several lines takes braces
written
An empty method keeps its braces, on separate lines
written
An auto-property keeps { get; set; } on one line; an accessor with a body puts its braces on separate lines
written
Spacing and alignment
Rule
Source
Note
A blank line between members
habit
Consecutive auto-properties sit together with none
A blank line after a one-line if (...) return;
habit
= aligned in a run of assignments
habit
Object initializers, constructor assignments, enum members. Message Read bodies are the exception
A wrapped call breaks after (, one argument per line
habit
A fluent chain breaks before the .
habit
Naming
Identifiers
Thing
Style
Source
Example
Types, namespaces, methods, properties, public fields
PascalCase
written
Interfaces
I and PascalCase
written
IGuildManager
Locals and parameters
lowerCamel
written
Private and protected fields, world side
lowerCamel, no prefix
written, editor
log, entityFactory
Private and protected fields, broker services
_ and lowerCamel
habit
The Server.* and API.* projects, and the chat, group, friendship and query database projects
The logger field
log
habit
_log in the projects above
The IServiceCollection parameter
sc
habit
Ids
unsigned
habit
uint for most, ulong for character ids and guids, ushort for realm ids
Names from the client
Where the client has a name for something, in its tables or its UI scripts, reviewers ask for that name over an invented one. Message and handler names put the area first and stay consistent across a family: ServerPetClearFlair, not ServerClearPetFlair. Names for the rest of the server's moving parts are on Contributing code patterns.
Comments
Documentation comments
Rule
Source
Note
A /// <summary> on every public method
written
The code drifts most on message Read and Write, handlers' HandleMessage and constructors. Write one anyway
The three-line form, a sentence with a full stop
habit
A verb first
habit
"Returns ...", "Initialise ...", and "Invoked when ..." for On hooks
<see cref="..."/> for the types a summary names
habit
The same summary on the interface and the class
habit
The text is copied; <inheritdoc/> isn't used
<param>, <returns>, <exception>
written
Optional
Update methods carry a stock summary, copied from IUpdate: "Invoked each world tick with the delta since the previous tick occurred."
Line comments
Rule
Source
Note
// with a space, no closing full stop
habit
// TODO: for a known gap
habit
FIXME and HACK are almost never used
Evidence goes beside what it explains
habit
In message models: what a capture showed, or which table a value comes from
Literals, enums and logging
Literals
Rule
Source
Example
Lower-case suffixes
habit
0u, 1ul, 0.5f
Upper-case hex digits
habit
0x1A, never 0x1a
Bit widths as unsigned literals
habit
writer.Write(Result, 8u)
Enums
Rule
Source
Note
PascalCase names and values
written
Values ordered, = aligned
written
A list kept in another order says so in a comment
No comma after the last value
written
Explicit values, in hex for flags
habit
Flags are usually padded: 0x0001
The default int base
habit
: uint only when a value needs bit 31
In Game.Static, one per file
habit
Under a folder for its area
Logging
Rule
Source
Note
ILogger<T> injected in new code
habit
Older classes hold NLog's LogManager.GetCurrentClassLogger() in a static field
Interpolated messages on the world side
habit
Structured templates ({Name}) in the broker services and scripts
Trace for per-entity events, Info for start-up and load counts, Warn for unexpected data, Error for exceptions
habit
Debug is barely used
Exceptions
Situation
What's thrown
A client sent a value it couldn't have
InvalidPacketValueException, with no message. The session logs it and disconnects the client