Standardized Coding Design and Style Guide for Open Source Project Development in C#

Table of Contents

Introduction

The purpose of this document is to provide software developers a standard procedure and guide for developing, formatting, and implementing software solutions written in C#. These procedures are based on commonly accepted industry best practices and will assist in the readability and maintainability of C# code developed by Talegen and others. These rules were derived from multiple sources and meet or exceed previously defined development standards. These rules should also be compatible with the Microsoft StyleCop source code analysis plugin for Visual Studio found on Nuget.org. The StyleCop plugin will be the primary tool used to ensure source code development standards are being adhered to by projects managed by Talegen.

General Naming Conventions

Elements within the application code must adhere to the following naming conventions to ensure consistency and continuity of style and design.

Element Case

There are two capitalization systems that are standard in software development. The choice of style depends on the element you are naming as well as the commonly accepted standard for the given programming language. At Talegen, we will be adhering to one common standard between all programming languages to avoid conflicts and maintain consistency.

  • The Pascal Casing convention is to capitalize the first character of each word in the variable name. For example, use “OpenButton” instead of “Openbutton” or “openbutton”.
  • The Camel Casing convention is to capitalize the first character of each word except for the first word. For example, use “backColor” instead of “BackColor” or “backcolor”.

With only a few exceptions, all variables throughout all code should adhere to these element casing standards.

Naming Mechanics and Word Choice

Use nouns for objects, verbs for methods, and so on. If a method starts with Get or Set consider refactoring the method as a property with exposed get and set accessors. Use terms consistently throughout the application. For example, use “userName” when referring to the user account name, and “userKey” when referring to the unique user account record key. Using “userId” can be confusing for some developers differentiating between the two.

Naming Elements

  • Every element must begin with an alphabetic character.
  • Every name must contain only alphabetic characters.
  • Beginning elements with upper-case letters; the following types of elements should use an upper-case letter as the first letter of the element name: namespaces, classes, enums, structs, delegates, events, methods, and public properties. In addition, any field which is public, protected, internal, or marked with the const attribute should begin with an upper-case letter using the Pascal Case naming standard. Non-private, read-only fields must also be named using an upper-case letter.
  • Beginning private field names with lower-case letters; Private fields and local variables must start with a lowercase letter and follow a Camel Case naming standard.
  • Do not use underscore in field names. Fields and variables should be named using descriptive, readable wording which describes the function of the field or variable. Typically, these names will be written using Camel case, and should not use underscores. For example, use “customerPostCode” instead of “customer_post_code”.
  • Hungarian notation should not be used. The use of Hungarian notation has become widespread in C++ code, but the trend in C# and other languages is to use longer, more descriptive variable names, which are not based on the type of the variable but which instead describe what the variable is used for. Additionally, modern code editors such as Visual Studio make it easy to identify type information for a variable or field, typically by hovering the mouse pointer over the variable name, reducing the need for Hungarian notation.
  • Avoid using overly abbreviated names. Unless an abbreviation is a commonly accepted way to describe something, a developer should avoid abbreviating variables. For example, a variable “usrNam” can be confused with several different possible functional names and should be expanded to “userName” to ensure the purpose of the variable is clearly understood. Some exceptions exist such as variables for iteration and lambda expressions where a single letter can represent an entity or numeric value within the loop or query. For example, “for (int i =0;i>5; i++) {}” would be an acceptable abbreviated variable name.
  • Variable names must not be prefixed. The use of underscores, m_, etc., to mark local class fields is no longer acceptable and should be replaced by using the “this.” prefix. The advantage of using “this.” is that it applies equally to all element types including methods, properties, etc., and not just fields, making all calls to class members instantly recognizable, regardless of which editor is being used to view the code. Another advantage is that it creates a quick, recognizable differentiation between instance members and static members, which will not be prefixed.

Naming Namespaces/Assemblies

  • All namespaces shall use Pascal case with no underscores. Use [Project].[Folder] as a standard naming convention. Any acronyms of three or more letters should be Pascal case with no all-caps. For example, “Xml” instead of “XML”.
  • Assemblies should be named using the base namespace. For example, “Talegen.Service.Web” default namespace should also build to “Talegen.Service.Web.dll”.

