Skip to main content

C# Coding Standard

← Back to Home

In This Article:

πŸ€” Notice something is missing or not working? β†’ Please contact Zoel (zoelbastianbach@agate.id) on Ms. Teams!

https://embed.notionlytics.com/s/TTBVNGRreGhSV00zYm5sRE9IWkJObWRRV2pjPQ==

Overview


Coding standards are collections of coding rules, guidelines, and best practices. It gives a uniform appearance to the codes written by different engineers and developers, and improves readability and maintainability of the code while reducing the complexity. It also helps in code reuse and helps to detect error.

  • ❓ Why should I care about this article?

    When applied correctly, this coding standard should help you write cleaner code that can be easily understood by other developers, while maintaining consistency. This will have positive impact on the quality of the product you're working on. Nevertheless, coding standard promotes sound programming practices and increases efficiency of the programmers.

  • 🎯 The goal of this article

    This article will provide a consistent set of coding style and language conventions utilized in Agate

  • 🀷🏻 If I have further question about this article, who can I reach out to?

    Please contact: Fachri Akbar (fachri@agate.id)

Coding Style


Coding style causes the most inconsistency and controversy between developers. Each developer has a preference, and rarely are two the same. However, consistent layout, format, and organization are key to creating maintainable code. The following sections describe the preferred way to implement C# source code in order to create readable, clear, and consistent code that is easy to understand and maintain

Quick Summary

[Coding Style Summary](Coding Standard 4afb02b9ea4d4627b73727640a7fa91e/Coding Style Summary cf92f2d43571486a82421453fa26dca2.csv)

Formatting

  • ❌ NEVER declare more than 1 namespace per file.

  • ❌ AVOID putting multiple classes in a single file.

  • Try to place curly braces ({ and }) on a new line. But try to maintain code readability

  • Try to keep on using curly braces ({ and }) in optional conditional statements.

  • βœ… ALWAYS use a Tab & Indention size of 4.

  • βœ… ALWAYS declare each variable independently (not in the same statement)

    // **❌** DO NOT do this:
    public int HitCount, DeathCount;
    
    // **βœ…** DO this instead:
    public int HitCount;
    public int DeathCount;
    
  • Place namespace β€œusing” statements together at the top of file. Group .NET namespaces above custom namespaces.

  • Group internal class implementation by type in the following order:

    1. Member variables.
    2. Constructors & Finalizers.
    3. Nested Enums, Structs, and Classes.
    4. Properties
    5. Methods
  • Sequence declarations within type groups based upon access modifier and visibility:

    • Public
    • Protected
    • Internal
    • Private
  • Segregate interface Implementation by using #region statements.

  • Recursively indent all code blocks contained within braces.

  • Only declare related attribute declarations on a single line, otherwise stack each attribute as a separate declaration.

    // **❌** DO NOT do this:
    [Attrbute1, Attrbute2, Attrbute3] 
    public class MyClass {…}
    
    // **βœ…** DO this instead:
    [Attrbute1, RelatedAttribute2] 
    [Attrbute3] 
    [Attrbute4] 
    public class MyClass {…}
    
  • Place the following attribute declarations on a separate line:

    • Assembly Scope
    • Type Scope
    • Method Scope
    • Member Scope
    • Parameter

Code Commenting

  • All comments should be written in the same language (English preferred), be grammatically correct, and contain appropriate punctuation.

  • βœ… ALWAYS use // or /// but ❌ NEVER / … /

  • ❌ DO NOT β€œflowerbox” comment blocks.

    // *************************************** 
    // Comment block 
    // ***************************************
    
  • βœ… USE inline-comments to explain assumptions and known issues, and also algorithm insights if the algorithm is too complex or it is being optimized (so it is difficult to understand).

    • ❌ DO NOT use inline-comments to explain obvious code. Well written code is self-documenting.
  • Only use comments for bad code to say β€œfix this code” – otherwise remove, or rewrite the code!

  • Include comments using Task-List keyword flags to allow comment-filtering.

    // TODO: Place Database Code Here 
    // UNDONE: Removed P\Invoke Call due to errors 
    // HACK: Temporary fix until able to refactor
    
  • Try to apply C# comment-blocks (///) to public, protected, and internal. But don’t rely just from comment to explain your code.

  • Only use C# comment-blocks for documenting the API.

  • βœ… ALWAYS include

    Include , , and comment sections where applicable.

    /// <summary>
    /// The loaded map.
    /// </summary>
    public MapData LoadedMap;
    
    /// <summary>
    /// Switchs the scene to specified scene’s name
    /// </summary>
    /// <returns><c>true</c>, if scene was switched, <c>false</c> otherwise.</returns>
    /// <param name="sceneName">Scene name</param>
    public bool SwitchScene(string sceneName) 	
    {…}
    
  • Try to include and where possible.

