C# Coding Style

This page details the coding style I’ve adopted for C# code in my applications. You should also read Microsoft’s – C# Coding Conventions and ensure you are comfortable with the concepts described therein.

Braces

Use the standard .NET block brace style. This means both starting and ending braces always reside on their own line. The only exception to this rule is simple properties, where the get and set statements can be reduced to a single line each.

namespace Bitsquid
{
public class Resource
{
public string AbsolutePath
{
get { return _absolute_path; }
set { _absolute_path = value; }
}

public IEnumerable<string> GetDependencyPaths()
{
var paths = new List<string>();

foreach (var dependency in Dependencies)
{
var path = dependency.AbsolutePath;
paths.Add(path);
}

return paths;
}
}
}

Omitting braces

You are allowed to omit braces in the following situation:
while (test)
{
// Simple test, single statement.
if (another_test)
DoSomething();

// Always add an empty line after such a statement.
DoAnotherThing();
}
But not in these situations:
// BAD - Multiple simple statements make scope unclear.while (test)
if (another_test)
DoSomething();

// BAD - All sections of a complex branch should have braces.if (test)
DoSomething();
else
{
DoSomethingElse();
}

Naming

While C++ does not really have a standard naming convention, C# ships with a huge library of functionality in the .NET Framework. In order to conform better to the .NET Framework naming style, we use slightly different naming rules in our C# code compared to our C++ code.

Name namespaces LikeThis

Namespaces should conform to the directory structure on disk. There are very few exceptions to this rule.

Name classes and structs LikeThis

This conforms to both our general naming guidelines and the convention used in the .NET Framework. Consider ending the name of derived classes with the name of the base class.
{
...
}
public class MeshResource : Resource
{
...
}

Name interfaces ILikeThis

Interfaces follow the same naming rules as classes, but are prefixed by the capital letter I.
Interfaces that enforce a set of abilities rather than represent a service are typically suffixed “able”, for example IEnumerable, IHashable, ISortable.
Ensure that when defining a class/interface pair where the class is a standard implementation of the interface, the names differ only by the letter I prefix on the interface name.

public interface IConnection
{
...
}

public interface IJsonSerializable
{
...
}

Name variables and method arguments like_this

Use lower case characters and underscores where you would put spaces in a normal sentence. While this convention does not confirm to the .NET standard library, it is used for both the C++ code and Lua scripts we ship with the engine. Choosing a different convention than the .NET Framework library in this case has very little impact, since the public interface remains unchanged.
A special case is boolean variables, who are typically prefixed with either is_, can_ or has_.

Name member variables _like_this

Being able to quickly distinguish member variables from local variables is good for readability.

public class Resource
{
private int _reference_count;
private bool _is_relative;
}

A single underscore is used as a prefix, because a prefix with letters in it (like m_) makes the code harder to read.
This _sentence can _be _easily read _even though _it _has _extra underscores. But m_throw in m_some letters m_and it m_is m_not so m_easy m_anymore, m_kay.
Also, using _ makes the member variables stand out more, since there could be other variables starting with m.

Name methods LikeThis()

Use capital letters for the start of every word, including the first word.
Functions that perform some calculation and return a value without modifying object state are typically prefixed with Get.
private IEnumerable< string > GetExistingFilePaths()
{
return _absolute_paths.Where(File.Exists);
}

Name properties LikeThis

Use capital letters for the start of every word, including the first word. Name properties using a noun, noun phrase, or an adjective.

Boolean properties

Boolean properties are typically prefixed with either Is, Can or Has.
private bool CanScrollHorizontally
{
get { return _can_scroll_horizontally; }
set { _can_scroll_horizontally = value; }
}

Name constants and enum entries LikeThis

This conforms to the .NET framework naming style.

Name enums and enum values LikeThis

Enums are types, just as classes and structs and should follow the same naming convention.
You should not include the enumeration type name in the enumeration value names. Use a singular name for an enumeration unless its values are bit fields. If its values are bit fields, it should have a plural name.

public enum GeometryType
{
Box,
Sphere,
Capsule,
Mesh,
}

[Flags]
public enum FileAttributes
{
Read = 1,
Write = 2,
Execute = 4,
}

Indentation and Spacing

Please indent code within namespaces
Contrary to C++, C# allows writing out the full namespace path on a single line, so we can indent the entire contents of namespaces.

namespace Stingray.View.ValueConverters
{
public class NumericValueConverter : IValueConverter
{
...
}
}

Organization

