Skip to main content

C# Coding Standards

· 20 min read
Byungnam Lee

Preface

For detailed examples, go to Unity Project Template

Rule of Thumb

  1. Readability first (your code should be your documentation most of the time)
  2. Follow IDE's auto formatted style unless you have really good reasons not to do so. (Ctrl + K + D in Visual Studio)
  3. Learn from existing code
  4. Rely on UWP_Namespace for Kinect-realted codes

References

This coding standards is inspired by these coding standards

IDE Helper

The settings that can imported into your IDE can be found here.

I. Main Coding Standards

  1. Use Pascal casing for class and structs

    class PlayerManager;
    struct PlayerData;
  2. Use camel casing for local variable names and function parameters

    public void SomeMethod(int someParameter)
    {
    int someNumber;
    int id;
    }
  1. Use verb(base form)-object pairs for method names, by default.

    public uint GetAge()
    {
    // function implementation...
    }
  1. However, if a method simply returns a boolean state, the verb part of the name should be prefixed Is, Can, Has or Should. If the function name becomes not natural by doing so, use the 3rd-person singular form of another verb.

    public bool IsAlive(Person person);
    public bool Has(Person person);
    public bool CanAccept(Person person);
    public bool ShouldDelete(Person person);
    public bool Exists(Person person);
  2. Use pascal casing for all method names except (see below)

    public uint GetAge()
    {
    // function implementation...
    }
  3. Use camel case for any non-public method. You might need to add custom Visual Studio style rule as described here

    private uint getAge()
    {
    // function implementation...
    }
  4. Use ALL_CAPS_SEPARATED_BY_UNDERSCORE for constants

    const int SOME_CONSTANT = 1;
  5. Use static readonly if an object is a constant

    public static readonly MyConstClass MY_CONST_OBJECT = new MyConstClass();
  6. Use ALL_CAPS_SEPARATED_BY_UNDERSCORE for static readonly variables

  7. Use readonly when a variable must be assigned only once

    public class Account
    {
    private readonly string mPassword;

    public Account(string password)
    {
    mPassword = password;
    }
    }
  8. Use pascal casing for namespaces

    namespace System.Graphics
  9. prefix boolean variables with b.

    bool bFired;        // for local variable
    private bool mbFired; // for private member variable
  10. prefix boolean properties with Is, Can, Should or Has.

    public bool IsFired { get; private set; }
    public bool HasChild { get; private set; }
    public bool CanModal { get; private set; }
    public bool ShouldRedirect { get; private set; }
  1. prefix interfaces with I

    interface ISomeInterface;
  2. prefix enums with E

    public enum EDirection
    {
    North,
    South
    }
  3. prefix structs with S unless they are readonly structs

    public struct SUserID;
  4. prefix private member variables with m. Use Pascal casing for the rest of a member variable

    Public class Employee
    {
    public int DepartmentID { get; set; }
    private int mAge;
    }
  5. Methods with return values must have a name describing the value returned

    public uint GetAge();
  6. Use descriptive variable names. e.g index or employee instead of i or e unless it is a trivial index variable used for loops.

  7. Capitalize every character in acronyms only if there is no extra word after them.

    public int OrderID { get; private set; }
    public string HttpAddress { get; private set; }
  8. Prefer properties over getter setter functions

BAD:
```cs
public class Employee
{
private string mName;
public string GetName();
public string SetName(string name);
}
```

GOOD:
```cs
public class Employee
{
public string Name { get; set; }
}
```
  1. Declare local variables as close as possible to the first line where it is being used.
  1. Use precision specification for floating point values unless there is an explicit need for a double.

    float f = 0.5F;
  2. Always have a default: case for a switch statement.

    switch (number)
    {
    case 0:
    ...
    break;
    default:
    break;
    }
  1. If default: case must not happen in a switch case, always add Debug.Assert(false); or Debug.Fail();

    switch (type)
    {
    case 1:
    ...
    break;
    default:
    Debug.Fail("unknown type");
    break;
    }
  2. Names of recursive functions end with Recursive

    public void FibonacciRecursive();
  3. Order of class variables and methods must be as follows:

    a. public variables/properties
    b. internal variables/properties
    c. protected variables/properties
    d. private variables
    Exception: if a private variable is accessed by a property, it should appear right before the mapped property.
    e. constructors
    f. public methods
    g. Internal methods
    h. protected methods
    i. private methods
  4. If parameter types are general, function overloading must be avoided