Language Usage


Quick Summary

[Language Usage Summary](Coding Standard 4afb02b9ea4d4627b73727640a7fa91e/Language Usage Summary 7703ba34a20d41269f809e9d1a538ee6.csv)

General

  • ❌ DO NOT omit access modifiers. Explicitly declare all identifiers with the appropriate access modifier instead of allowing the default.

    // **❌** DO NOT do this:
    void WriteEvent(string message) {…}
    
    // **βœ…** DO this instead:
    private void WriteEvent(string message) {…}
    
  • ❌ AVOID typing long code in one line (max 120 characters per line).

  • Try to wrap repeated code block that occurred on more than one part into a function

    //check all tile except obstacle’s tile to be manipulated
    for each (tile:Tile in tileList)
    {
        if (tile.id != TILE_TREE && tile.id != TILE_ROCK && tile.id != TILE_RIVER) 
        {…}
    }
    
    //in some other place
    if (tile.id != TILE_TREE && tile.id != TILE_ROCK && tile.id != TILE_RIVER) 
    {…}
    
    //check all tile except obstacle’s tile to be manipulated
    for each (tile:Tile in tileList)
    {
        if (!IsObstacleID (tile.id)
        {…}
    }
    ...
    //di suatu tempat lain
    if (!IsObstacleID (tile.id)
    {…}
    
    bool IsObstacleID(int id)
    {
        return (id != TILE_TREE && id != TILE_ROCK && id != TILE_RIVER);
    }
    

Variable and Types

  • Try to initialize variables where you declare them.

    // **❌** DO NOT do this:
    //OtherFunction () not called if SomeFunction () return false
    if(SomeFunction() && OtherFunction()) 
    {…}
    
    // **βœ…** DO this instead:
    bool someResult = SomeFunction();
    bool otherResult = OtherFunction();
    if(someResult && otherResult) 
    {…}
    
  • βœ… ALWAYS use the built-in C# data type aliases, not the .NET common type system (CTS).

    // **❌** DO NOT do this:
    //OtherFunction () not called if SomeFunction () return false
    public System.Int16 ShortLikeVar;
    public System.Int32 IntLikeVar;
    public System.Int64 LongLikeVar;
    public System.String SomeString;
    
    // **βœ…** DO this instead:
    public short SomeShortVar;
    public int SomeIntVar;
    public long SomeLongVar;
    public string SomeString;
    
  • Try to use int for any non-fractional numeric values that will fit the int datatype - even variables for nonnegative numbers. The use of int is common throughout C#, and it is easier to interact with other libraries when you use int. Also, uint, short and long are not shown on Unity Editor

  • Only use long for variables potentially containing values too large for an int.

  • If you need even larger value range, possible unlimited (ex 10) use BigInteger, a custom class. But you need to take note that the performance it not as light as primitive type (int and long)

    30

  • Try to use double for fractional numbers if precision is crucial in the calculations.

  • ❌ AVOID using inline numeric literals (magic numbers). Instead, use a Constant or Enum.

  • ❌ AVOID declaring string literals inline. Instead use Resources, Constants, Configuration Files, Registry or other data sources.

  • Declare readonly or static readonly variables instead of constants for complex types.

  • Only declare constants for simple types.

  • For server code or should-always-run-even-error app, avoid direct casts. Instead, use the β€œas” operator and check for null. Direct cast could throw InvalidCastException if type is not valid.

    // **❌** DO NOT do this:
    //OtherFunction () not called if SomeFunction () return false
    object dataObject = LoadData();
    DataSet ds = (DataSet) dataObject; //Posible to throw exception if type not valid
    
    // **βœ…** DO this instead:
    object dataObject = LoadData();
    DataSet ds = dataObject as DataSet; //Return null if type not valid
    if(ds != null) {…}
    
  • βœ… ALWAYS explicitly initialize arrays of reference types using a for

  • ❌ AVOID boxing and unboxing value types.

    int count = 1;
    object refToCount = count; // Implicitly boxed.
    int newCount = (int)refToCount; // Explicitly unboxed.
    
  • Try to use the β€œ@” prefix for string literals instead of escaped strings.

    // **❌** DO NOT do this:
    //OtherFunction () not called if SomeFunction () return false
    string path = β€œC:\\Document\\Agate”
    
    // **βœ…** DO this instead:
    string path = @”C:\Document\Agate”
    
  • Prefer String.Format() or StringBuilder over string concatenation. Only when this operation happen a lot, ex: on server side. String concatenation consume less memory.

  • Prefer use a StringBuilder object to append strings in loops, especially when you are working with large amounts of text

  • ❌ DO NOT compare strings to String.Empty or β€œβ€ to check for empty strings. Instead, compare by using String.Length == 0.

Flow Control

  • ❌ AVOID invoking methods within a conditional expression.

    // **❌** DO NOT do this:
    //OtherFunction () not called if SomeFunction () return false
    if(SomeFunction() && OtherFunction()) 
    {…}
    
    // **βœ…** DO this instead:
    bool someResult = SomeFunction();
    bool otherResult = OtherFunction();
    if(someResult && otherResult) 
    {…}
    
  • ❌ AVOID creating recursive methods. Use loops or nested loops instead. Consider that recursive can cause a stack overflow.

  • Use the ternary conditional operator only for trivial conditions. ❌ AVOID complex or compound ternary operations.

    int result = isValid ? 9 : 4;
    
  • ❌ AVOID evaluating Boolean conditions against true or false.

    // **❌** DO NOT do this:
    if (isValid == true) 
    {…}
    
    // **βœ…** DO this instead:
    if (isValid) 
    {…}
    
  • ❌ AVOID assignment within conditional statements.

    if((i=2)==2) {…}
    
  • ❌ AVOID compound conditional expressions – use Boolean variables to split parts into multiple manageable expressions.

    // **❌** DO NOT do this:
    if (((value > _highScore) && (value != _highScore)) && (value < _maxScore))
    {…}
    
    // **βœ…** DO this instead:
    isHighScore = (value >= _highScore);
    isTiedHigh = (value == _highScore);
    isValid = (value < _maxValue);
    if ((isHighScore && ! isTiedHigh) && isValid)
    {…}
    
  • Only use switch/case statements for simple operations with parallel conditional logic.

  • Prefer nested if/else over switch/case for short conditional sequences and complex conditions.

  • ❌ NEVER fallthrough inside switch/case and always define default statement.

    switch (condition)
    { 
        case 1: 
            doJob1(); //kode fall through ke case 2
        case 2:
            doJob2();
            break;
        case 4: //kode fall through ke case 5
        case 5:
            doJob5();
            break;
    } //tidak ada default case
    
    switch (condition)
    { 
        case 1: 
            doJob1(); //tulis ulang case 2, jangan fall through ke case 2
            doJob2();
            break;
        case 2:
            doJob2();
            break;
        case 4: //tulis ulang case 5, jangan fall through ke case 5
            doJob5();
            break;
        case 5:
            doJob5();
            break;
        default:
            doOther();
            break;
    }
    

Exception

  • βœ… ALWAYS handle exception (try, catch, finally) for server code or should-always-run-even-error app. But log error, even better if install alarm for error

  • ❌ AVOID using exception for client game code. Just let game crash if error happen.

  • ❌ DO NOT use try/catch blocks for flow-control.

  • Only catch exceptions that you can handle.

  • ❌ NEVER declare an empty catch

  • ❌ AVOID nesting a try/catch within a catch

  • βœ… ALWAYS catch the most derived exception via exception filters.

  • Order exception filters from most to least derived exception type.

  • ❌ AVOID re-throwing an exception. Allow it to bubble-up instead.

  • If re-throwing an exception, preserve the original call stack by omitting the exception argument from the throw statement.

    // **❌** DO NOT do this:
    catch(Exception ex)
    {
        Log(ex);
    		throw ex;
    }
    
    // **βœ…** DO this instead:
    catch(Exception ex)
    {
        Log(ex);
        throw;
    }
    
  • Only use the finally block to release resources from a try

  • βœ… ALWAYS use validation to avoid exceptions.

    // **❌** DO NOT just do this:
    try
    {
        conn.Close();
    }
    catch(Exception ex)
    {
        // handle exception if already closed!
    }
    
    // **βœ…** DO this first instead:
    if(conn.State != ConnectionState.Closed)
    {
        conn.Close();
    }
    
  • βœ… ALWAYS set the InnerException property on thrown exceptions so the exception chain & call stack are maintained.

  • ❌ AVOID defining custom exception classes. Use existing exception classes instead.

  • When a custom exception is required;

    1. βœ… ALWAYS derive from Exception not ApplicationException.

    2. βœ… ALWAYS suffix exception class names with the word β€œException”.

    3. βœ… ALWAYS add the SerializableAttribute to exception classes.

    4. βœ… ALWAYS implement the standard β€œException Constructor Pattern”:

      public MyCustomException ();
      public MyCustomException (string message);
      public MyCustomException (string message, Exception innerException);
      
    5. βœ… ALWAYS implement the deserialization constructor:

      protected MyCustomException(SerializationInfo info, StreamingContext contxt);
      
  • βœ… ALWAYS set the appropriate HResult value on custom exception classes.

    (Note: the ApplicationException HResult = -2146232832)

  • When defining custom exception classes that contain additional properties:

    1. βœ… ALWAYS override the Message property, ToString() method and the implicit operator string to include custom property values.

    2. βœ… ALWAYS modify the deserialization constructor to retrieve custom property values.

    3. βœ… ALWAYS override the GetObjectData(…) method to add custom properties to the serialization collection.

      public override void GetObjectData(SerializationInfo info, StreamingContext context)
      {
          base.GetObjectData (info, context);
          info.AddValue("MyValue", _myValue);
      }
      

Events, Delegates, & Threading

  • βœ… ALWAYS check Event & Delegate instances for null before invoking.

  • βœ… DO use the default EventHandler and EventArgs for most simple events.

  • βœ… ALWAYS derive a custom EventArgs class to provide additional data.

  • βœ… DO use the existing CancelEventArgs class to allow the event subscriber to control events.

  • βœ… ALWAYS use the β€œlock” keyword instead of the Monitor type

  • Only lock on a private or private static object.

  • ❌ AVOID locking on a Type.

    lock(typeof(MyClass));
    
  • ❌ AVOID locking on the current object instance.

    lock(this);
    

Objects Composition

  • βœ… ALWAYS declare types explicitly within a namespace. Do not use the default β€œ{global}” namespace.

  • ❌ AVOID overuse of the public access modifier. Typically fewer than 10% of your types and members will be part of a public API, unless you are writing a class library.

  • Consider using internal or private access modifiers for types and members unless you intend to support them as part of a public API.

  • ❌ NEVER use the protected access modifier within sealed classes unless overriding a protected member of an inherited type.

  • ❌ AVOID declaring methods with more than 7 parameters. Consider refactoring this code.

  • Try to replace large parameter-sets (> than 7 parameters) with one or more class or struct parameters – especially when used in multiple method signatures.

  • ❌ DO NOT use the β€œnew” keyword on method and property declarations to hide members of a derived type.

  • Only use the β€œbase” keyword when invoking a base class constructor or base implementation within an override.

  • Consider using method overloading instead of the params attribute (but be careful not to break CLS Compliance of your API’s).

    // **❌** DO NOT do this:
    public Vector3 CreateVector3(params int[] values) {…}
    
    // **βœ…** DO this instead:
    public Vector3 CreateVector3() {…}
    public Vector3 CreateVector3(int x) {…}
    public Vector3 CreateVector3(int x, int y) {…}
    public Vector3 CreateVector3(int x, int y, int z) {…}
    
  • Consider overriding Equals() on a struct.

  • βœ… ALWAYS override the Equality Operator (==) when overriding the Equals()

  • βœ… ALWAYS override the String Implicit Operator when overriding the ToString()

  • βœ… ALWAYS call Close() or Dispose() on classes that offer it.

  • Wrap instantiation of IDisposable objects with a β€œusing” statement to ensure that Dispose() is automatically called. But, check for platform specific implementation note

    using(SqlConnection cn = new SqlConnection(_connectionString))
    {…}
    
  • βœ… ALWAYS implement the IDisposable interface & pattern on classes referencing external resources.

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Free other state (managed objects).
        }
        // Free your own state (unmanaged objects).
        // Set large fields to null.
    }
    
    // C# finalizer. (optional)
    ~Base()
    {
        // Simply call Dispose(false).
        Dispose (false);
    }
    
  • ❌ AVOID implementing a Finalizer. Never define a Finalize() method as a finalizer. Instead use the C# destructor syntax.

    // **❌** DO NOT do this:
    void Finalize(){…}
    
    // **βœ…** DO this instead:
    ~MyClass {…}
    

See Also


Naming Standard

Namespace Standard

Foldering Standard

Git Usage Standard

Back to top ⬆️