C# Coding Conventions, Coding Standards & Best Practices
Overview
Introduction This coding guidelines document aims to provide a common standard and practices within the organization. This guidelines will help: · Promote code readability · Promote code maintainability · Faster enablement of new resources joining the project · Mitigate bugs The content of this document comes from multiple experiences and different sources. References are noted at the end of this material. In some cases, the statements were copied verbatim from the external sources to preserve the original intent of the author; thus credit is given to the original authors. Developers are encouraged to have a clear, concise, and self-documenting code with the idea in mind that other developers may read and update it.
Description
CIO China Rapid - Coding Standards and Best Practices C#
Terminology
All through-out of this document, the following set of terminologies will help you understand the coding guidelines.
Wording | Intent | Justification |
---|---|---|
Do... | This standard or practice should be followed in all cases. If you think that your specific application is exempt, it probably isn't. | These standards are present to mitigate bugs. |
Do Not... | This standard or practice should never be applied. | Same as above. |
Consider | This standard or practice should be followed in most cases. | These standards are typically stylistic and attempt to promote a consistent and clear style. |
Avoid | This standard or practice should not be followed, unless there's reasonable justification. | Same as above. |
You can… | This standard or practice can be followed if you want to; it's not necessarily good or bad. There are probably implications to following the practice (dependencies, or constraints) that should be considered before adopting it. | These standards are typically stylistic, but are not ubiquitously adopted. |
General
File and Structure
- Do name the source file with the name of the public type it contains
Filename should follow the name of the public type. For example, if the public class is Container it should follow that the filename is Container.cs.
- Do Not have more than one public type in a source file
Each source file should only have one public type. The exception is when the type differs only on the number of generic parameters or when one is nested in the other. Multiple internal types are allowed in the same source file.
Assembly Properties
The assembly should contain the appropriate property values describing its name, copyright, and so on.
Standard | Example |
---|---|
Set Copyright to Copyright © |
[assembly: AssemblyCopyright("Copyright © Accenture Inc. 2015")] |
Set AssemblyCompany to |
[assembly: AssemblyCompany("Accenture Incorporated")] |
Set both AssemblyTitle and AssemblyProduct to the current sample name | [assembly: AssemblyTitle("CSNamedPipeClient") |
[assembly: AssemblyProduct("CSNamedPipeClient")] |
Style and Formatting
- Consider limiting the length of line of codes
By limiting the length of line of codes improves code readability. The maximum line of codes should be set to 130 characters. Break the codes when the line length exceeds 130 characters.
Assemblies/Libraries
- Do reference assemblies/libraries in the project which are actually used
Make sure to only reference assemblies that are being used by the project. In some instances during development, some assemblies/libraries are used but later on new assembly version has become available. Make it a practice to remove unused assemblies/libraries.
Global Variables
- Do minimize the use of global variables
Global variables should be passed as parameters to functions. When a global variable needs to be modified, use it either as an output parameter or return a copy of the global variable.
- Do Not reference global variables inside a function or class
Global variables referenced inside a function or a class alters the state of the global variable without the caller knowing.
Method Declarations and Calls
- Do follow a specific format when declaring and calling methods
The method name, return type and parameter list can take in different forms. When method declaration does not fit in a single line, break the parameter list into several lines. Both types and parameter name should be on the same line and should be aligned under the preceding one. In a similar manner, method calls should follow the same format.
- Method Declarations
//Single line method declaration
int DoSomeProcessing(string param1, string param2, string param3);
// Multiple line method declaration
int DoSomeProcessing(string param1, string param2, string param3,
int param4, int param5, int param6);
- Method Calls
var result = DoSomeProcessing("param1", "param2", "param3");
var result = DoSomeProcessing("param1", "param2", "param3",
4, 5, 6);
- Do declare parameters in certain order
When declaring parameters, all in parameters should come first while out parameters comes last.var result = Method(string a, byte b, out int c, out string d)
Whitespace
Blank Lines
- Consider using blank line to separate logical group of codes
if ( ... )
{
// Do something
// ...
return false;
}
return true;
- Consider using one and only one single blank line between each method inside the class.
void method1 ( string parameter1 )
{
// Do something
} void method2 ( string parameter1)
{
// Do something
}
Spaces
Spaces improve readability by decreasing code density. Here are some guidelines for the use of space characters within code:
Space Guideline | Example |
---|---|
No space between method name and parenthesis | CreateFoo(); |
Single space after a comma | Method(myChar, 0, 1); |
No spaces inside bracket | sx = array[index]; |
Single space before flow control statements | while (x == y) |
Single space separates operators | if (x == y) |
Braces
- Do place curly braces on a separate line and not in the same line as if, for etc
if (nameProp == "Computer1")
{
//Do something
}
- Do place curly braces in the same level as the code outside the braces
protected int GetAge (string userName)
{
//Do something here
if (userName == "WhosThis")
{
//Do something here
}
}
Coding Conventions
Coding conventions serve the following purposes:
- They create a consistent look to the code, so that readers can focus on content, not layout.
- They enable readers to understand the code more quickly by making assumptions based on previous experience.
- They facilitate copying, changing, and maintaining the code.They demonstrate C# best practices.
- They demonstrate C# best practices.
Naming Conventions
In short examples that do not include using directives, use namespace qualifications. If you know that a namespace is imported by default in a project, you do not have to fully qualify the names from that namespace. Qualified names can be broken after a dot (.) if they are too long for a single line, as shown in the following example.
var currentPerformanceCounterCategory = new System.Diagnostics. PerformanceCounterCategory();
- You do not have to change the names of objects that were created by using the Visual Studio designer tools to make them fit other guidelines.
dofactory
Naming Conventions
do
use PascalCasing for class names and method names.
public class ClientActivity{
public void ClearStatistics()
{
//...
}
public void CalculateStatistics()
{
//...
}
}
do
use camelCasing for method arguments and local variables.
public class UserLog{
public void Add(LogEvent logEvent)
{
int itemCount = logEvent.Items.Count;
// ...
}
}
do not
use Hungarian notation or any other type identification in identifiers
// Correct
int counter;
string name;
// Avoid
int iCounter;
string strName;
do not
use Screaming Caps for constants or readonly variables
// Correct
public static const string ShippingType = "DropShip";
// Avoid
public static const string SHIPPINGTYPE = "DropShip";
avoid
using Abbreviations. Exceptions: abbreviations commonly used as names, such as Id, Xml, Ftp, Uri
// Correct
UserGroup userGroup;
Assignment employeeAssignment;
// Avoid
UserGroup usrGrp;
Assignment empAssignment;
// Exceptions
CustomerId customerId;
XmlDocument xmlDocument;
FtpHelper ftpHelper;
UriPart uriPart;
do
use PascalCasing for abbreviations 3 characters or more (2 chars are both uppercase)
HtmlHelper htmlHelper;
FtpTransfer ftpTransfer;
UIControl uiControl;
do not
use Underscores in identifiers. Exception: you can prefix private static variables with an underscore.
// Correct
public DateTime clientAppointment;
public TimeSpan timeLeft;
// Avoid
public DateTime client_Appointment;
public TimeSpan time_Left;
// Exception
private DateTime _registrationDate;
do
use predefined type names instead of system type names like Int16, Single, UInt64, etc
// Correct
string firstName;
int lastIndex;
bool isSaved;
// Avoid
String firstName;
Int32 lastIndex;
Boolean isSaved;
do
use implicit type var for local variable declarations. Exception: primitive types (int, string, double, etc) use predefined names.
var stream = File.Create(path);
var customers = new Dictionary();
// Exceptions
int index = 100;
string timeSheet;
bool isCompleted;
do
use noun or noun phrases to name a class.
public class Employee
{
}
public class BusinessLocation
{
}
public class DocumentCollection
{
}
do
prefix interfaces with the letter I. Interface names are noun (phrases) or adjectives.
public interface IShape
{
}
public interface IShapeCollection
{
}
public interface IGroupable
{
}
do
name source files according to their main classes. Exception: file names with partial classes reflect their source or purpose, e.g. designer, generated, etc.
// Located in Task.cs
public partial class Task{
//...
}
// Located in Task.generated.cs
public partial class Task
{
//...
}
do
organize namespaces with a clearly defined structure
// Examples
namespace Company.Product.Module.SubModule
namespace Product.Module.Component
namespace Product.Layer.Module.Group
do
vertically align curly brackets.
// Correct
class Program
{
static void Main(string[] args)
{
}
}
do
declare all member variables at the top of a class, with static variables at the very top.
// Correct
public class Account{
public static string BankName;
public static decimal Reserves;
public string Number {get; set;}
public DateTime DateOpened {get; set;}
public DateTime DateClosed {get; set;}
public decimal Balance {get; set;}
// Constructor
public Account()
{
// ...
}
}
do
use singular names for enums. Exception: bit field enums.
// Correct
public enum Color{
Red,
Green,
Blue,
Yellow,
Magenta,
Cyan
}
// Exception
[Flags]
public enum Dockings{
None = 0,
Top = 1,
Right = 2,
Bottom = 4,
Left = 8
}
do not
explicitly specify a type of an enum or values of enums (except bit fields)
// Don't
public enum Direction : long
{
North = 1,
East = 2,
South = 3,
West = 4
}
// Correct
public enum Direction
{
North,
East,
South,
West
}
do not
suffix enum names with Enum
// Don't
public enum CoinEnum
{
Penny,
Nickel,
Dime,
Quarter,
Dollar
}
// Correct
public enum Coin
{
Penny,
Nickel
,Dime,
Quarter,
Dollar
}
Submain Coding Standards
1. Casing
1. **PascalCase**: This is for class names, file names, namespace names, ALL method names, and public member names.
2. **camelCase**: This is used for member names that are not publicly accessible.
3. **UPPER_CASE**: You might also think of this as upper snake case. This is only used for constants.
4. **Pascal_snake_case**: This is used in unit testing, a descriptive method name such as Should_return_2_when_adding_1_and_1 is much easier to read.
Cardinal Casing Sins of C#
- Hungarian Notation
- Mixed Conventions
2. Formating
- Indentation
- Brackets
- Terminator
- Extra Lines
3. “var” Is Your Friend
- Using var is actually a good idea for all those short-lived variables.
4. Always Use Access Modifiers
- use the lowest necessary modifier. This is equivalent to the principle of least privilege.
5. Use Auto Properties
public string Name {get; set;}
6. Where to Declare Variables
- Declare static, const variables at the top of class block
- Declare temporary variables at the top of method block or where to use it.
7. Code File Organization
8. Use the Aliases, Jack!
9. “using” Is Your Friend!
C# Coding Standard
Naming conventions table
Name | Case |
---|---|
Variables | camelCase |
Class | PascalCase |
Constructor | PascalCase |
Properties | PascalCase |
Delegate | PascalCase |
Enum | PascalCase |
Arguments in methods | camelCase |
Method | PascalCase |
Constants | PascalCase |
Field | camelCase |
Optimize syntax
To declare an empty method which only returns a view in MVC, we should use the expression body.
//Avoid
public ActionResult Dashboard()
{
return View();
}
//Do
public ActionResult Dashboard() => View();
To check null or empty condition.
//Avoid
var varName = "faisal";
if (varName != null && varName != "")
{
//code
}
//Do
var varName = "faisal";
if (!string.IsNullOrEmpty(varName))
{
//code
}
Use null coalescing expression,
Test test = new Test();
//Avoid
var varName = test.Name != null ? test.Name : "";
//Do
var varName = test.Name ?? "";
Use object initializer,
//Avoid
Test test = new Test();
test.Id = 1;
test.Name = "faisal";
//Do
var test = new Test
{
Id = 1,
Name = "faisal"
};
Use ?. operator,
//Avoid
var empName = "";
Session["Name"] = "Faisal Pathan";
if (Session["Name"] != null)
{
empName = Session["Name"].ToString();
}
else
{
empName = "";
}
//Do
var empName = "";
Session["Name"] = "Faisal Pathan";
empName = Session["Name"]?.ToString() ?? "";
Avoid extra braces,
Note - only work with single line statements.
var count = 10;
//Avoid
if (count > 0)
{
//code
count++;
}
//Do
if (count > 0) count++;
//code
//Avoid
for (int i = 0; i < count; i++)
{
//code
count += 10;
}
//Do
for (int i = 0; i < count; i++) count += 10;
var testList = new List<Test>();
var names = new ArrayList();
//Avoid
foreach (var item in testList)
{
names.Add(item.Name);
}
//Do
foreach (var item in testList) names.Add(item.Name);
Use string interpolation.
Test test = new Test();
//Avoid
var details = string.Format("{0}, you are welcome, Your Id is {1}", test.Name , test.Id + "_emp");
//Do
var details = $"{test.Name}, you are welcome, Your Id is {test.Id}_emp";
New lightweight switchcase with c# 8,
int itemSwitch = 1;
//Good
switch (itemSwitch) {
case 1:
Console.WriteLine("Item 1");
break;
case 2:
Console.WriteLine("Item 2");
break;
case 3:
Console.WriteLine("Item 3");
break;
default:
Console.WriteLine("Default item case");
break;
}
//better
var message = itemSwitch switch
{
1 => Console.WriteLine("Item 1"),
2 => Console.WriteLine("Item 2"),
3 => Console.WriteLine("Item 3") ,
_ => "Default item case"
};
Console.WriteLine(message);
Layout Conventions
Good layout uses formatting to emphasize the structure of your code and to make the code easier to read. Microsoft examples and samples conform to the following conventions:
- Use the default Code Editor settings (smart indenting, four-character indents, tabs saved as spaces). For more information, see Options, Text Editor, C#, Formatting.
- Write only one statement per line.
- Write only one declaration per line.
- If continuation lines are not indented automatically, indent them one tab stop (four spaces).
- Add at least one blank line between method definitions and property definitions.
- Use parentheses to make clauses in an expression apparent, as shown in the following code.
if ((val1 > val2) && (val1 > val3))
{
// Take appropriate action.
}
Commenting Conventions
- Place the comment on a separate line, not at the end of a line of code.
- Begin comment text with an uppercase letter.
- End comment text with a period.
- Insert one space between the comment delimiter (//) and the comment text, as shown in the following example.
// The following declaration creates a query. It does not run
// the query.
- Do not create formatted blocks of asterisks around comments.
Language Guidelines
The following sections describe practices that the C# team follows to prepare code examples and samples.
String Data Type
- Use string interpolation to concatenate short strings, as shown in the following code.
string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}";
- To append strings in loops, especially when you are working with large amounts of text, use a StringBuilder object.
var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);
Implicitly Typed Local Variables
- Use implicit typing for local variables when the type of the variable is obvious from the right side of the assignment, or when the precise type is not important.
// When the type of a variable is clear from the context, use var
// in the declaration.
var var1 = "This is clearly a string.";
var var2 = 27;
var var3 = Convert.ToInt32(Console.ReadLine());
- Do not use var when the type is not apparent from the right side of the assignment.
// When the type of a variable is not clear from the context, use an
// explicit type.
int var4 = ExampleClass.ResultSoFar();
- Do not rely on the variable name to specify the type of the variable. It might not be correct.
// Naming the following variable inputInt is misleading.
// It is a string.
var inputInt = Console.ReadLine();
Console.WriteLine(inputInt);
- Avoid the use of var in place of dynamic.
- Use implicit typing to determine the type of the loop variable in for loops.
The following example uses implicit typing in a for statement.
var phrase = "lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
//Console.WriteLine("tra" + manyPhrases);
- Do not use implicit typing to determine the type of the loop variable in foreach loops.The following example uses explicit typing in a foreach statement.
foreach (var ch in laugh)
{
if (ch == 'h')
Console.Write("H");
else
Console.Write(ch);
}
Console.WriteLine();
Note
Be careful not to accidentally change a type of an element of the iterable collection. For example, it is easy to switch from System.Linq.IQueryable to System.Collections.IEnumerable in a foreach statement, which changes the execution of a query.
Unsigned Data Type
In general, use int rather than unsigned types. The use of int is common throughout C#, and it is easier to interact with other libraries when you use int.
Arrays
Use the concise syntax when you initialize arrays on the declaration line.
// Preferred syntax. Note that you cannot use var here instead of string[].
string[] vowels1 = { "a", "e", "i", "o", "u" };
// If you use explicit instantiation, you can use var.
var vowels2 = new string[] { "a", "e", "i", "o", "u" };
// If you specify an array size, you must initialize the elements one at a time.
var vowels3 = new string[5];
vowels3[0] = "a";
vowels3[1] = "e";
// And so on.
Delegates
Use the concise syntax to create instances of a delegate type.
// First, in class Program, define the delegate type and a method that
// has a matching signature.
// Define the type.
public delegate void Del(string message);
// Define a method that has a matching signature.
public static void DelMethod(string str)
{
Console.WriteLine("DelMethod argument: {0}", str);
}
// In the Main method, create an instance of Del.
// Preferred: Create an instance of Del by using condensed syntax.
Del exampleDel2 = DelMethod;
// The following declaration uses the full syntax.
Del exampleDel1 = new Del(DelMethod);
try-catch and using Statements in Exception Handling
- Use a try-catch statement for most exception handling.
static string GetValueFromArray(string[] array, int index)
{
try
{
return array[index];
}
catch (System.IndexOutOfRangeException ex)
{
Console.WriteLine("Index is out of range: {0}", index);
throw;
}
}
- Simplify your code by using the C# using statement. If you have a try-finally statement in which the only code in the finally block is a call to the Dispose method, use a using statement instead.
// This try-finally statement only calls Dispose in the finally block.
Font font1 = new Font("Arial", 10.0f);
try
{
byte charset = font1.GdiCharSet;
}
finally
{
if (font1 != null)
{
((IDisposable)font1).Dispose();
}
}
// You can do the same thing with a using statement.
using (Font font2 = new Font("Arial", 10.0f))
{
byte charset = font2.GdiCharSet;
}
&& and || Operators
To avoid exceptions and increase performance by skipping unnecessary comparisons, use && instead of & and || instead of | when you perform comparisons, as shown in the following example.
Console.Write("Enter a dividend: ");
var dividend = Convert.ToInt32(Console.ReadLine());
Console.Write("Enter a divisor: ");
var divisor = Convert.ToInt32(Console.ReadLine());
// If the divisor is 0, the second clause in the following condition
// causes a run-time error. The && operator short circuits when the
// first expression is false. That is, it does not evaluate the
// second expression. The & operator evaluates both, and causes
// a run-time error when divisor is 0.
if ((divisor != 0) && (dividend / divisor > 0))
{
Console.WriteLine("Quotient: {0}", dividend / divisor);
}
else
{
Console.WriteLine("Attempted division by 0 ends up here.");
}
New Operator
- Use the concise form of object instantiation, with implicit typing, as shown in the following declaration.
var instance1 = new ExampleClass();
The previous line is equivalent to the following declaration.
ExampleClass instance2 = new ExampleClass();
- Use object initializers to simplify object creation.
// Object initializer.
var instance3 = new ExampleClass { Name = "Desktop", ID = 37414,
Location = "Redmond", Age = 2.3 };
// Default constructor and assignment statements.
var instance4 = new ExampleClass();
instance4.Name = "Desktop";
instance4.ID = 37414;
instance4.Location = "Redmond";
instance4.Age = 2.3;
Event Handling
If you are defining an event handler that you do not need to remove later, use a lambda expression.
public Form2()
{
// You can use a lambda expression to define an event handler.
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}
// Using a lambda expression shortens the following traditional definition.
public Form1()
{
this.Click += new EventHandler(Form1_Click);
}
void Form1_Click(object sender, EventArgs e)
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}
Static Members
Call static members by using the class name: ClassName.StaticMember. This practice makes code more readable by making static access clear. Do not qualify a static member defined in a base class with the name of a derived class. While that code compiles, the code readability is misleading, and the code may break in the future if you add a static member with the same name to the derived class.
LINQ Queries
- Use meaningful names for query variables. The following example uses seattleCustomers for customers who are located in Seattle.
var seattleCustomers = from customer in customers
where customer.City == "Seattle"
select customer.Name;
- Use aliases to make sure that property names of anonymous types are correctly capitalized, using Pascal casing.
var localDistributors =
from customer in customers
join distributor in distributors on customer.City equals distributor.City
select new { Customer = customer, Distributor = distributor };
- Rename properties when the property names in the result would be ambiguous. For example, if your query returns a customer name and a distributor ID, instead of leaving them as Name and ID in the result, rename them to clarify that Name is the name of a customer, and ID is the ID of a distributor.
var localDistributors2 =
from customer in customers
join distributor in distributors on customer.City equals distributor.City
select new { CustomerName = customer.Name, DistributorID = distributor.ID };
- Use implicit typing in the declaration of query variables and range variables.
var seattleCustomers = from customer in customers
where customer.City == "Seattle"
select customer.Name;
- Align query clauses under the from clause, as shown in the previous examples.
- Use where clauses before other query clauses to ensure that later query clauses operate on the reduced, filtered set of data.
var seattleCustomers2 = from customer in customers
where customer.City == "Seattle"
orderby customer.Name
select customer;
- Use multiple from clauses instead of a join clause to access inner collections. For example, a collection of Student objects might each contain a collection of test scores. When the following query is executed, it returns each score that is over 90, along with the last name of the student who received the score.
// Use a compound from to access the inner sequence within each element.
var scoreQuery = from student in students
from score in student.Scores
where score > 90
select new { Last = student.LastName, score };