Use one file per class, interface, enum or other declaration. Even if your interface or enum is just a few lines.
The file name should mirror the class name. We use standard .NET naming style for files, so a file containing the class Resource should be named Resource.cs on disk. This conforms to the default file name given to new classes and interfaces by Visual Studio and ReSharper.
Classes that are located within a namespace are expected to be below a folder of the same name both on disk and in the solution. Folders should contain additional folders for nested namespaces.

Organize using statements

Keep your using statements nicely organized at the top of the file in order to minimize merge conflicts. Use the following rules to organize using statements:
  • Using statements should be placed at the top of the file.
  • Using statements should not be indented.
  • Using statements should be ordered alphabetically.
  • Using statements referring to the System namespace are ordered before all other using statements.
  • Using statements that define aliases are ordered after all other using statements.
  • Using statements that are not required by the code inside the file should be removed.
ReSharper can organize your using statements automatically according to this rule set. Simply right-click anywhere in the code and select Organize Usings > Remove and Sort from the context menu.

Put state at the top of the class

All state members should be put at the top of the class declaration, so all the mutable state can be surveyed at-a-glance. This includes automatic properties. Do not attempt to separate automatic properties from member variables – they both represent state, and it is common for an automatic property to be converted into a property with a backing field or the other way around.
public class Resource
{
// State
public string AbsolutePath { get; private set; }
private bool _is_relative;

public Resource(string path)
{
...
}
}

Initialize members in constructors

If you do not initialize a variable, it will be initialized to the value yielded by the default(T) expression, where T is the type of the member variable. This yields 0 for numeric variables, false for booleans, null for reference types and the result of calling the non-argument constructor for structs.
While C# allows you to initialize member variables on the same line as they are declared, it is good practice to initialize member variables in the constructor instead. This ensures all the initialization code is in one place, and allows members to be initialized from constructor arguments.
public class Resource
{
// State
public string AbsolutePath { get; private set; }
private bool _is_relative = true; // BAD! Do it in the constructor instead.

public Resource(string path)
{
AbsolutePath = path;
_is_relative = IsRelativePath(path);
}
}

Style Considerations

Code is read more often than it is written. We want to leverage modern C# features whenever we can, since they all contribute to making the code more readable. Note that ReSharper will notify you of most of these style issues, and can even perform these changes automatically.

Modifier keywords

We always specify accessibility keywords explicitly in our declarations:
// BAD: Accessibility is unclear.class MyClass
{
string _name;
int _age;
}

// BETTER: Explicit accessibility makes it clearer.internal class MyClass
{
private string _name;
private int _age;
}

Modifier keyword order

We use the following keyword order for type declarations:
private static sealed abstract class MyClass { … }
We use the following keyword order for method declarations:
private static unsafe extern virtual abstract string void MyMethod() { … }
We use the following keyword order for member declarations:
private static readonly const volatile string _my_field;

Use var

C# has supported local variable type inference since version 3.0. This allows you to omit unnecessary type declarations when a local variable is initialized on the same row as it is declared.

// BAD: Explicit type declarations make the code "noisy" and harder to read.int index = 5;
string greeting = "Hello";
double height = 1.0;
int[] numbers = new int[] { 1, 2, 3 };
Dictionary< Guid, ReadOnlyCollection< Tuple< int, string > > > orders = new Dictionary< Guid, ReadOnlyCollection< Tuple< int, string > > >();

// BETTER: Using the var keyword stops us from having to repeat ourselves and makes the statements line up nicely.var index = 5;
var greeting = "Hello";
var height = 1.0;
var numbers = new[] { 1, 2, 3 };
var orders = new Dictionary< Guid, ReadOnlyCollection< Tuple < int, string > > >();

Use object initializers

Object initializers were introduced in C# 3.0. They enable you to create and configure an object with a single statement.
// BAD: Create an object and poke at it.var james = new Employee();
james.Id = Guid.NewGuid();
james.Name = "James";
james.Manager = susan;

// BETTER: The Employee is created and configured with a single statement.var james = new Employee { Id = Guid.NewGuid(), Name = "James", Manager = susan };
You can also use collection initializers to populate a collection with a single statement.

// BAD: Create a collection and poke at it.
var names = new List< string >();
names.Add("John");
names.Add("Paul");
names.Add("George");
names.Add("Ringo");

// BETTER: The list is created and populated with a single statement.var names = new List< string > { "John", "Paul", "George", "Ringo" };
Collection initializers can be used to populate any collection that implements the IEnumerable interface, including Dictionary< TKey, TValue >. The syntax for initializing a dictionary is slightly different:
var number_words = new Dictionary< int, string > {
{ 0, "zero" },
{ 1, "one" },
{ 2, "two" },
...
{ 9, "nine" }
}
Posted in C#

Question or comment?