Classes and Structures

  • All classes should use Pascal case naming with no underscores., leading “C” or “cls” prefixes.
  • Interface names must begin with the capital letter “I”. Interface names should always begin with I. For example, ICustomer.
  • Classes should not have the same name as the namespace in which they reside. Any acronyms of three or more letters should be Pascal case with no all-caps. For example, “XmlParser” instead of “XMLParser”.
  • Avoid abbreviations and always use singular nouns. A class which will represent a singular entity should have a singular name. For example, a class named “UserModel” would define a model for one or more users. “UsersModel” would be considered incorrect.
  • End collection class names with “Collection”.
  • End list class names with “List”.
  • End delegate class names with “Delegate”.
  • End exception class names with “Exception”.
  • End attribute class names with “Attribute”.
  • End MVC HtmlHelper class names with “Helper”.
  • An exception to the class naming convention will be database entity elements. These entities are auto-generated and represent a mapping of the database schema. These entity classes are exempt from naming rules as they are to represent the database schema naming methodology.

Documenting Code

Code files within the application must adhere to a standard documentation format to ensure consistency and continuity of style and design.

XML Documentation

C# syntax provides a mechanism for inserting documentation for classes and elements directly into the code, through the use of XML documentation headers. Describing these elements is beyond the scope of this document. For an introduction to these headers and a description of the header syntax, see the following article: http://msdn.microsoft.com/en-us/magazine/cc302121.aspx. The code file should always include a comment with the following types of elements should have documentation headers: classes, constructors, delegates, enums, events, finalizers, indexers, interfaces, methods, properties, and structs. Each of these elements should be completely documented including summaries for elements, fields, return values and parameter descriptions. Generic type parameters must also be documented to ensure completeness.

Source Code Headers

  • With the exception of auto-generated files, a manually created code file must contain a header that begins on the first line of the file, and must be formatted as a block of comments containing XML markup.
  • At a minimum the header shall contain the file name, company name, and copyright. 
//------------------------------------------------------------- 
// <copyright file="ClassFileName.cs" company="Talegen, LLC"> 
// Copyright (c) Talegen, LLC. All rights reserved. 
// </copyright> 
//-------------------------------------------------------------
  • For open source, Apache 2.0 projects, the standard header comment shall be applied to the source code file.
/*
 *(c) Copyright Talegen, LLC.
 *
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 *
 */ 

General Commenting Rules

  • Use double forward-slash comments to begin a comment line. For example, use “// comment” instead of “/* comment */” for single line comments.
  • Do not end a line of code with a comment.
  • Property summaries must match accessors. The property’s summary text must begin with wording describing the types of accessors exposed within the property. If the property contains only a get accessor, the summary must begin with the word “Gets”. If the property contains only a set accessor, the summary text must begin with the word “Sets”. If the property exposes both a get and set accessor, the summary text must begin with “Gets or sets”. In some instances, the set accessor will be restricted and in these cases the summary text must begin only with “Gets”.
  • Prior to deployment, ensure all temporary or extraneous comments are removed to avoid confusion during future maintenance work. If you must leave comments in the code, mark all future work areas with “// TODO:” to allow Visual Studio to mark these lines as tasks.
// TODO: throw error because user name already exists 
  • Use comments on code that consists of loops and logic branches. These are key areas that will assist source code readers.

Programming Style

A general adherence to the following programming style should be maintained to provide for consistency and legibility throughout the website application.

Layout Rules

These rules are defined to ensure a consistent layout within code to help the reader’s eye follow program structure and logic easier.

Curly Brackets

  • The opening and closing of curly brackets within a statement, element, or expression must be placed on their own line. The exception being in JavaScript an opening curly brace must start on the same line with matching logic or command statement as to avoid compile errors with the auto-semi-colon feature of the JavaScript language.
  • Curly brackets within a logic statement should not be omitted. For example, each “if ()” statement should be followed with an opening and closing curly brace, each on their own line. The only exception being in JavaScript where a beginning curly brace may begin on the same line as its matching logic or command statement.
  • To improve the readability of the code, opening curly brackets must not be followed by a blank line. Likewise, closing curly brackets must not be preceded by a blank line.
  • With certain exceptions, closing curly brackets within a method must be followed by a blank line.

Class Properties

  • All property accessors must be either consistently written on a single line or on multiple lines, but not both.