Use:
```cs
public Anim GetAnimByIndex(int index);
public Anim GetAnimByName(string name);
```

Instead of:
```cs
public Anim GetAnim(int index);
public Anim GetAnim(string name);
```
  1. Each class must be in a separate source file unless it makes sense to group several smaller classes.

  2. The filename must be the same as the name of the class including upper and lower cases.

    public class PlayerAnimation {}

    PlayerAnimation.cs

  3. When a class spans across multiple files(i.e. partial classes), these files have a name that starts with the name of the class, followed by a dot and the subsection name.

    public partial class Human;

    Human.Head.cs Human.Body.cs Human.Arm.cs

  4. Use assert for any assertion you have. Assert is not recoverable. (e.g, most function will have Debug.Assert(not null parameters) )

  1. The name of a bitflag enum must be suffixed by Flags

    [Flags]
    public enum EVisibilityFlags
    {
    None = 0,
    Character = 1 << 0,
    Terrain = 1 << 1,
    Building = 1 << 2,
    }
  2. Prefer overloading over default parameters

  1. When default parameters are used, restrict them to natural immutable constants such as null, false or 0.
  1. Shadowed variables are not allowed.

    public class SomeClass
    {
    public int Count { get; set; }
    public void Func(int count)
    {
    for (int count = 0; count != 10; ++count)
    {
    // Use count
    }
    }
    }
  2. Always use containers from System.Collections.Generic over ones from System.Collections. Using a pure array is fine as well.

  3. Use real type over implicit typing(i.e, var) unless the type is unimportant. Some acceptable var usage includes IEnumerable and when the new keyword is used for anonymous type.

  4. Use static class, not singleton pattern

  5. Use async Task instead of async void. The only place where async void is allowed is for the event handler.

  1. Validate any external data at the boundary and return before passing the data into our functions. This means that we assume all data is valid after this point.

  2. Therefore, do not throw any exception from inside non-boundary methods. Also, exceptions should be handled at the boundary only.

  1. As an exception to the previous rule, exception throwing is allowed when switch-default is used to catch missing enum handling logic. Still, do not catch this exception

    switch (accountType)
    {
    case AccountType.Personal:
    return something;
    case AccountType.Business:
    return somethingElse;
    default:
    throw new NotImplementedException($"unhandled switch case: {accountType}");
    }
  2. Prefer not to allow null parameters in your function, especially from a public one.

  1. If null parameter is used, and postfix the parameter name with OrNull

    public Anim GetAnim(string nameOrNull)
    {
    }
  2. Prefer not to return null from any function, especially from a public one. However, you sometimes need to do this to avoid throwing exceptions.

  3. If null is returned from any function. Postfix the function name with OrNull.

    public string GetNameOrNull();
  4. Try not to use an object initializer. Use explicit constructor with named parameters instead. Two exceptions. a. When the object is created at one place only. (e.g, one-time DTO) b. When the object is created inside a static method of the owning class. (e.g, factory pattern)

  5. Declare the variable for an out parameter on a seprate line. Do NOT declare it int the argument list.

  6. Do not use using declaration, introduced in C# 8.0. Use using statement instead.

  7. Always specify a data type after new keyword unless you are using annoymous type inside a function.

  8. Use private init-only setter(private init), introduced in C# 9.0, wherever possible.

  9. Use file scoped namespace declarations, introduced in C# 10.0.

  10. When strong-typing a generic type, use readonly record struct, introduced in C# 10.0.

