Standards in C# coding

Contents

Introduction

The purpose of creating this set of rules is an attempt to set standards for C# coding that would be convenient and practical at the same time. Not all of these rules have definitive ground. We simply accepted some of them as standards. After all, it does not matter what you choose, but rather how strictly you follow the selected rules.

The static code analyzer VisualStudio (also known as FxComp) and StyleCop can automatically apply many coding and formatting rules by analyzing compiled assemblies. You can configure them to analyze at compilation, or make analysis part of the daily assembly. This document contains additional rules and recommendations, while the website www.csharpcodingguidelines.com provides code analysis guidelines, required depending on what code base you are using.

Some think that coding standards limit their creativity. However, such an approach is justified and proven by years. Why? Because not every developer knows:

  • that it takes 10 times as much time to sort out code as to modify it;
  • about subtleties of using the main C# structures;
  • which .NET Framework conventions to adhere to, for instance, when using IDisposable or LINQ with its delayed execution;
  • how particular solutions of one task may affect productivity, security, multilanguage support, etc.;
  • how to gain insight into another developer's elegant, yet abstract code.

Basic principles

This article cannot cover all the cases you may face. If you have doubts, rely on the basic principles, applicable in any situation. Some of these principles are:

  • The Principle of Least Surprise. Always favor the most obvious solution, to make your code understandable and not to confuse other developers
  • Keep It Simple Stupid (KISS). Choose the simplest solution for your tasks
  • You Ain’t Gonna Need It (YAGNI). Work on the task at hand, do not create code only because you think you are going to need it in the future
  • Don't Repeat Yourself (DRY). Abstain from duplicating code within one component, repository or bounded context, and keep in mind the rule of three.
  • The four principles of object-oriented programming: encapsulation, abstraction, inheritance, and polymorphism.
  • Generally, you do not have to make the entire code base compliant with these guidelines. Nevertheless, it should comply as much as possible.

Class design guidelines

A class or an interface must have a single purpose (ELMA1000)

A class or an interface must have a single purpose in the system, in which it is used. Usually, a class describes a type, e.g. email or ISBN (international standard book number), or represents an abstraction of a business logic, or describes a data structure, or provides interaction with other classes. It must not combine these functions. This rule is known as the Single Responsibility Principle, one of the SOLID principles.

Tip: A class with the word "And" in its name clearly breaks this rule.

Tip: Use design patterns for interactions between classes. If you fail to apply any pattern to a class, the class may have too much responsibility.

Note: If you are creating a class, the describes a primitive type, then you can simplify its use by making it unchangeable.

Create new class instances with a constructor so that the result would be a ready-to-use object (ELMA1001)

The created object should not require setting additional properties before using it, whatever its purpose. If the constructor requires more than three parameters (which violates the rule ELMA1561), perhaps the class takes too much responsibility (violates the rule ELMA1000).

An interface should be compact and focused on one task (ELMA1003)

An interface must have a name that clearly describes its purpose and role in the system. Do not combine vaguely related elements into one interface just because they pertain to one class. Create interfaces based on functionality, for which the invoked methods are responsible, or on a specific purpose, the interface serves. This rule is known as the Interface Segregation Principle.

Use an interface instead of a base class to support several implementations (ELMA1004)

If you want to expose an extension point of your class, expose it as an interface, not a base class. You do not want to make the users of this extension point create their own implementations based on the base class, which may misbehave. Although, for convenience, you can create a default implementation (abstract class), which can serve as a starting point.

Use an interface for implementing loose coupling of classes (ELMA1005)

Interfaces are a great tool for implementing loose coupling of classes:

  • They help avoid bi-directional coupling;
  • They simplify replacement of one implementation with another;
  • They allow replacing an unavailable external service or resource with a temporary stub for using in a non-productive environment;
  • It allows replacing the current implementation with a dummy-based one for module testing;
  • Using a framework for implementing dependency injection, you can compile class selection logic depending on the requested interface in one place.

Avoid static classes (ELMA1008)

Static classes are often the reason behind bad code unless static classes are used to create extension methods. They are also very difficult if at all possible, to test in isolation, until you start using very sophisticated tools.

Note: If you do need a static class, mark it as static. In this case, the compiler will forbid creating instances of this class and initialize your class before accessing it for the first time. This will rid you of the necessity to use a private constructor.

Do not hide compiler warnings using a new keyword (ELMA1010)

This CS0114 compiler warning appears if you break Polymorphism, one of the most important principles of object-oriented programming. The warning disappears if you use a new keyword. Child classes become hard to understand. Take a look at this example:

public class Book
{
    public virtual void Print()
    {
        Console.WriteLine("Printing Book");
    }  
}

public class PocketBook : Book
{
    public new void Print()
    {
        Console.WriteLine("Printing PocketBook");
    }
}

In this case, the class will not behave as expected:

PocketBook pocketBook = new PocketBook();

pocketBook.Print();  // Will display "Printing PocketBook"

((Book)pocketBook).Print();  // Will display "Printing Book"

There should be no difference between invoking the Print() method by referencing a base class or as a derivative class method.

Functions that use a base type must be able to use the base type subtypes, without knowing about it (ELMA1011)

In other words, the behavior of child classes should not contradict the behavior, defined by the base class, i.e. it should be expected for the code, using a variable of the base type. A well-known example of breaking this rule is the NotImplementedException exception when it occurs when redefining a base class method.

Note: This principle is also known as the Barbara Liskov substitution principle, one of the S.O.L.I.D. principles.

Do not refer to derivative classes from the base class (ELMA1013)

If there are dependencies on child classes in the parent class, it breaks the principles of object-oriented programming and prevents other developers from inheriting from your base class.

An object should have limited information on other objects, which are not directly related to this object (ELMA1014)

If your code looks like the code below, it breaks the Law of Demeter.

someObject.SomeProperty.GetChild().Foo()