public string SomeString
{
  get
  {
     return this.someString;
  }
 
  set
  {
     this.someString = value;
  }
}
 
public override bool SomeBoolean { get; set; }
 
// starting with C# 6.0
public string AnotherValue => return this.someList.FirstOrDefault();

Maintainability Rules

These rules are defined to ensure that code is easily maintainable and help eliminate possible logic errors caused by coding style.

Parenthesis

  • It is possible in C# to insert parenthesis around virtually any type of expression, statement, or clause, and in many situations, use of parenthesis can greatly improve the readability of the code. However, excessive use of parenthesis can have the opposite effect, making it more difficult to read and maintain the code. Parenthesis should not be used in situations where they provide no practical value. Typically, this happens anytime the parenthesis surround an expression which does not strictly require the use of parenthesis, and the parenthesis expression is located at the root of a statement. For example, return (x.Value);

Code Files

  • In order to increase long-term maintainability of the code-base, each class should be placed in its own code file, and file names should reflect the name of the class within the file.
  • Each code file should contain only one namespace declaration.

Code Ordering Rules

These rules are defined to ensure a consistent ordering pattern within code to help the developer quickly find program structure and elements easier within a code file.

“Using” Directives

  • All “using” directives must be placed within the namespace. There are subtle differences between placing using directives inside and outside of a namespace. The benefits of inside the namespace are that it helps eliminate compiler confusion between conflicting types.
  • All unused namespaces shall have their using statements removed from a source file.
  • All using statements shall be ordered alphabetically.
  • All System.* using statements shall be listed above all others alphabetically.

Element Ordering

  • Elements at the file root level or within a namespace must be positioned in the following order: extern alias directives, using directives, namespaces, delegates, enums, interfaces, structs, and classes.
  • Within a class, struct, or interface, elements must be positioned in the following order: constants, fields, constructors, destructors, delegates, events, enums, interfaces, properties, indexers, methods, structs, and classes.
  • Elements must be ordered based on access level. Adjacent elements of the same type must be positioned in the following order by access level: public, internal, protected internal, protected, and private.
  • Static elements must appear in order before instance elements.

It has been argued that having related elements grouped together makes more sense, but in organizing by relation, one also breaks down the readability of the source code. For example, the following code blocks logically work the same but the ordered example is much easier to comprehend and allows for fast lookup of a private field. Because elements are ordered, #region keywords can also be used to organize sections of the class to enhance the readability of the code.

public class EasyToRead
{
    #region Private Fields
    private string someString;
    private int someInteger;
    private decimal someDecimal;
    #endregion

    #region Public Constructors
    public EasyToRead()
    {
        this.someString = string.Empty;
        this.someInteger = 0;
        this.someDecimal = 0;
    }
    #endregion

    #region Public Properties
    public string SomeString
    {
        get
        {
            return this.someString;
        }
 
        set
        {
            this.someString = value;
        }
    }
 
    public int SomeInteger
    {
        get
        {
            return this.someInteger;
        }
 
        set
        {
            this.someInteger = value;
        }
    }
 
    public decimal SomeDecimal
    {
        get
        {
            return this.someDecimal;
        }
 
        set
        {
            this.someDecimal = value;
        }
    }
    #endregion
 
    #region Public Methods
    public void DoStringSomething(string value)
    {
        this.PrivateStringSomething(value);
    }
 
    public void DoIntegerSomething(int value)
    {
        this.PrivateIntegerSomething(value);
    }
 
    public void DoDecimalValue(decimal value)
    {
        this.DoSomeDecimalThing(value);
    }
    #endregion
 
    #region Private Methods
    private void PrivateStringSomething(string value)
    {
        this.someString = "Do something privately with" + value;
    }
 
    private void PrivateIntegerSomething(int value)
    {
        this.someInteger = value + 1;
    }
 
    private void DoSomeDecimalThing(decimal value)
    {
        this.someDecimal = value;
    }
    #endregion
}

The following HardToRead class is an example of how an unordered class may appear. The color coding of access types helps denote how fragmented the “by relation” ordering method is and makes finding an element for a related type more difficult when used elsewhere outside of the related elements. Also, because there is no organization of elements, developers can enter private and public methods in any order they desire providing more problems in discovering class elements when looking for a specific element of a given protection. An example of this is found in the HardToRead class example where public methods are defined after private methods except for the last two methods. As a developer reading the code, if one knows private methods will always be defined near the end of a class, one will save a significant amount of time skipping portions of code searching for a given method.

