Tuesday, July 18, 2017

C# Style Guide

C# Style Guide

Remember that at the end of the day these are only recommendations.

Table of Contents

  1. Tooling
  2. General
  3. Layout
  4. Spacing
  5. Ordering
  6. Naming
  7. Testing Code
  8. Acknowledgments
  9. References

Tooling

StyleCop

We include a ruleset file for StyleCop where we try to keep the styles matching.
For this to work you need to add the NuGet package and manually add the ruleset file to each project.
<CodeAnalysisRuleSet>..\CSharpStyleguide.ruleset</CodeAnalysisRuleSet>
Another thing to consider is that auto-generated code (like EF7 migrations) should not be required to comply with these rules. In order to bypass the ruleset, you need to manually add at the top of the file:
// <auto-generated/>

EditorConfig

If you are used to .editorconfig files then you can grab the one we include here.

General

Use var instead of explicit types

.editorconfig
Exceptions are allowed. For more information read this fantastic post from Eric Lippert.
// Bad
HttpClient httpClient = new HttpClient();

// Good
var httpClient = new HttpClient();

// Allowed for using an interface instead of a class
IUserService userService = new UserService();

Don't use explicit this reference

SA1101 .editorconfig
It is redundant and does not add for readability. For more information read this SO post.
// Bad
this.ValidateParameters();

// Good
ValidateParameters();

Use language built-in alias instead of class names

SA1121 .editorconfig
// Bad
String.IsNullOrEmpty(name);

// Good
string.IsNullOrEmpty(name);

Use readonly fields when possible

Prefer string interpolation to string.Format

// Bad
var message = string.Format("My name is {0} {1}.", person.FirstName, person.LastName);

// Good
var message = $"My name is {person.FirstName} {person.LastName}.";

Layout

Use four spaces per indentation level

.editorconfig
You can enable the "View White Space" option and use CTRL+R/CTRL+W keyboard shortcuts.
// Bad
public void SomeMethod()
{
∙∙DoSomethingFirst();
∙∙DoSomethingLater();
}

// Good
public void SomeMethod()
{
∙∙∙∙DoSomethingFirst();
∙∙∙∙DoSomethingLater();
}

Don't use more than one empty line in a row

SA1507
// Bad
public void SomeMethod()
{
    DoSomethingFirst();
    DoSomethingLater();


    DoSomethingAtTheEnd();
}

// Good
public void SomeMethod()
{
    DoSomethingFirst();
    DoSomethingLater();

    DoSomethingAtTheEnd();
}

Remove unused or redundant using statements (or directives)

Use single line and lambda getters for simple methods and read-only properties

.editorconfig
// Good
public string Name { get; private set; }

// Good
public string UppercaseName => Name.toUpperCase(); 

Curly braces for multi line statements must not share line

SA1500
// Bad
public void SomeMethod() {
    // ...
}

// Good
public void SomeMethod()
{
    // ...
}

Opening or ending curly braces must not be followed/preceded by a blank line

SA1508 SA1509
// Bad
public void SomeMethod() 
{

    DoSomethingFirst();
    DoSomethingLater();

}

// Good
public void SomeMethod()
{
    DoSomethingFirst();
    DoSomethingLater();
}

Don't use braces for just one line

SA1503
// Bad
if (payment == null)
{
    return "A payment is required.";
}

// Good
if (payment == null)
    return "A payment is required.";

Spacing

Commas must be spaced correctly

SA1001
A comma should be followed by a single space and never be preceded by any whitespace.
// Bad
var result = Calculate(3 , 5);
var result = Calculate(3,5);

// Good
var result = Calculate(3, 5);

Symbols must be spaced correctly

SA1003
An operator symbol must be surrounded by a single space on either side.
// Bad
var result = 5+3;
var result = 5 +3;
var result = 5+ 3;

// Good
var result = 5 + 3;
Except for unary operators:
// Bad
var result = ! toggle;

// Good
var result = !toggle;

Opening parenthesis should not be followed by a space

SA1008
// Bad
var result = Calculate( 3, 5);

// Good
var result = Calculate(3, 5);

Closing parenthesis should not be preceded by a space

SA1009
// Bad
var result = Calculate(3, 5 );

// Good
var result = Calculate(3, 5);

Opening square brackets should not be preceded or followed by a space

SA1010
// Bad
var element = myArray [index];
var element = myArray[ index];

// Good
var element = myArray[index];

Closing square brackets should not be preceded or followed by a space

SA1011
// Bad
var element = myArray[index ];
var element = myArray[index] ;

// Good
var element = myArray[index];

Opening curly brackets should be preceded and followed by a space

SA1012
// Bad
var names = new string[] {"Marie", "John", "Paul" };

// Good
var names = new string[] { "Marie", "John", "Paul" };

Closing curly brackets should be preceded and followed by a space

SA1013
// Bad
var names = new string[] { "Marie", "John", "Paul"};