An object should not allow accessing the classes on which it depends because user-objects may use properties and methods incorrectly to access objects behind them. By doing so, you allow the invoked code and the class you use to incorporate into a single whole. Thus, you limit the possibility to replace one implementation with another in the future.

Note: Using a class, implementing the Fluent Interface, may seem as a violation of this rule. But invoked methods simply pass the invocation context to the next element. Thus, it does not cause contradictions.

Exception: When using inversion of control and dependency injection frameworks, it is often required to expose dependencies as public properties. Until these properties are used for something else but implementing dependency injection, this should not be considered as a violation of the rule.

Avoid bi-directional dependencies (ELMA1020)

Bi-directional dependency means that two classes know about each other's public methods or depend on each other's internal behavior. Refactoring or replacement of one of these two classes require changing both classes and may result in a lot of unforeseen work. The most obvious solution is to create an interface for one of the classes and use dependency injection.

Exception: Domain models used in Domain Driven Design can use bi-directional dependencies, describing real-life associations. In such cases, you should make sure that they are absolutely necessary and avoid them if possible.

Classes must have a state and a behavior (ELMA1025)

If your repository contains multiple classes, which only describe data, then most likely you have several classes (static) that contain big data processing logic (see rule ELMA1008). Use the principles of object-oriented programming according to the recommendations in this section. Move your data processing logic to the data, which it uses.

Exception: The only exception from this rule are classes, used for transferring data between applications, also known as Data Transfer Objects, or classes, used as wrapping for method parameters.

Classes must maintain their internal state consistent (ELMA1026)

Check arguments passed to public methods. For example:

public void SetAge(int years)
{
    AssertValueIsInRange(years, 0, 200, nameof(years));
    this.age = years;
}

Check internal states with invariants:

public void Render()
{
    AssertNotDisposed();

    // ...
}

Class member design guidelines

It must be possible to set class properties in any order (ELMA1100)

Properties should not depend on other properties. In other words, it should be no different which property you set first. For instance, first DataSourc, then DataMember or vice versa.

Use a method instead of property (ELMA1105)

Use methods instead of properties, if:

  • The work is more expensive than setting the field value.
  • The property represents conversion. For example, Object.ToString method.
  • The property returns different values for each invocation, even if arguments do not change. For example, the method NewGuid will each time return a new value.
  • Using the property leads to a side effect. For example, internal state change, which is not directly related to the property (it breaks the command-query responsibility segregation (CQRS)).

Exception: Internal cache fill in or lazy-loading implementation are good examples of exceptions from this rule.

Do not use mutually exclusive properties (ELMA1110)

If you have properties, that cannot be used at the same time, it means that they represent two mutually exclusive concepts. Even if these concepts can have certain common logic and state, obviously they have different and incompatible rules.

This rule is often broken in the domain model, when properties encapsulate all the possible types of conditional logic, containing mutually exclusive rules. It often creates a wave effect and maintaining such code becomes harder.

A method or a property must have one purpose (ELMA1115)

Same as a class (see rule ELMA1000), each method must have only one responsibility.

Do not make the objects describing state public with static members (ELMA1125)

Stateful objects contain multiple properties and the logic that these properties encapsulate. If you make such an object public via a static property or another object's method, then it will be difficult to refactor and unit-test classes, that depend on the object with a state. Generally, using the structure described above is a good example of breaking many guidelines, described in this chapter.

A classic example is the HttpContext.Current property in ASP.NET. Many consider the HttpContext class a source of ugly code. In fact, one of the testing rules — Isolate the Ugly Stuff — often pertains to this class.

Return IEnumerable<T> or ICollection<T> instead of any specific collection (ELMA1130)

Usually, there is no need to let users change the internal collection, which is returned as the result of method invocation since it breaks encapsulation. Do not return an array, set or another collection class directly. Instead, return IEnumerable<T> or, if the user needs to know the number of items in the collection, ICollection<T>.

Note
.Net 4.5+
You can also use IReadOnlyCollection<T>IReadOnlyList<T> or IReadOnlyDictionary<TKey, TValue>.

Properties, methods or arguments, which represent a string or a collection, must not be null (ELMA1135)

Users may not expect null to be returned as the result of a method execution. Always return an empty collection or string instead of null. Among other things, it will relieve you from the necessity to cram your code with additional checks for null or worse, use string.IsNullOrEmpty().

Define parameters as specifically as possible (ELMA1137)

If a class element requires a part of data of another class as parameters, define the data types of these parameters very specifically and do not assume an entire object as a parameter. For example, consider a method that requires passing as a parameter a certain connection string, described in a central interface IConfiguration. Instead of depending on the entire configuration that implements this interface, pass only the connection string. It will not only allow you to reduce the number of dependencies in the code, but also improve long-term maintainability.

Note: To remember this rule, use this phrase: Do not use a truck to deliver a single package.

Use types definitive for your domain area instead of primitives (ELMA1140)

Instead of using strings, integers and fractions to represent such specific types as ISBN (international standard book number), email address or money sum, create an object for each type that will include both the data and the validation rules applied to the data. By doing so, you can avoid multiple implementations of the same business rules. It will improve maintainability of your code and reduce the number of bugs.

General design guidelines

Generate an exception instead of returning a status message (ELMA1200)

A code base that uses a returned status message to define whether an operation completed successfully or failed often has nested if expressions throughout the code. Often users forget to check the returned value. Structured exception handling was introduced to allow you to generate exceptions and detect or replace them on a higher level. In most systems, it is a general practice to generate exceptions each time an unexpected situation occurs.

Provide a comprehensive exception message (ELMA1202)

A message must clarify, what caused an exception, and clearly state what to do to avoid it in the future.

Generate specific exceptions (ELMA1205)

For instance, if a method accepted null as an input parameter, generate ArgumentNullException instead of ArgumentException.

Do not ignore an error by handling general exceptions (ELMA1210)

