This guide includes a select set of best practices when writing C#, specifically when building .NET Web API apps. The coding standards and examples in this guide are not meant to imporve upon existing Microsoft standards, but to clarify and provide additional information to ensure clarity.
Wherever possible these best practices are enforced with the default Resharper static code analysis plus some modification defined in this guide.
- Layout
- Commenting
- Strings
- Implicit Types
- Arrays
- Exceptions
- New Operator
- Static Members
- LINQ Queries
- Code Style
- Add Parentheses to Avoid Non Obvious Precedence
- Remove Redundant 'this' Qualifier
- Put Access Modifier First
- Convert Nullable of T to 'T?'
- Convert Property to Auto Property
- Convert to Property with Expression Body
- Use Readonly Fields
- Use Consistent Naming
- Use Optional Parameters
- Invert 'if' Statement to Reduce Nesting
- Join Local Variable Declaration and Assignment
- Separate Attributes In Section
- Use the null-conditional operator
- Merge Sequential Checks in AND or OR Expressions
- Ensure Namespace Corresponds to File Location
- Make Parameter Type IEnumerable of T When the parameter implements IEnumerable
- Avoid Possible multiple enumeration of IEnumerable
- Avoid Redundant 'else' Keyword
- Remove Redundant Parentheses
- Replace Built in Type Reference with a CLR Type Name or a Keyword
- Use IsNullOrEmpty for strings
- Good layout uses formatting to emphasize the structure of your code to make it easier to read.
- Use the default Visual Studio settings (smart indenting, four-character indents, tabs saved as spaces).
- 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.
Why? Most people read top-to-bottom left-to-right. Keeping comments in line with the code is much easier to read and also hard to miss when scanning code. It's also more work when refactoring code to not accidentally move or delete comments along with code.
```
// Increment foo due to requirement LP-1234 http://ticket.mycompany.com/LP-1234 stating that
// 'foo' can never be larger than 'bar'
if (foo > bar)
{
bar += foo;
}
```
Why? Proper grammar is easier to read and keeps consistency. It also has the effect of making the developer look more professional and intelligent.
Why? Proper punctuation makes comments easier to read and keeps consistency. It also has the effect of making the developer look more professional and intelligent.
Why? Makes comments more consistent, also easier to read when comment is separated from the slashes that start the comment block.
```
// This is a comment.
```
Why? This is generally considered an antiquated practice, also adds a lot of noise just to document code.
```
// Avoid
// ****************
// * Comment here *
// ****************
```
Why? Although strings are immutable in C#, the overhead is small when there are only a few strings. The benefit of less code to achieve the result is greater than the performance overhead.
string displayName = nameList[n].LastName + ", " + nameList[n].FirstName;
Why? Strings are immutable, so whenever a string is combined within another string then a new string is created in memory. The prior two strings remain in memory until garbage collection occurs. If hundreds of concatonations occur in a loop, then hundreds of strings will be left orphaned and consuming memory until the next schedule memory garbage collection. StringBuilder was created to prevent many string objects from being orphaned when merging text many times over.
```
var phrase = "my phrase";
var manyPhrases = new StringBuilder();
for (var i = 0; i < 10000; i++)
{
manyPhrases.Append(phrase);
}
```
Why? 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. Not repeating types in a single line is much faster to read and equally as intuitive.
```
// When the type of a variable is clear from the context, use var
// in the declaration.
var myString = "This is clearly a string.";
var myNumber = 27;
var myInteger = Convert.ToInt32(Console.ReadLine());
```
Why? It's always desireable to be able to understand the code as much as possible from reading it alone. 'var' makes it hard to determine the actual type, when it's not explicit.
```
// When the type of a variable is not clear from the context, use an
// explicit type.
int result = ExampleClass.ResultSoFar();
```
Why? The variable name might not provide enough information, or perhaps misleading information.
```
// Naming the following variable inputInt is misleading.
// It is a string.
var inputInt = Console.ReadLine();
Console.WriteLine(inputInt);
```
Why? In general, use 'int' rather than unsigned types because in most cases unsigned numbers are not needed, it's not a common practice, and it is easier to interact with other libraries when you use int.
Why? It makes it easy to determine the type of the array.
```
// 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";
```
Why? It allows the code to gracefully handle the exception before execution continues.
```
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;
}
}
```
Why? 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.
```
// 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.");
}
```
Why? We can infer the type from the explicit instantiation, so we don't need to be verbose when declaring the variable.
```
var instance1 = new ExampleClass();
```
Why? It's less verbose and cleaner to read than separate instantiation and property assignment.
```
// Avoid
var myClass = new ExampleClass();
myClass.Name = "Desktop";
myClass.ID = 37414;
myClass.Location = "Redmond";
myClass.Age = 2.3;
// Preferred
var myClass = new ExampleClass
{
Name = "Desktop",
ID = 37414,
Location = "Redmond",
Age = 2.3
};
```
Why? 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.
Why? This makes it much easier to understand what the query is returning and what potential downstream queries may be using it for.
The following example uses seattleCustomers for customers who are located in Seattle.
```
var seattleCustomers = from cust in customers
where cust.City == "Seattle"
select cust.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 };
```
Why? This makes it much easier to determine what the query returned downstream.
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 cust in customers
join dist in distributors on cust.City equals dist.City
select new { CustomerName = cust.Name, DistributorID = dist.ID };
```
Why? Bundling multiple queries together without proper where clauses can create large datasets that perform very poorly.
Ensure that 'where' clauses are added before any later query clauses so that the initial query is reduced and the subsequent queries are acting on a filtered set of data.
```
var seattleCustomers2 = from cust in customers
where cust.City == "Seattle"
orderby cust.Name
select cust;
```
Why? It makes is much easier to determine the order of operations in the expression.
```
// Avoid.
if (a & b | c)
// Recommended.
if ((a & b) | c)
```
Why? It doesn't serve any practical purpose. It makes the code harder to read.
```
// Avoid.
this._service = service;
// Recommended.
_service = service;
```
Why? It makes it easy to see the accessibility of the item. Also, arranging them in a similar way throughout your code is a good practice, which improves code readability.
```
// Avoid.
static private int count;
// Recommended.
private static int count;
```
Why? 'T?' is built into the language as a shorthand for Nullable. It is easier to quickly see that the object is nullable.
```
// Avoid.
Nullable<int> count;
// Recommended.
int? count;
```
Why? Auto properties are simpler to read as well as write.
```
// Avoid.
private Color bgColor;
public Color BackgroundColor
{
get { return bgColor; }
set { bgColor = value; }
}
// Recommended.
public Color BackgroundColor { get; set; }
```
Why? Expression-bodied properties, introduced in C# 6 are both more concise and readable
```
// Avoid.
private string _name;
public int NameLength
{
get
{
return string.IsNullOrEmpty(_name) ? 0 : _name.Length;
}
}
// Recommended.
private string _name;
public int NameLength => string.IsNullOrEmpty(_name) ? 0 : _name.Length;
```
Why? To ensure that this class will not inadvertently assign fields anywhere within its methods.
```
// Avoid.
public class MyClass
{
private string _name;
public MyClass(string name)
{
_name = name
}
}
// Recommended.
public class MyClass
{
private readonly string _name;
public MyClass(string name)
{
_name = name
}
}
```
Why? This ensures consistency and readability throughout the application.
- The PascalCasing convention, used for all identifiers except parameter names, capitalizes the first character of each word (including acronyms over two letters in length), as shown in the following examples:
- PropertyDescriptor
- HtmlTag A special case is made for two-letter acronyms in which both letters are capitalized, as shown in the following identifier: IOStream
- The camelCasing convention, used only for parameter names, capitalizes the first character of each word except the first word, as shown in the following examples. As the example also shows, two-letter acronyms that begin a camel-cased identifier are both lowercase.
- propertyDescriptor
- ioStream
- htmlTag
Why? Allows you to write less code and is easier to read.
```
// Avoid.
void Foo(object value)
{
Foo(value, true);
}
void Foo(object value, bool flag)
{
//implementation
}
// Recommended.
void Foo(object value, bool flag = true)
{
//implementation
}
```
Why? Without inversion, If blocks can encompass the whole body of the method. Inversion makes the code more readable by getting rid of the nested scope.
```
// Avoid.
void PrintName(Person p)
{
if (p != null)
{
if (p.Name != null)
{
Console.WriteLine(p.Name);
}
}
}
// Recommended.
void PrintName(Person p)
{
if (p == null) return
if (p.Name == null) return;
Console.WriteLine(p.Name);
}
```
Why? Removes an unnecessary line and improves readability of your code.
```
// Avoid.
int Bar()
{
int myInt;
myInt = 3;
return myInt - 1;
}
// Recommended.
int Bar()
{
var myInt = 3;
return myInt - 1;
}
```
Why? Makes attributes easier to read. Easier to keep separate attributes from appearing coupled.
```
// Avoid.
[Attribute1, Attribute2]
public class MyClass { }
// Recommended.
[Attribute1]
[Attribute2]
public class MyClass { }
```
Why? C# 6 introduced this shorthand notation to test for null before member access. It is easier to read and less code to write.
```
// Avoid.
string GetAttr(XElement node, string attrName)
{
var attrNode = node.Attribute(attrName);
return attrNode == null ? null : attrNode.Value;
}
// Recommended.
string GetAttr(XElement node, string attrName)
{
var attrNode = node.Attribute(attrName);
return attrNode?.Value;
}
```
Why? Makes the code footprint smaller by utilizing shorthand notation introduced in C# 6.
```
// Avoid.
if (p == null || p.Arguments == null) { }
// Recommended.
if (p?.Arguments == null) { }
```
Why? This makes it miuch easier to see where a code file exists in the solution, which in turn makes importing classes easier.
```
// Assume we are in a class called MyClass in a project called MyProject.Services.
// Avoid.
namespace Random.Namespace
{
public class MyClass { }
}
// Recommended.
namespace MyProject.Services
{
public class MyClass { }
}
```
Why? Makes the method agnostic with respect to the type of collection you give it, making it more resusable.
```
// Avoid.
public void DoStuff(List<Person> personList) { }
// Recommended.
public void DoStuff(IEnumerable<Person> personList) { }
```
Why? Multiple enumeration could lead to a major performance penalty, especially when using LINQ to SQL.
```
// Avoid.
IEnumerable<string> names = GetNames();
foreach (var name in names)
{
Console.WriteLine("Found " + name);
}
var allNames = new StringBuilder();
foreach (var name in names)
{
allNames.Append(name + " ");
}
// Recommended.
IEnumerable<string> names = GetNames().ToList();
foreach (var name in names)
{
Console.WriteLine("Found " + name);
}
var allNames = new StringBuilder();
foreach (var name in names)
{
allNames.Append(name + " ");
}
```
Why? Removes unnecessary boilerplate from the method, making it easier to read.
```
// Avoid.
public int Sign(double d)
{
if (d > 0.0)
{
return 1;
}
else
{
return -1;
}
}
// Recommended.
public int Sign(double d)
{
if (d > 0.0)
{
return 1;
}
return -1;
}
```
Why? They do not help at all and just add up to dead code as well as adding complexity.
```
// Avoid.
Thing item = ((Thing))myThing;
// Recommended.
Thing item = (Thing)myThing;
```
Why? The C# keywords are easier to read and allow more consistent code, instead of having a mix of keywords and built in types.
```
// Avoid.
Boolean hasCount;
// Recommended.
bool hasCount;
```
Why? This avoids redundant null and length checks for strings, allowing for cleaner code.
```
// Avoid.
public void SetName(string name)
{
if (name != null && name.Length > 0)
{
myName = name;
}
}
// Recommended.
public void SetName(string name)
{
if (!string.IsNullOrEmpty(name))
{
myName = name;
}
}
```