II. Code Formatting

  1. Use Visual Studio's default for tabs. If another IDE is used, use 4 spaces instead of a real tab.

  2. Always place an opening curly brace ({) in a new line

  1. Add curly braces even if there's only one line in the scope

    if (bSomething)
    {
    return;
    }
  2. Declare only one variable per line

    BAD:

    int counter = 0, index = 0;

    GOOD:

    int counter = 0;
    int index = 0;

III. Framework Specific Guidelines

A. Auto Serialization/Deserialization (e.g. System.Text.Json)

  1. Auto-serializable data must be defined as class.

  2. Auto-serializable class must not contain any library-specific attribute in it.

  3. All data in auto-serializable class must be declared/defined via public auto properties. (1-to-1 mapping between properties and member variables)

  4. If you need a read-only property in auto-serializable class, make a public method instead.

  5. Auto-serializable class must have only one public constructor. This constructor must not take any parameter.

  6. Do not directly call a auto-serialization method. (e.g. JsonSerializer.Serialize<>()). Make a wrapper method instead to limit the parameter types.

B. XAML Controls

  1. Do not name (i.e, x:name) a control unless you absolutely need it
  1. Use pascal casing with prefixed x character for the name.

    xLabelName
  2. Prefix the name with full control type

    xLabelName
    xButtonAccept

C. ASP .NET Core

  1. When using DTO(Data Transfer Object)s for a request body for a RESTful API, make each value-type property as nullable so that model validation is automatic

    [Required]
    public Guid? ID { get; set; }
  2. Validate all the requests as the first thing in any controller method. Once validation passes, all inputs are assumed to be correct. So no [required] nullable properties will be null.

  3. Unlike above, [RouteParam] will not have ?

    public bool GetAsync([RouteParam]Guid userID)

D. Service/Repo Pattern

  1. For the DTO classes and enums that are only used internally (e.g, internal microservice or DTO between service and repo), prefix it with X. This means they are transient classes and enums

    public sealed class XNode
    {
    }

    public enum EXTransactionStatus
    {
    }

Unity Project Template

This governance includes Project Structure Best Practices and C# Coding Standards for Unity.

A. Unity — Project Structure Best Practices

Employing an organized project structure is beneficial to your project and your team.

Scope: Folder Structure Folder Naming File Naming

Benefits:

Consistency — The project structure has consistency in presentation regardless of team location, spoken language, or individual programmers. Maintenance — Consistent project structure will aid readability. Readability helps new and existing programmers revisit the code base for fixes and improvements. Communication — Developers more implicitly understand each other in written and verbal communication.

B. Coding Standards

Coding standards define a programming style.

Employing organized coding standards is beneficial to your project and your team.

Scope:

Naming Conventions File Naming and Organization Formatting and Indentation Comments and Documentation Classes, Functions and Interfaces Testing

Benefits:

Code Integration Team Member Integration Maintenance Uniform Problem Solving Minimizes Communication Minimizes Performance Pitfalls

└── Unity
├── Assets
│   ├── 3rdParty
│   │   ├── [VendorName]
│   │   │   ├── readme.txt
│   │   │   └── readme.txt.meta
│   │   └── [VendorName].meta
│   ├── 3rdParty.meta
│   ├── Art
│   │   ├── AnimationClips
│   │   ├── AnimationClips.meta
│   │   ├── Animators
│   │   ├── Animators.meta
│   │   ├── Audio
│   │   │   ├── AudioClips
│   │   │   ├── AudioClips.meta
│   │   │   ├── AudioMixers
│   │   │   └── AudioMixers.meta
│   │   ├── Audio.meta
│   │   ├── Fonts
│   │   ├── Fonts.meta
│   │   ├── Materials
│   │   ├── Materials.meta
│   │   ├── Models
│   │   ├── Models.meta
│   │   ├── Shaders
│   │   ├── Shaders.meta
│   │   ├── Sprites
│   │   ├── Sprites.meta
│   │   ├── Textures
│   │   └── Textures.meta
│   ├── Art.meta
│   ├── Documentation
│   ├── Documentation.meta
│   ├── PhysicMaterials
│   ├── PhysicMaterials.meta
│   ├── Prefabs
│   │   ├── RMC
│   │   │   ├── [MyProject]
│   │   │   └── [MyProject].meta
│   │   └── RMC.meta
│   ├── Prefabs.meta
│   ├── Presets
│   ├── Presets.meta
│   ├── Resources
│   ├── Resources.meta
│   ├── Scenes
│   ├── Scenes.meta
│   ├── ScriptableObjects
│   │   ├── RMC
│   │   │   ├── [MyProject]
│   │   │   └── [MyProject].meta
│   │   └── RMC.meta
│   ├── ScriptableObjects.meta
│   ├── Scripts
│   │   ├── Editor
│   │   │   ├── Editor.asmdef
│   │   │   ├── Editor.asmdef.meta
│   │   │   ├── RMC
│   │   │   │   ├── [MyProject]
│   │   │   │   ├── [MyProject].meta
│   │   │   │   ├── Templates
│   │   │   │   │   ├── TemplateEditorMenuItems.cs
│   │   │   │   │   └── TemplateEditorMenuItems.cs.meta
│   │   │   │   └── Templates.meta
│   │   │   └── RMC.meta
│   │   ├── Editor.meta
│   │   ├── Other.meta
│   │   ├── Runtime
│   │   │   ├── RMC
│   │   │   │   ├── [MyProject]
│   │   │   │   ├── [MyProject].meta
│   │   │   │   ├── Runtime.asmdef
│   │   │   │   ├── Runtime.asmdef.meta
│   │   │   │   ├── Templates
│   │   │   │   │   ├── ITemplateInterface.cs
│   │   │   │   │   ├── ITemplateInterface.cs.meta
│   │   │   │   │   ├── TemplateClass.cs
│   │   │   │   │   ├── TemplateClass.cs.meta
│   │   │   │   │   ├── TemplateComponent.cs
│   │   │   │   │   ├── TemplateComponent.cs.meta
│   │   │   │   │   ├── TemplateScriptableObject.cs
│   │   │   │   │   └── TemplateScriptableObject.cs.meta
│   │   │   │   └── Templates.meta
│   │   │   └── RMC.meta
│   │   ├── Runtime.meta
│   │   ├── Tests
│   │   │   ├── Editor
│   │   │   │   ├── Editor.Tests.asmdef
│   │   │   │   ├── Editor.Tests.asmdef.meta
│   │   │   │   ├── RMC
│   │   │   │   │   ├── [MyProject]
│   │   │   │   │   ├── [MyProject].meta
│   │   │   │   │   ├── Templates
│   │   │   │   │   │   ├── TemplateClassEditModeTest.cs
│   │   │   │   │   │   └── TemplateClassEditModeTest.cs.meta
│   │   │   │   │   └── Templates.meta
│   │   │   │   └── RMC.meta
│   │   │   ├── Editor.meta
│   │   │   ├── Runtime
│   │   │   │   ├── RMC
│   │   │   │   │   ├── [MyProject]
│   │   │   │   │   ├── [MyProject].meta
│   │   │   │   │   ├── Templates
│   │   │   │   │   │   ├── TemplateComponentPlayModeTest.cs
│   │   │   │   │   │   └── TemplateComponentPlayModeTest.cs.meta
│   │   │   │   │   └── Templates.meta
│   │   │   │   ├── RMC.meta
│   │   │   │   ├── Runtime.Tests.asmdef
│   │   │   │   └── Runtime.Tests.asmdef.meta
│   │   │   └── Runtime.meta
│   │   └── Tests.meta
│   └── Scripts.meta
├── Packages
│   ├── manifest.json
│   └── packages-lock.json
└── ProjectSettings
├── AudioManager.asset
├── ClusterInputManager.asset
├── DynamicsManager.asset
├── EditorBuildSettings.asset
├── EditorSettings.asset
├── GraphicsSettings.asset
├── InputManager.asset
├── NavMeshAreas.asset
├── PackageManagerSettings.asset
├── Physics2DSettings.asset
├── PresetManager.asset
├── ProjectSettings.asset
├── ProjectVersion.txt
├── QualitySettings.asset
├── SceneTemplateSettings.json
├── TagManager.asset
├── TimeManager.asset
├── UnityConnectSettings.asset
├── VersionControlSettings.asset
├── VFXManager.asset
└── XRSettings.asset

47 directories, 94 files

Unity3D Coding Standards

  • Use C#-Unity-Format to manage document formatting.
  • Macros are all uppercase.
  • Naming of structures, types, enums use:
    • Lowercase.
    • Underscore to separate words.
    • _t at the end of the name to indicate it is a type.
    • [area]_[noun]_t format.
    • Prepend k4a to public types; i.e. k4a[area]_[noun]_t.
  • Enum element names additionally use:
    • The same [area]_[noun] as the beginning of each element.
    • Underscore to separate words.
    • uppercase.
  • Naming of functions use:
    • Lowercase.
    • Underscores to separate words.
    • [area]_[verb]_[noun] format.
    • k4a_ at the start of public functions; i.e. k4a_[area]_[verb]_[noun].
  • Stop / Free / Destroy functions should not return an error type. If it is not possible to handle all error cases, and there is nothing the user can do to resolve the issue, then it is acceptable to crash. If there is action the user could take (not including an application restart or PC reboot), then returning an error specific to the recovery action is acceptable.
  • Don't use default parameters in public APIs (C++/C#)

An attempt at documenting a composition of coding standards acquired from multiple game developers across the community.

Table of Contents

  1. Code Formatting
  2. Code File Layout
  3. Naming Conventions
  4. Code Documentation
  5. References

Code Formatting

  • Use spaces instead of tabs. Do not mix spaces and tabs;
  • Each indentation level should be 4 spaces wide;
  • Each brace should have its own line;
// Avoid
if(playerWasHit) {
PlaySound(playerHitSound);
Damage(player, damageAmount);
}
// Prefer
if(playerWasHit)
{
PlaySound(playerHitSound);
Damage(player, damageAmount);
}
// Bad
public float Health { get { return health; } }
// Good
public float Health
{
get
{
return health;
}
}
  • It is acceptable to use the expression body definition operator => for property getters and setters for simple, single-statement properties;
public float Health
{
get => health;
set => health = value;
}
  • Every statement after a conditional should be inside braces, even if it is a single statement;
// Bad
if(enemyInRange)
Explode();
// Good
if(enemyInRange)
{
Explode();
}
// Avoid
Debug.Log("Player " + playerId + " took a hit from " + damageSource + " for " + damageAmount + " damage.");
// Avoid
Debug.LogFormat("Player {0} took a hit from {1} for {2} damage.", playerId, damageSource, damageAmount);
// Prefer
Debug.Log($"Player {playerId} took a hit from {damageSource} for {damageAmount} damage.");
  • Switch-case code should be implemented inside braces;
switch(colorId)
{
case PlayerBodyColors.White:
{
playerBody.SetTexture(whiteTexture);
}
break;
case PlayerBodyColors.Red:
{
playerBody.SetTexture(redTexture);
}
break;
default:
{
playerBody.SetTexture(defaultTexture);
}
break;
}
  • Encode the document in UTF-8 if possible;
  • End-Of-Line character should be CRLF;

Code File Layout

  • Library usings should be the first lines of a file, followed by typedef-like usings;
  • Put every class definition inside an appropriate namespace;
  • Prefer defining only one class per file;
  • File name should be the same as the class name.
// File: AiPathfinder.cs
using System.Collections.Generic;
using UnityEngine;

using WaypointMap = Dictionary<Vector3,Waypoint>;

namespace MyGame.AiNavigation
{
public class AiPathfinder
{
...
}
}
  • Usings should be defined in the following order:

    1. System or .NET libraries
    2. Unity libraries
    3. Third-Party plugins (asset store)
    4. Your own utility libraries
    5. Project namespaces
    6. Type name aliases
  • All namespace categories should be blocked together, without separating with spaces or comments.

  • An exception to this is typedef-like usings, which should be separated from library usings with an empty line.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using ExampleCompany;
using OtherCompany.BoostedInspector;
using MyUtilityLib;
using MyUtilityLib.DebugUtilities;
using MyOtherLib.Patterns;
using ThisProject;
using ThisProject.Audio;
using ThisProject.Combat;

using EntityPrefabMap = Dictionary<EntityType,GameObject>;
  • Define a class in the following order:
    1. Nested classes
    2. Constants
    3. Enums
    4. Properties
    5. Fields
    6. Constructors (if applicable)
    7. Unity Messages
    8. Public methods
    9. Private methods
public class MyClass : MonoBehaviour
{
private class MyNestedClass
{
...
}

private const int SOME_CONSTANT = 1;

public enum SomeEnum
{
FirstElement,
SecondElement
}

public int SomeProperty
{
get => someField;
}

private int someField;

private void Start()
{
...
}

public void SomePublicMethod()
{
...
}

private void SomePrivateMethod()
{
...
}
}
  • Prefer defining methods in the following order:
    1. Initialization methods
    2. Core functionality methods
    3. Helper or explanatory methods
// Initialization
private void Initialize()
{
...
}

// Core functionality
private void Move(Vector3 direction)
{
...
}

// Helper
private bool CheckIfPositionIsWalkable(Vector3 position)
{
...
}

Naming Conventions

  • Identifiers for classes, methods, namespaces, enums, properties, attributes and coroutines are PascalCase;
namespace OpenSoulsGame.Debug
public class RichTextFormatter
public string StringToBold(this string inputString)
public float DefaultSpacing
{
...
}
[ConsoleAttribute] private int letterSpacing;
  • Identifiers for fields, local variables, parameters are camelCase;
public  int playerId;
private InputHandler playerInput;
private float health;
var name = GetPlayerName(playerId);
  • Constants are written in UPPER_CASE;
public const int MAX_SCENE_OBJECTS = 256;
  • Acronyms should be treated as words and are written in PascalCase.
public class XmlFormatter
public class AiBehaviour
  • The conventions for casing are unaffected by the modifiers public, private, protected, internal, static or readonly;

  • Namespace identifiers should briefly describe the systems or sets of definitions contained in the namespace.

namespace Utilities.Debug
namespace TowerDefenseGame.Combat
namespace TowerDefenseGame.UI
  • Class identifiers should briefly describe its responsibilities or data. Prefer including the suffix "Base" in abstract classes where applicable.
// A class responsible for performing movement on the player character's transform
class PlayerMotor
// An abstract class for implementing behaviors for AI Agents
abstract class AiBehaviourBase
  • Interfaces should include the prefix "I". The interface name should briefly describe the purpose of its members or the components it interacts with.
interface IMotorTarget
interface IUiElement
  • Method identifiers should describe the effect caused by the method, or the return value if the method has no effect.
// A method that performs movement on the player character
public void Move(Vector3 movement)
{
...
}
// Tipically, the identifier for methods without a return type should be a verb
// A method that converts radians to degrees.
private float RadianToDegrees(float radians)
{
...
}
// The identifier helps to understand how the returned value should be interpreted
// A method to determine if a position in the world can be traversed by the player
private bool IsPositionWalkable(Vector3 position)
  • Coroutines are written with the prefix 'CO_', and the rest of its identifier should follow the same rules as methods.
IEnumerator CO_SpawnPlayer(int playerId)

Code Documentation

  • Write self-documenting code when possible. Avoid overly abbreviated variables which don't have semantic value for the reader.
// Bad:
p = p + v * dt;
// Good:
position = position + velocity * deltaTime;
  • You can also use explanatory variables to avoid writing complicated and unreadable lines:
// Bad:
pos = Vector3.Lerp(targetEnemy.position, player.GetComponent<AutoMovementController>().targetWaypoint.nextPosition, elapsedTime / max);
// Good:
var waypoint = player.GetComponent<AutoMovementController>().targetWaypoint;
var startPosition = targetEnemy.position;
var finalPosition = waypoint.nextPosition;
var lerpPoint = elapsedTime / maxMovementTime;
position = Vector3.Lerp(startPosition, finalPosition, lerpPoint);
  • Write useful comments. Avoid being redundant with what the code is telling the reader. Instead, disclose valuable information that might not be directly perceivable.
// Bad:
// increment player id
playerId++;
// Good:
// We know that a new player has joined, generate a new identifier.
playerId++;
  • Do not comment bad or unreadable code. Prefer rewriting it instead.
// Bad:
// Increase current position by the velocity scaled by deltaTime to perform movement.
p = p + v * dt;
// Good:
position = position + velocity * deltaTime;
  • Do not contradict the code!
// Bad:
// Health value should be between 0 and 100.
private int health;

...

this.health = 150;
// Good:
// Base health values should be between 0 and 100.
private int health;

...

// Apply the temporary health buff from consuming potion.
this.health = 150;

References