Do not ignore errors by handling general exceptions, such as ExceptionSystemException and others, in the application code. Only the top level error handler should capture general exceptions for logging and correct completion of the application runtime.

Handle exceptions in the asynchronous code properly (ELMA1215)

When you generate or handle an exception in the code that uses async/await or Task bear in mind the following two rules:

  • Exceptions that occur within the async/await blocks and inside Task apply to the task that awaits the execution of these blocks.
  • Exceptions that occurred in the code preceding async/await and Task, apply to the invoking code.

Always check the event handler delegate for null (ELMA1220)

An event that has no subscriptions equals null. Thus, before invoking it, make sure, that the list of delegates, representing the event, does not equal null.

Invoke the event with a Null-conditional operator. It will prevent the delegate from being changed by competing flows.

event EventHandler Notify;

protected virtual void OnNotify(NotifyEventArgs args)  
{
    if (Notify != null) 
    {
        Notify.Invoke(this, args);
    }
}

To invoke each event, use a protected virtual method (ELMA1225)

Following this recommendation will allow derivative classes handle a base class event by redefining the protected method. The name of the protected virtual method must be the same as the event name, but with the On prefix. For example, the protected virtual method for an event named TimeChanged must be named OnTimeChanged.

Tip: You do not have to invoke base class implementation from the classes, which redefine the protected virtual method. The base class should continue working correctly, even if its implementation is not invoked.

Use property change notification events (ELMA1230)

A property change notification event should be named like PropertyChanged, where Property should be changed to the name of the property, to which the event is related.

Tip: If your class has multiple properties, which require corresponding events, try implementing the INotifyPropertyChanged interface instead. It is often used in the patterns Presentation Model and Model-View-ViewModel.

Do not send null as an argument when invoking an event (ELMA1235)

Often an event handler is used for handling similar events from many senders. In this case, the passed argument is used to pass the event invocation context. Always send a reference to the context (usually, this) when invoking an event. Also do not send null when invoking an event, if it does not contain data. If an event contains no data, send EventArgs.Empty instead of null.

Exception: For static events, the passed argument must be null.

Use general restrictions, if possible (ELMA1240)

Instead of converting and casting a type from a specific to a general, on the contrary, use the where keyword or the as operator to cast the object to a specific type. For example:

class SomeClass  
{}

// Incorrect  
class MyClass  
{
    void SomeMethod(T t)  
    {  
        object temp = t;  
        SomeClass obj = (SomeClass) temp;  
    }  
}

// Correct  
class MyClass where T : SomeClass  
{
    void SomeMethod(T t)  
    {  
        SomeClass obj = t;  
    }  
}

Calculate the LINQ-query result before returning it (ELMA1250)

Have a look at this code:

public IEnumerable GetGoldMemberCustomers()
{
    const decimal GoldMemberThresholdInEuro = 1_000_000;
    
    var query = from customer in db.Customers
                where customer.Balance > GoldMemberThresholdInEuro
                select new GoldMember(customer.Name, customer.Balance);
    
    return query;  
}

Since LINQ-queries use delayed execution, returning q will return the expression tree, representing the abovementioned query. Each time a user calculates the result using foreach or something similar, the entire query is executed again, each time creating new instances of GoldMember. Consequently, you will not be able to use the operator == to compare different instances of GoldMember. Instead, always calculate the LINQ-query result, using ToList(), ToArray() or similar methods.

Do not use this and base unless necessary (ELMA1251)

Usually, there is no need to know on which level a member is declared in the class hierarchy. If this level is stated in the code, refactoring becomes trickier.

Guidelines on improving code maintainability

A method should not contain more than 7 declarations (ELMA1500)

A method that includes more than 7 declarations most likely performs to many functions or takes too much responsibility. In addition, human memory requires a method to be short. It is unable to hold many things at the same time to accurately analyze and understand what a code fragment does. Divide a method into several smaller ones, with clear purpose and names that indicate what they do. Make sure that the working algorithm of this part of the program remains understandable.

Make all members of the private class and the internal sealed types by default (ELMA1501)

To make a more weighted decision on which elements will be available to other classes, first, limit their visibility. Then, think carefully which members or types to make public.

Avoid double negation (ELMA1502)

Despite such properties as customer.HasNoOrders happen to be, avoid using them with double negation. For example:

bool hasOrders = !customer.HasNoOrders;

Double negation is harder to comprehend than a simple expression, and people tend to get confused.

An assembly name must correspond to the namespace it contains (ELMA1505)

All DLL must be named according to the pattern Company.Component.dll where Company is your company name, and Component is the name of one or more namespaces, separated with periods. For example:

EleWise.ELMA.CRM.Web.dll.

As an example, consider a class group compiled under the namespace EleWise.ELMA.CRM, which is in a specific assembly. According to this guideline, this assembly must be named EleWise.ELMA.CRM.dll.

Exception: If you decide to combine classes from different unrelated namespaces in one assembly, add the Core suffix to its name. Do not use this suffix in namespace names. For example: EleWise.ELMA.CRM.Core.dll.

Name source code file according to the data type they contain (ELMA1506)

Use the pascal notation for naming files and do not use underscores. Do not include generalized parameters and their number in names.

Note: Using underscores is acceptable for prefixes, such as Locko_Application.csBUD_BudgetTeam.md.

Limit a source code file content with one data type (ELMA1507)

Exception: Nested types must be part of the same file.

Exception: The types that are different only in the number of generalized parameters must be in the same file.

namespace EleWise.ELMA.Model.Entities
{
    // Entity base interface
    public interface IEntity:  : IIdentified
    {
        ...
    }

    // Interface of an entity with ID
    public interface IEntity<IdT>:  IEntity
    {
        ...
    }
}

Name of a source code file containing a partial data type must reflect the function of this part (ELMA1508)

When you use partial types and divide parts into files, the name of each file must be logically divided into two parts. First part is the type name. The second part is the role the fragment plays in the type. For example:

// MyClass.cs file
public partial class MyClass
{...}

// MyClass.Designer.cs file
public partial class MyClass
{...}

Use using instead of a full type reference from another namespace (ELMA1510)

Do not use a full type reference from another namespace to prevent naming conflicts. For example, do not do this:

var list = new System.Collections.Generic.List<string>();

Do this:

using System.Collections.Generic;

var list = new List<string>();

If you need to avoid name conflicts, use the using directive to create a namespace or type alias:

using Label = System.Web.UI.WebControls.Label;

Do not use "magic" numbers (ELMA1515)

Do not use literal values, numbers, and strings in your code for anything but declaring constants. For example:

public class Whatever  
{
    public static readonly Color PapayaWhip = new Color(0xFFEFD5);
    public const int MaxNumberOfWheels = 18;
    public const byte ReadCreateOverwriteMask = 0b0010_1100; 
}

Strings intended for logging or tracking are an exception from this rule. Literal values are allowed only when their meaning is clear from the context and they will not be changed. For example:

mean = (a + b) / 2;  
WaitMilliseconds(waitTimeInSeconds * 1000); 

If the value of one constant depends on the value of another, state this in your code.

public class SomeSpecialContainer  
{  
    public const int MaxItems = 32;  
    public const int HighWaterMark = 3 * MaxItems / 4; // 75%  
}

Note: Enumerations may often be used as a storage for symbolic constants.

Use var only when the variable type is obvious (ELMA1520)

Use var only when a variable receives the result of a LINQ-query or if the variable type is obvious and using var will improve the code readability. For example, do not do this:

var item = 3;                       // What type? int? uint? float?
var myfoo = MyFactoryMethod.Create("arg");  // Unclear, what is the type
                                            // of the class or interface. Moreover,
                                            // it is hard to edit code that works
                                            // with this variable, if the source class
                                            // is not available to your

Instead, use var like this:

var query = from order in orders where order.Items > 10 and order.TotalValue > 1000;
var repository = new RepositoryFactory.Get();   
var list = new ReadOnlyCollection();

In all three examples, the type of the values assigned to variables is obvious. For more information about using var, read Eric Lippert's article Uses and misuses of implicit typing.

Declare and initialize variables as late as possible (ELMA1521)

Avoid the C and VisualBasic language style, when all the variables are declared at the beginning of the unit. Declare and initialize each variable only when it is necessary.

Assign the value to each variable in a separate declaration (ELMA1522)

Never do this:

var result = someField = GetSomeMethod();

Exception: Several assignment expressions in one string are allowed when using out variables, is-pattern or conversion to tuple. For example:

bool success = int.TryParse(text, out int result);

if ((items[0] is string text) || (items[1] is Action action))
{
}

(int a, int b) = M();

Prefer object and collection initializers to setting properties separately and adding new objects to a collection separately (ELMA1523)

Instead of this:

var startInfo = new ProcessStartInfo("myapp.exe");  
startInfo.StandardOutput = Console.Output;
startInfo.UseShellExecute = true;

var countries = new List();
countries.Add("Netherlands");
countries.Add("United States");

var countryLookupTable = new Dictionary<string, string>();
countryLookupTable.Add("NL", "Netherlands");
countryLookupTable.Add("US", "United States");

Use object initialization:

var startInfo = new ProcessStartInfo("myapp.exe")  
{
    StandardOutput = Console.Output,
    UseShellExecute = true  
};

var countries = new List { "Netherlands", "United States" };

var countryLookupTable = new Dictionary<string, string>
{
    ["NL"] = "Netherlands",
    ["US"] = "United States"
};

Do not explicitly compare with true or false (ELMA1525)

Comparing a logical value with true or false is usually bad coding style. For example:

while (condition == false)// wrong, bad style
while (condition != true)// also wrong
while (((condition == true) == true) == true)// where do you stop?
while (condition)// OK

Comparing with true or false is only acceptable for bool? type fields (Nullable)

Do not change the cycle variable for or foreach inside the cycle body (ELMA1530)

Updating a cycle variable inside a cycle body make the code confusing. Especially, if the variable is changed in more than one place.

for (int index = 0; index < 10; ++index)  
{  
    if (someCondition)
    {
        index = 11; // Wrong! Instead use ‘break’ or ‘continue’. 
    }
}

Avoid nested cycles (ELMA1532)

Methods containing nested cycles are harder to understand than those containing one cycle. In fact, in most cases cycles can be replaced with a much smaller LINQ-query, which uses the from keyword two or more times to unify data.

Always use structures ifelsedowhileforforeach and case with braces (ELMA1535)

Please note, that this will also help avoid possible confusion with such structures as this:

if (isActive) if (isVisible) Foo(); else Bar(); // to which ‘if’ ‘else’ pertains?

// Better way:
if (isActive)
{
    if (isVisible)
    {
        Foo();
    }
    else
    {
        Bar();
    }
}

Always use the default block at the end of the switch/case structure (ELMA1536)

If the default block is empty, add a comment. In addition, if this block should not be reached, generate InvalidOperationException when it is invoked to detect future changes that may fall through the existing cases. This will allow you to write better code, because all the script execution scenarios have been thought through.

void Foo(string answer)  
{  
    switch (answer)  
    {  
        case "no":  
        {
          Console.WriteLine("You answered with No");  
          break;
        }
          
        case "yes":
        {
          Console.WriteLine("You answered with Yes");
          break;
        }
        
        default:
        {
          // Not supposed to end up here.  
          throw new InvalidOperationException("Unexpected answer " + answer);
        }  
    }  
}

End each if-else-if block with else declaration (ELMA1537)

For example:

void Foo(string answer)
{
    if (answer == "no")
    {
        Console.WriteLine("You answered No");
    }
    else if (answer == "yes")
    {
        Console.WriteLine("You answered Yes");
    }
    else
    {
        // What should happen when this block is reached? Ignore it?
        // If not, generate InvalidOperationException.
    }
}

