C# Coding Best Practices

C# Coding Best Practices

·

4 min read

C# is a reliable and time-tested language. However, even the most experienced developers make mistakes from time to time. To write code that is easy to read and maintain, it’s important to follow best practices.

This blog post will discuss some of the most common C# best practices, and explain why they are important. Keep in mind that not all of these guidelines will apply to every project — use your best judgment when deciding what is right for your codebase.

Enumerables:

  1. When checking if an IEnumerable<T> is empty always use enumerable.Any() instead of enumerable.Count() == 0.

  2. When returning a collection, always return an empty collection if there are no elements, but never null.

     IEnumerable<Product> GetProductsByCategory(string category)
     {
         if (string.IsNullOrWhiteSpace(category))
         {
             return Enumerable.Empty<Product>();
         }
         var products = _dbContext.Products.Where(p => p.Category == category).ToList();
         return products;
     }
    

3. When accepting a collection as a method argument, however, always check for null.

4. You can initialize the collection with an extension method:

Let’s say you have a Person class and want to have a list of people. With collection initializer syntax, it would look like this:

public class Person
{
  public string Name { get; set; }
  public int Age { get; set; }
}

var people = new List<Person>
{
                 new Person { Name = "Sophia", Age = 25},
                 new Person { Name = "Lucas", Age = 30},
                 new Person { Name = "Emma", Age = 38},
};

After C# 6, the Add() method used by the collection initializer can be an extension method as well:

public static class PersonExtensions
{
  public static void Add(this ICollection<Person> people, string name, int age) =>
  people.Add(new Person {Name = name, Age = age });
}

//Usage:

var people = new List<Person> { {"Sophia", 25}, {"Lucas", 30} };

Methods:

5. Almost always return an interface type and return the most generic one making sense for the typical consuming code.

6. Try to keep the maximal number of arguments on a method to 3.

7. Make more and better-defined functions. Extract related concepts into groups/packages to avoid cognitive complexity. Read more about cognitive complexity here.

Interfaces:

8. Keep interfaces as short as possible so it’s relatively simple to provide an alternative implementation for them (even when doing unit testing).

9. AVOID using marker interfaces (interfaces with no members).

Declaration statements:

10. Don’t use var when the type is not apparent from the right side of the assignment. Don’t assume the type is clear from a method name. A variable type is considered clear if it’s a new operator or an explicit cast.

int age = Convert.ToInt32(Console.ReadLine());

Strings:

11. Instead of using the == operator for string comparison, use the Equals method with StringComparison:

public bool Equals(string value, StringComparison comparisonType);

Beware of concatenating a large number of strings, especially inside a loop. Use the System.Text.StringBuilder class instead.

Exception handling:

12. In general, it’s good programming practice to catch a specific type of exception rather than use a basic catch statement. Read more about this here.

13. In custom exceptions, provide additional properties as needed.

14. Always keep a trace of the exception stack
Using throw e; is not a good way to throw a caught exception since C# allows us, simply with throw;, to throw the exception in a catch block.

This way we would keep track of the stack and get a much better view of the exception.

Bad example:

try
{
    FunctionThatMightThrow();
}
catch (Exception ex)
{
    logger.LogInfo(ex);
    throw ex;
}

Good example:

try
{
    FunctionThatMightThrow();
}
catch (Exception error)
{
    logger.LogInfo(error);
    throw;
}

Including information in an exception is a good practice since it will help in the debugging of the error. If, on the other hand, the goal is to record an exception, then a throw should be used to pass the buck to a caller.

Conclusions

I hope that you found this article useful. Also, if there are any comments/more best practices that you think should be included, please do so in the comments section.