public class HardToRead
 {
     public HardToRead()
     {
     }
     private int someInteger = 0; 
     public int SomeInteger { 
      get { return this.someInteger; }     
      set { this.someInteger = value;} 
     } 
     private void PrivateIntegerSomething(int value) {     this.someInteger = value + 1; } 
     public void DoIntegerSomething(int value) {     this.PrivateIntegerSomething(value); } 
     private string someString = ""; 
     public string SomeString {
       get { return this.someString; }
       set { this.someString = value; } 
     } 
     private void PrivateStringSomething(string value) { 
       this.someString = "Do something privately with " + value; 
     } 
     public void DoStringSomething(string value) {     
       this.PrivateStringSomething(value); 
     } 
     private decimal someDecimal = 0; 
     public decimal SomeDecimal {     
       get {
         return this.someDecimal;     
       }
       set {
         this.someDecimal = value;     
       } 
     } 
     public void DoDecimalValue(decimal value) { this.DoSomeDecimalThing(value); } 
     private void DoSomeDecimalThing(decimal value) {
       this.someDecimal = value; 
     }
 }

Readability Rules

These rules are defined to ensure a consistent readability within code to help the reader’s eye follow program structure and logic easier.

General Readability

  • Type aliases should be used at all times to ensure proper memory allocation. Rather than using one of the type names specified below, the built-in aliases for these types should always be used: bool, byte, char, decimal, double, short, int, long, object, sbyte, float, string, ushort, uint, ulong.
Type AliasTypeFully Qualified Type
boolBooleanSystem.Boolean
byteByteSystem.Byte
charCharSystem.Char
decimalDecimalSystem.Decimal
doubleDoubleSystem.Double
shortInt16System.Int16
intInt32System.Int32
longInt64System.Int64
objectObjectSystem.Object
sbyteSByteSystem.SByte
floatSingleSystem.Single
stringStringSystem.String
ushortUInt16System.UInt16
uintUInt32System.UInt32
ulongUInt64System.UInt64
  • Code should not contain more than one statement on a line.
  • Use the static String.Empty instead of an empty “”. This will eliminate the need for the compiler to embed an empty string into the compiled code.
  • Do not declare variables or implement classes using the entire namespace. Import the namespace with a “using” command and then implement the class. For example, use “private Uri someUrl;” rather than “private System.Uri someUrl;”.
  • Do not place #region annotations inside the body of an element. In Visual Studio, the region will appear collapsed by default, hiding the code within the region. It is generally a bad practice to hide code within the body of an element, as this can lead to bad decisions as the code is maintained over time.