Try to avoid multiple declarations of return (ELMA1540)

One entry — one exit is a reasonable principle. It helps keep the method execution understandable. If the method is small and complies with ELMA1500, then several declarations of return may be relevant and will improve code readability. For example, if a method returns a logical value, it is more convenient to use two return declarations instead of a logical variable, which the method will return and to which values will be assigned during the execution.

Do not use the if-else block instead of a simple (conditional) assignment (ELMA1545)

Express your intentions directly. For example, instead of:

bool isPositive;

if (value > 0)  
{  
    isPositive = true;  
}  
else  
{  
    isPositive = false;  
}

Do this:

bool isPositive = (value > 0); 

Instead of:

string classification;

if (value> 0)
{  
    classification = "positive";  
}
else
{
    classification = "negative";
}

return classification;

Do this:

return (value > 0) ? "positive" : "negative";

Instead of:

int result;

if (offset == null)
{
    result = -1;
}
else
{
    result = offset.Value;
}

return result;

Do this:

return offset ?? -1;
Note
.Net 4.5+

Instead of:

if (employee.Manager != null)
{
    return employee.Manager.Name;
}
else
{
    return null;
}

Do this:

return employee.Manager?.Name;

However, know the limit. Do not do this:

return workflowTask?.Bookmark?.Instance?.Process?.Header?.Published;

And never do this:

return GetProcessHeader()?.Name;

encapsulate a complex expression in a method or a property (ELMA1547)

Consider this example:

if (member.HidesBaseClassMember && (member.NodeType != NodeType.InstanceInitializer))
{
    // do something
}

To understand what this code does you are going to need to analyze its details and predict all the execution results. Sure, you can add a comment before the code, but it would be better to replace a complex expression with a method, with a self-explanatory name.

if (NonConstructorMemberUsesNewKeyword(member))
{  
    // do something
}  