// Good
var names = new string[] { "Marie", "John", "Paul" };

Ordering

Within a class, struct, or interface, elements should be ordered

SA1201
  1. Header
  2. NameSpaces
  3. Constants
  4. Fields
  5. Constructors
  6. Delegates
  7. Events
  8. Properties
  9. Methods
  10. Finalizers (Destructors)

Elements should be ordered by access

SA1202
  1. public
  2. internal
  3. protected internal
  4. protected
  5. private

It is recommended to follow certain order when using declaration keywords

SA1206
  1. Access modifiers
  2. static
  3. All other keywords
// Bad
override public int Area()
{
    // ...
}

// Good
public override int Area()
{
    // ...
}
// Bad
static public void Drive() { }

// Good
public static void Drive() { }

It is recommended to order using directives alphabetically

SA1210
// Bad
using System;
using Newtonsoft.Json;
using System.Linq;
using System.Collections.Generic;

// Good
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;

Naming

Be descriptive with your naming, avoid abbreviations and single letter names

// Bad
var hc = new HttpClient();

// Good
var httpClient = new HttpClient();
// Bad
var e = "test@test.com";

// Good
var email = "test@test.com";

Use PascalCase for namespaces, classes, enums, structs, constants, delegates, events, methods and properties

SA1300 SA1303
// Bad
public class file_reader
{
    // ...
}

// Good
public class FileReader
{
    // ...
}
// Bad
public const int TIME_IN_SECONDS = 5;

// Good
public const int TimeInSeconds = 5;
// Bad
public string secondAddress { get; set; }

// Good
public string SecondAddress { get; set; }

Use camelCase for variables

// Bad
var FileReader = new FileReader();
var file_reader = new FileReader();
var _fileReader = new FileReader();

// Good
var fileReader = new FileReader();

Use _underscoreCase for private instance/static fields

SA1309
// Bad
private IUserService UserService;
private IUserService userService;
private IUserService user_service;

// Good
private IUserService _userService;

Interface names must begin with "I"

SA1302
// Bad
public interface LoggerInterface
{
    // ...
}

// Good
public interface ILogger
{
    // ...
}

Include Async suffix on async methods

// Bad
public async Task<int> GetLatestPosition()
{
    // ...
}

// Good
public async Task<int> GetLatestPositionAsync()
{
    // ...
}

Testing Code

We all know testing code is not built the same way that regular code is, because its purpose is different. This is why we have special guidelines for this type of code.

Test Class naming: {TargetTestClass}Tests

// Bad
namespace MyProject
{
    [TestClass]
    public class MyService
    { }
}

// Good
namespace MyProject
{
    [TestClass]
    public class MyServiceTests
    { }
}

Test Class Location: keep a correspondence with the code being tested

Use the same folder structure / namespace structure as the code that's being tested. This makes it easier to find test classes for a particular class and classes being tested by a particular test class.

Method naming: {TargetTestMethod}_{Expectation}[_When{Condition}]

The name needs to express:
  • What is being tested
  • What should happen.
  • Under which conditions.
What is being tested will usually be a particular method. (These are unit tests, after all.) For integration tests, there is more freedom in choosing this portion, but should still be a good name indicating the scenario being tested.
What should happen should be concise and to the point. Avoid should or must or expressions of rule/desire. The test itself is that validation, there's no need to specify it.
The conditions can be avoided if "normal" conditions are easy to assume and they're the one being tested. For example, a "must work under regular conditions" test says nothing. A good name would be "SaveToDatabase_PersistsChanges"
// Bad: Uses filler words
public void SaveToDatabase_ShouldPersistChanges() { }

// Bad: does not correctly indicate responsibility
public void SaveToDatabase_Works() { }

// Good
public void SaveToDatabase_PersistsChanges() { }

// Also good
public void SaveToDatabase_ThrowsArgumentException_WhenConnectionStringIsNotPresent() { }

Group tests by test target

We generally avoid using #region since it is a symptom of poorly designed code. However, tests classes are likely to grow if the same class handles several scenarios to be tested, or if several methods are exposed. Group them by the method being tested.
Include overloaded versions of methods in the same region.
#region SaveToDatabase
public void SaveToDatabase_PersistsChanges() { }

public void SaveToDatabase_ThrowsArgumentException_WhenConnectionStringIsNotPresent() { }
#endregion

#region RetrieveFromDatabase
public void RetrieveFromDatabase_RetrievesClientById() { }

public void RetrieveFromDatabase_RetrievesClientsByName() { }

public void RetrieveFromDatabase_ThrowsArgumentOutOfRangeException_WhenIdIsEmptyGuid() { }
#endregion

Acknowledgments

Thank you to all who contributed to any of the styles in this document or mentioned references.

References

Encrypt/Decrypt the App.Config

Program.cs using System; using System.Diagnostics; using System.IO; namespace EncryptAppConfig {     internal class Program     {         pr...