Spacing Rules

  • The following keywords must be followed by a single space: catch, fixed, for, foreach, group, if, in, into, join, let, lock, orderby, return, select, stackalloc, switch, throw, using, where, while, yield.
  • The following keywords must not be followed by any space: checked, default, sizeof, typeof, unchecked.
  • The new keyword should always be followed by a space unless it is used to create a new array, in which case there should be no space between the new keyword and an opening array bracket.
  • A comma should always be followed by a single space, unless it is the last character on the line, and a comma should never be preceded by any whitespace, unless it is the first character on a line.
  • A semicolon should always be followed by a single space, unless it is the last character on the line, and a semicolon should never be preceded by any whitespace, unless it is the first character on the line.
  • The following types of operator symbols must be surrounded by a single space on either side: colons, arithmetic operators, assignment operators, conditional operators, logical operators, relational operators, shift operators, and lambda operators.
  • Unary operators must be preceded by a single space, but must never be followed by any space. For example, bool x = !value; An exception to this rule occurs whenever the symbol is preceded or followed by a parenthesis or bracket, in which case there should be no space between the symbol and the bracket. For example, if (!value)…
  • An opening parenthesis should not be preceded by any whitespace, unless it is the first character on the line, or it is preceded by certain C# keywords such as if, while, or for. In addition, an opening parenthesis is allowed to be preceded by whitespace when it follows an operator symbol within an expression.
  • A closing parenthesis should not be preceded by whitespace. In most cases, a closing parenthesis should be followed by a single space, unless the closing parenthesis comes at the end of a cast, or the closing parenthesis is followed by certain types of operator symbols, such as positive signs, negative signs, and colons. If the closing parenthesis is followed by whitespace, the non-whitespace character must not be an opening or closing parenthesis or square bracket, or a semicolon or comma.
  • An opening square bracket must never be preceded by whitespace, unless it is the first character on the line, and an opening square must never be followed by whitespace, unless it is the last character on the line.
  • A closing square bracket must be followed by whitespace, unless it is the last character on the line, it is followed by a closing bracket, or an opening parenthesis, it is followed by a comma or semicolon, or it is followed by certain types of operator symbols.
  • An opening curly bracket should always be preceded by a single space, unless it is the first character on the line, or unless it is preceded by an opening parenthesis, in which case there should be no space between the parenthesis and the curly bracket.
  • A closing curly bracket should always be followed by a single space, unless it is the last character on the line, or unless it is followed by a closing parenthesis, a comma, or a semicolon.
  • An opening generic bracket should never be preceded or followed by whitespace, unless the bracket is the first or last character on the line.
  • A closing generic bracket should never be proceeded by whitespace, unless the bracket is the first character on the line. A closing generic bracket should be followed by an open parenthesis, a closing parenthesis, a closing generic bracket, a nullable symbol, an end of line or a single white space (but not whitespace and an open parenthesis).
  • An opening attribute bracket should never be followed by whitespace, unless the bracket is the last character on the line.
  • A closing attribute bracket should never be preceded by whitespace, unless the bracket is the first character on the line.

Application Design

ASP.NET MVC Page Development

When at all possible, a single controller shall be used to manage all actions for a programmatic interface within the application. There shall be a clear and clean separation of concerns between control logic, data models, and view interfaces.

Web Project Structure

  • All application-wide controllers shall reside in the /Controllers/ folder.
  • Whenever possible, application models shall reside in their respective business layer namespaces.
  • All application-wide views shall reside in the /Views/ folder.
  • All views shall be programmed using the ASP.net Razor syntax.
  • Functionally specific folders shall be created under the MVC /Areas/ folder. Each of these area folders will contain their own model, view, and controller sub-folders. A functional folder can consist of one or more program specific capabilities via one or more area controllers.
  • When possible, JavaScript files should reside in a functionality-named sub-folder under the /Scripts/ folder.
  • All images and image content shall reside in a functionality-named sub-folder under the /Resources/Images/ folder.
  • JavaScript should be included in a pre-defined bundle on startup if it is to be used throughout the website. Otherwise, JavaScript should be injected on a per-view basis via a Razor @section named “Scripts”. This will all the developer render any content (including a custom script bundle) into a single location within the contents of the HTML page.

Business Layer Classes

Business layer classes shall conform to the following standards to ensure consistency.

General Rules

  • If business layer classes utilize a standard set of properties and methods, those properties and methods should be in a Base super-class that is then inherited by all business classes. This abstract class should provide properties for both database and error management contexts stored within an IFrameworkContext<T> interface.
  • Business layer classes usually contain all relevant methods used in working with a database entity or a process within the website application. For example, a UserBusinessLayerService class may contain almost all user-record related methods.
  • When possible, business layer classes shall use a service software development pattern.

Error Handling

Error handling shall be used consistently wherever an error may arise that needs entrapment and reporting. The developer should use a general catch for unhandled errors but also should be diligent in their use of try/catch to handle potential errors within their methods or methods being utilized from other libraries.

Try…Catch…Finally Statements

  • The try, catch, and finally statements shall be used within any method in which an exception can be thrown or is not desired to be thrown upward to the calling function. Developers should ensure any file handles, connections, or disposable objects are cleared in a finally statement before exiting a method.
  • All unhandled errors will be caught by a generic catch-all error handling system that will display a generic system error.
  • If re-throwing an Exception in a catch block, use “throw;” rather than “throw ex;” as the latter causes a copy of the Exception to be created and thrown from the catch block losing any prior stack-trace information that might have bubbled up to the current execution space.

“Using” Blocks

  • Whenever possible, use a “using” block when working with a disposable object that needs resources released back to the system through a call to Dispose(). The using block calls this method automatically, even if an Exception occurs within the using block.