private bool NonConstructorMemberUsesNewKeyword(Member member)  
{  
    return
        (member.HidesBaseClassMember &&
        (member.NodeType != NodeType.InstanceInitializer)  
}

Should you need to change this method, you are still going to need to figure out how it works. But now it is much easier to understand the code that invokes it.

Invoke the more overloaded method from other overloads (ELMA1551)

This rule is applied only to those methods that are mutually overloaded with optional arguments. For example:

public class MyString  
{
    private string someText;
    
    public int IndexOf(string phrase)
    {  
        return IndexOf(phrase, 0);
    }
    
    public int IndexOf(string phrase, int startIndex)
    {  
        return IndexOf(phrase, startIndex, someText.Length - startIndex);
    }
    
    public virtual int IndexOf(string phrase, int startIndex, int count)
    {  
        return someText.IndexOf(phrase, startIndex, count);
    }  
}

The MyString class provides three overloads of the IndexOf method, two of which call the other with a lot of parameters. Note, that this rule applies to class constructors. Implement the more overloaded constructor and call it from other overloads by using the this() operator. Also note, that parameters with the same names must be in the same order in all the overloads.

Important: If you want to change class behavior by redefining these methods, declare the most overloaded method as non-private virtual method, invoked by all overloads.

Use optional arguments only to replace overloads (ELMA1553)

The only allowed reason to use optional arguments of C# 4.0 is replacing the example from the rule ELMA1551 with a single similar method, under the condition that overload methods have not been used before (they are new):

public virtual int IndexOf(string phrase, int startIndex = 0, int count = -1)
{
    int length = (count == -1) ? (someText.Length - startIndex) : count;
    return someText.IndexOf(phrase, startIndex, length);
}

If an optional parameter is a reference type, then its default value can only be null. However, as we know, string, list, and collections must never equal null (as per the rule ELMA1135). Therefore, you should use an overloaded method instead.

Note: The compiler copies the value of optional parameters to the call site. Therefore, changing the default value for optional parameters should be accompanied with recompilation of the invoking code.

Note: When a class method is used via an interface, passing optional parameters is unavailable until the object that contains this method is cast to the respective class. For more information, read Eric Lippert's article.

Avoid using named arguments (ELMA1555)

C# 4.0 named arguments were created to simplify invocation of COM components, which are known to offer tons of unnecessary parameters. If you need named arguments to improve readability of the method invocation, most likely this method has too much responsibility and must be refactored.

Exception: The only exception is when named arguments can improve readability is method invocation with a logical parameter, whose source code is unavailable for you. For example:

object[] myAttributes = type.GetCustomAttributes(typeof(MyAttribute), inherit: false);

Do not let a method or constructor assume more than three parameters (ELMA1561)

If your method or constructor assumes more than three parameters, use a structure or class to encapsulate them in accordance with the Specification pattern. Generally, the fewer parameters there are, the clearer the method. Furthermore, unit-testing of a method with multiple parameters requires numerous testing scenarios.

Exception: When using inversion of control and dependency injection frameworks, it is often necessary to set dependencies as parameters in a constructor. Until these parameters are used for anything but dependency injection implementation, this should not be considered breaking the rule.

Do not use ref and out in parameters (ELMA1562)

They make code less readable and potentially cause errors. Instead, return compound objects as the function execution result.

Exception: It is allowed to declare and use methods, which implement TryParse. For example:

bool success = int.TryParse(text, out int number);

Do not create methods, which assume a logical value as a parameter (ELMA1564)

Take a look at this method:

public Customer CreateCustomer(bool platinumLevel) {}

It looks good at the first glance, but when you use this method, the logical variable loses its point:

Customer customer = CreateCustomer(true);

Usually, if a method assumes bool as a parameter, it is doing more than one thing and requires refactoring into two or more methods. An alternative is replacing the bool with an enumeration.

Do not use parameters as temporary variables (ELMA1568)

Never use a parameter as an internal variable. Even if a parameter type matches the type you need, the name usually does not reflect the purpose of the temporary variable.

Prefer is to as (ELMA1570)

If you use the as operator to cast an object to a specific interface, always check the result it returns for null. Failing to follow this guideline may cause NullReferenceException on a much later stage of program execution, if the object does not implement the required interface.

Pattern matching helps prevent it and improve readability. For example, instead of:

var remoteUser = user as RemoteUser;
if (remoteUser != null)
{
}

Use:

if (user is RemoteUser remoteUser)
{
}
Attention!
This example applies only to С# 7. For C# 4 you still have to use as casting with checking for null

Do not leave commented code fragments (ELMA1575)

Never send commented code to repository. Instead, use a task tracking system to see what work has to be done. Nobody will know the purpose of a commented code fragment. Was it temporarily commented for testing? Was it copied as an example? Should I delete it?

Naming guidelines

Use American English (ELMA1701)

All the class members, parameters, and variables must be named using American English words.

  • Choose a simple, readable, grammatically correct name. For example, HorizontalAlignment is more readable than AlignmentHorizontal.
  • Prefer readability to size. A property name CanScrollHorizontally is better than ScrollableX (an ambiguous X axis reference).
  • Avoid using names that conflict with programming keywords.

Exception: In most projects, you can use words and phrases from a domain area typical for this application, as well as names specific to your company. The static code analyzer Visual Studio will analyze the entire code, therefore you may need to add these terms to the custom code analysis dictionary.

For each language element, use a respective notation (ELMA1702)

Language element Notation Example
Class, structure Pascal AppDomain
Interface Pascal IBusinessService
Enumeration (type) Pascal ErrorLevel
Enumeration (value) Pascal FatalError
Event Pascal Click
Private field Camel listItem
Protected field Pascal MainPanel
Constant field Pascal MaximumItems
Constant local variable Camel maximumItems
Read-only static field Pascal RedValue
Local variable Camel listOfValues
Method Pascal ToString
Local function Pascal FormatText
Namespace Pascal System.Drawing
Parameter Camel typeName
Type parameters Pascal TView
Property Pascal BackColor
Tuple Camel firstName

Do not include numbers in variable, parameter, and type names (ELMA1704)

In most cases, they are poor excuses for not defining a clear and self-explanatory name.

Do not use prefixes in field names (ELMA1705)

For example, do not use g_ or s_ to distinct static and non-static fields. Usually, if it is hard to tell apart variables from class fields in a method, then this method is too clumsy. Here are some examples of incorrect names: _currentUsermUserNamem_loginTime.

Do not use abbreviations (ELMA1706)

For instance, use OnButtonClick instead of OnBtnClick. Avoid using single characters in variable names, such as i and q. Instead use full words, such as index and query.

Exception: Using common acronyms and abbreviations may be an exception. For example, using UI instead of UserInterface and Id instead of Identity.

Name class members, parameters, and variables according to their purpose, not type (ELMA1707)

  • Use a name that points at a function, that a class name performs. For example, the name GetLength is better than GetInt.
  • Do not use such terms as EnumClass or Struct in names.
  • Variables, referring to collections, must be named in plural.

Exception: Property declaration for dependency injection. In this case, name and type may match 

private readonly IMetadataRuntimeService MetadataRuntimeService;

Name types with a combination of nouns and adjectives (ELMA1708)

For example, the name IComponent consists of a noun, the name ICustomAttributeProvider consists of a phrase, and the name IPersistable consists of an adjective.

Bad examples: SearchExamination (a page for searching examination results), Common (lacks a noun, does not explain the purpose) and SiteSecurity (although technically OK, the purpose is unclear).

Do not use in class names such terms as Utility or Helper. Classes with such names are usually static and designed without regard to the principles of object-oriented programming (see also rule ELMA1008).

When naming parameters of universal types, use descriptive names (ELMA1709)

  • Always add Т prefix to descriptive names of type parameters.
  • Always use descriptive names if a name consisting of one letter is not clear as it is. In this case, use the letter Т as a type parameter name.
  • It is recommended to specify restrictions applied to the type parameter in the type parameter name. For example, a parameter intended only for ISession may be called TSession.

Do not repeat a class or enumeration name in their members (ELMA1710) 

class Employee
{
    // Wrong!
    static GetEmployee() {}
    DeleteEmployee() {}
    
    // Correct
    static Get() {...}
    Delete() {...}
    
    // Also correct.
    AddNewJob() {...}
    RegisterForMeeting() {...}
}

Name elements similarly to the elements of related .NET Framework classes (ELMA1711)

.NET developers are already accustomed to naming patterns, which are used in .NET Framework. Following these patterns will make it easier for them to read your code. For example, if you define a class that implements a collection, name the methods for deleting, adding and getting the number of items like AddRemove and Count instead of AddItemDelete, or NumberOfItems.

Avoid short names or names that can be confused with other names (ELMA1712)

Despite the following expression may seem correct from the technical point of view, it can easily be misleading: 

bool b001 = (lo == l0) ? (I1 == 11) : (lOl != 101);

Name properties properly (ELMA1715)

  • Name properties using nouns, noun phrases or sometimes adjective phrases.
  • Name a property of the logical type using affirmative phrases. For example, CanSeek instead of CannotSeek.
  • Try using prefixes IsHasCanAllows or Support in the names of properties of the logical type.
  • Try naming a property the same as its type. When you create a property of the enumeration type, the property name may be the same as the enumeration type name. For example, if you have an enumeration CacheLevel, then the property returning one of its values also must be named CacheLevel.

Name methods and local functions using verbs or verb-object combinations (ELMA1720)

Use methods and local functions using such verbs as Show or such verb-object combinations as ShowDialog. A good name must hint at what a method does and, if possible, why.

Also do not use the word And in the names of methods or local functions. It means that a method or a local function has more than one function which breaks the single responsibility principle (ELMA1115).

In namespace names use proper names, module (layer) names, verbs and words, characterizing this namespace (ELMA1725)

The names of the following namespaces are good examples: 

NHibernate.Extensibility
Microsoft.ServiceModel.WebApi
Microsoft.VisualStudio.Debugging
FluentAssertion.Primitives
CaliburnMicro.Extensions

Note: Make sure that namespace names never contain type names, unless it is a plural noun, e.g. Collections, which is usually acceptable.

Use a verb or a verb phrase in an event name (ELMA1735)

Name an event with a verb or a verb phrase. For instance: ClickDeletedClosingMinimizing, and Arriving. A declaration of the Search event may look like this:

public event EventHandler<SearchArgs> Search;

Use –ing and –ed for events that should occur before or after another event (ELMA1737)

For example, an event that precedes the closing of a window should be named Closing, and the event that occurs after closing the window — Closed. Do not use Before and After or any suffixes to identify such events.

Assume you want to define events related to deleting an object. Name the events Deleting and Deleted and avoid such names as BeginDelete and EndDelete. Name the events as described:

  • Deleting: Occurs right before deleting the object.
  • Delete: Occurs when the object must be deleted by the event handler.
  • Deleted: Occurs when the object is already deleted.

Use the On prefix in the event handler name (ELMA1738)

A good practice is adding the On prefix to the name of a method that handles an event. For example, if a method handles the Closing event then the name should be OnClosing.

Use the underscore for lambda-expression parameters, which have no value (ELMA1739)

If you are using a lambda-expression (for example, to subscribe to an event) and the current parameters do not have a value, use the following syntax to stress it out:

button.Click += (_, __) => HandleClick();

Name extension method groups in a class using the Extensions suffix (ELMA1745)

If an extension method name conflicts with another element or extension method, you should add a prefix in the form of invocation class name. Adding them to a related class with the Extensions suffix will improve readability.

Add the Async or TaskAsync suffixes to the asynchronous method names (ELMA1755)

Adding the Async suffix to methods that return Task and Task<TResult> is common. If a method with this name already exists, then use the TaskAsync suffix instead.

Performance improvement guidelines

Use Any(), to check IEnumerable for being empty (ELMA1800)

If a method or another element returns IEnumerable<T> or another collection class that does not provide the Count property, use the extension method Any() instead of Count() to check the collection for being empty. If you use Count(), you are risking performance reduction, since it will lead to iteration of the entire collection (for example, in case of IQueryable<T> a data query will be executed).

Note
.Net 4.5+
If you return IEnumerable<T> to prevent changing the returned collection, try using new read-only classes as recommended in the rule ELMA1130.

Use async only for long-term and low-intensity tasks (ELMA1820)

Using async will not automatically starting anything in the work thread, as the Task.RunAsync does; it will simply add the necessary logic that allows releasing the current thread and returning the result to the same thread after completing an asynchronous operation. In other words, use async only for operations, related to input/output.

Use Task.Run for high-intensity tasks (ELMA1825)

If you need to execute an operation related to allocating additional CPU resources, use Task.Run to offload the work to another thread from the thread pool. Keep in mind, that you will have to manually return the result to your main thread.

Avoid using await/async with Task.Wait (ELMA1830)

await will not block the current thread, but only inform the compiler that it is necessary to build a states machine. However, Task.Wait will block the thread and may event result in an interlock (see ELMA1835).

Beware of async/await interlock in a single-thread environment (ELMA1835)

Consider the following asynchronous example:

private async Task GetDataAsync()
{
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

Then call it in the ASP.NET MVC controller method like this:

public ActionResult ActionAsync()
{
    var data = GetDataAsync().Result;
    
    return View(data);  
}

Here you are going to end up with an interlock. Because the getter of the Result property will block the thread until the async operation is completed but since the async method will automatically return the result to the original thread and ASP.NET uses the single-thread synchronization context, they will keep waiting for each other. A similar issue may arise with WPF, Silverlight or C#/XAML applications in Windows Store. You can find more information here.

Framework usage guidelines

Use type aliases of C# types instead of types from the System namespace (ELMA2201)

For example, use object instead of Objectstring instead of String and int instead of Int32. These aliases were introduced to make primitive types first-class C# members, so use them properly.

Exception: When referencing static elements of such types a full CLS name is usually used, for example, Int32.Parse() instead of int.Parse(). The same applies to methods that specify the type of the returned value. For example: ReadInt32GetUInt16.

Be precise when naming properties, variables or fields, referring to localized resources (ELMA2205)

These recommendations apply to localized resources, such as error messages and menu text.

  • Use the pascal notation for resource keywords.
  • Prefer descriptive names to short names. Make names brief, but keep readability.
  • Use only letters and numbers when naming resources.

Do not keep the code lines that are to be changed when deploying the application (ELMA2207)

For example, connection strings, server addresses, etc. Use resource files, the ConnectionStrings property of the ConfigurationManager class or the Settings class generated by Visual Studio. Maintain relevant values of the settings via app.config or web.config (not somewhere else).

Build with the highest warning level (ELMA2210)

Configure your working environment to use the fourth warning level for the C# compiler and enable the option "Treat warnings as errors". It will allow compiling the code with the highest quality possible.

Thoroughly fill out attributes in the AssemblyInfo.cs file (ELMA2215)

Make sure, that attributes for the company name, description, copyright warning, etc. are filled in. One of the ways to make the version number and other common for all assemblies fields always the same is to move the corresponding attributes from AssemblyInfo.cs to the file SolutionInfo.cs, which is used by all the projects in the solution.

Avoid using LINQ for simple expressions (ELMA2220)

Instead of:

var query = from item in items where item.Length > 0 select item;

Use a method from the System.Linq namespace:

var query = items.Where(item => item.Length > 0);

In addition, LINQ-queries must be divided into several lines for readability. Thus, the second expression looks more compact.

Use lambda-expressions instead of anonymous functions (ELMA2221)

Lambda-expressions are a more aesthetic alternative to anonymous functions. Thus, instead of :

Customer customer = Array.Find(customers, delegate(Customer customer)
{
    return customer.Name == "Tom";
});
use a lambda-expression:
Customer customer = Array.Find(customers, customer => customer.Name == "Tom");
or even better:
var customer = customers.FirstOrDefault(c => c.Name == "Tom");

Use the dynamic keyword only when working with objects of this type (ELMA2230)

The dynamic keyword was introduced for working with dynamic languages. Using them creates a serious performance bottleneck, since the compiler has to generate a certain amount of additional code.

Use dynamic only when referring to members of a dynamically created class instance (when using the Activator class) as an alternative to Type.GetProperty() and Type.GetMethod() or when working with COM Iterop types.

Try using async/await along with Task (ELMA2235)

Using new C# 5.0 keywords facilitates coding and code readability, which in turn simplifies the code maintenance. Even if you need to create many asynchronous operations. For example, instead of doing this:

public Task GetDataAsync()
{
  return MyWebService.FetchDataAsync()
    .ContinueWith(t => new Data(t.Result));
}
declare the method like this:
public async Task<Data> GetDataAsync()
{
  string result = await MyWebService.FetchDataAsync();
  return new Data(result);
}

Tip: Even if you need .NET Framework 4.0 you can still use async and await. Simply install Async Targeting Pack.

Documentation guidelines

Write comments and documentation in English (ELMA2301)

Document all the publicprotected and internal types and members (ELMA2305)

Documenting your code will allow Visual Studio to show tips when your class is used somewhere else. Additionally, when your classes are well documented, you can generate document to your code, which looks professional.

When writing XML documentation, bear in mind other developers (ELMA2306)

Write XML documentation bearing other developers in mind. Maybe he or she will not have access to the source code and you need to better explain how to use your type.

Use MSDN style for writing documentation (ELMA2307)

Adhere to the MSDN online Help style to help other developers figure out your documentation quicker.

Tip: GhostDoc can generate xml comments for creating documentation with hotkeys.

Avoid inline comments (ELMA2310)

If you need to clarify a fragment of code with a comment, most likely you should make this code a separate method and give it a name that would explain its purpose.

Write comments only to explain complex solutions and algorithms (ELMA2316)

Try to make your comments answer the questions why and what, not how. Avoid explaining a code fragment with words, instead help the person who will read your code to understand why you have chosen this solution or algorithm and what you are trying to achieve with it. If you select the alternative, if possible, also explain why a simpler solution caused trouble.

Do not use comments to track work that must be done later (ELMA2318)

It may seem a good idea to add a TODO comment to a code fragment to track work that should be done later. In fact, nobody needs such comments. To track incomplete work, use a task tracking system such as Team Foundation Server.

Layout guidelines

Use common layout guidelines (ELMA2400)

  • Keep lines under 130 characters long.

  • Use 4 spaces as indents instead of tabulation.

  • Use one space between a keyword (e.g. if) and an expression but do not use spaces after (and before). For example: if (condition == null).

  • Add a space before and after operators (for example +-== and so on)

  • Always use the structures ifelseswitch casedowhilefor and foreach with paired braces, even if it is not necessary.

  • Always open and close paired braces in a new line.

  • Do not initialize and object/collection line-by-line. Use the following format:

  •   var dto = new ConsumerDto
      {
          Id = 123,
          Name = "Microsoft",
          PartnerShip = PartnerShip.Gold,
          ShoppingCart =
          {
              ["VisualStudio"] = 1
          }
      };
  • Do not divide a lambda-expression declaration into several lines. Use this format:
  •   methodThatTakesAnAction.Do(x =>
      { 
          // some actions
      }
  • Do not take over an expression by parts to a new line. Divide long lines like this:
  •  private string GetLongText =>
                  "ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC";
  • Declare a LINQ-query in one line or declare each keyword of this query in a new line, as shown:
  • var query = from product in products where product.Price > 10 select product;
  • or
  • var query =
          from product in products
          where product.Price > 10
          select product;
  • Start a LINQ-query with declaration of all the from expressions and do not mix them with other expressions.

  • Add braces around each comparison in a conditional expression, but do not add them around a single expression. For example: if (!string.IsNullOrEmpty(str) && (str != "new"))

  • Add an empty line between multiline expressions, multiline class members, after closing paired braces, after unrelated code blocks, before and after the #region keyword and between using declarations, if namespaces have different root namespaces.

Place and group namespaces by company name (ELMA2402)

// Microsoft namespaces first
using System;
using System.Collections.Generic;
using System.XML;

// Then other namespaces alphabetically
using EleWise.ELMA.ComponentModel;
using EleWise.ELMA.Services;
using Telerik.WebControls;
using Telerik.Ajax;

Static directives and aliases must be declared after all others. Always place using directives at the top of the file, before declaring namespaces (not inside them).

 

Place class members in a strict order (ELMA2406)

Keeping your code in order helps other team members read it. Generally, you read a source code file is from top to bottom, as if you were reading a book. It prevents a situation when you need to view a file upward and downward looking for the required fragment.

  1. Private fields and constants (in #region)
  2. Public constants
  3. Public read-only static fields
  4. Fabric methods
  5. Constructors and finalizers
  6. Events
  7. Public properties
  8. Other methods and private properties in the order of invocation

Declare local function at the end of the method, in which they are declared (after the rest code).

Be careful when using the #region directive (ELMA2407)

The #region keyword may be useful, but it can also hide the main purpose of the class. Therefore, use #region only for:

  • Private fields and constants (preferably in Private Definitions region).
  • Nested classes
  • Interface implementations (only if interface implementation is not the main purpose of this class)

Useful links

In addition to the many links in this document, I would like to recommend the following books, articles, and websites for all interested in software quality: