Tips To Write Clean C# Code

Introduction

In these modern days, writing code is not difficult. However, writing clean and scalable code is not easy. In this article, we will talk about some tips to write clean C# code for our projects. At first glance, any developer that has never seen your code before has to understand it as much as possible. Each class should tell a story; it helps us to understand the code better.

Here is a list of important tips to write clean C# code.

Use a Good IDE

First things first, choose the Best IDE Available for your Tech Stack. In our case, Visual Studio is one of the most popular and betters IDEs for C#. It is a solid and completely FREE product of Microsoft. Some developers prefer Rider IDE as well (paid). Working with these IDEs makes sure that your code remains tidy. Visual Studio has pretty stable Intellisense features that can correct and suggest changes in code. You can find a detailed tutorial on how to install here.

Use Meaningful Names

Naming variables is probably the hardest part of the entire software development life cycle. Thinking of meaningful names for your variables and methods is quite time-consuming. But skipping this process and giving random names is not a good idea either, is it?

Bad Practice

int d;

This is the easiest way to name variables right? But DO NOT do it. A good name helps other developers to understand the context and usage of the variable/method. Here is how you would want to name your variables.

Good Practice

int daysToAppocalypse;

Use Camel/Pascal Case Notation

Apart from choosing an appropriate name for your variables, also maintain how you write the names. Ideally, we use Camel Case and Pascal Case Notation as best code practices. Do not use random Capitals throughout your variables. That just doesn’t look pretty!

Camel Case Notation

Basically, the first letter of the first word of the variable will be in lower case, and the first letter of every other word that follows should be in upper case. You will have to use this notation while naming your local variables and method arguments.

Bad Practice

int RandomInteger;
string FirstName;

Good Practice

int randomInteger;
string firstName;

Pascal Case Notation

Here, the first letters of all your words should be in Upper Case. We use this kind of notation for naming Methods and Classes

Bad Practice

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

Good Practice

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

Pay Attention to Formatting

Formatting your code improves code readability. Tabs over Spaces, Remember?

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

How does this look? Pretty annoying, yeah? Now, Visual Studio has a built-in feature to format your code perfectly. To do this, go to the concerned class and simply Press CTRL + K and CTRL + D. See it? Cool, yeah?

latest

Add Comments Whenever Necessary

This is something we all developers hate, don’t we? 😀 However, adding a few lines of comments/description of what the method actually does helps you and the other developers in the longer run. Visual Studio makes it much easy for you. Simply Go above the concerned method and type in ///, VS automatically generates a comment template for you including the arguments of the method.

latest2

Now, why is this cool? Whenever you call this method (from anywhere), Visual Studio shows your comment too. Trust me, it is very helpful.

P.S. Add comments only when the situations demand you to. For example, when a particular method is too complex and requires an in-depth explanation. This is one case where you would like to add comments. Remember that maintaining comments will become a task as well, so use comments sparingly.

Reuse Code

Writing code that can be reusable is quite vital. It can decrease the overall lines of code in your project and makes it highly efficient. You do not want to copy-paste a function through multiple classes. Rather what you could do is, make a Shared Library Project and reference it in each of the required projects. This way, we build reusable functions. And, if there is any modification needed, you would just have to change the code in the shared library, not everywhere.

Keep Class Size Small

According to the Solid Principles, you must segregate classes to small blocks which has a single responsibility function only. This helps us to achieve loosely coupled code. Make sure that you don't need to scroll over and over while viewing a class. This can be a general rule of thumb.

Use Design Patterns

This may be something to do for an architect level developer. It takes quite a lot of experience to determine what kind of design patterns to apply to which scenario. Design patterns are basically patterns that can provide a reusable solution while architecting solutions.

Structure your Solution Well

Do you build Structured Solutions? It is highly satisfying and important to build one. Here is one of my on-going solutions following an Onion Architecture.

latest4

You understand the point. It is still possible to do everything within a single project. But, in order to favor scalability and loosely couple the solutions, we split them up to various layers like Application, Domain, Infrastructure, and so on.

Advantages

  1. Reusability: If you want to use the same Project for another solution, you could do so.
  2. Improved Security: High-quality code is less likely to contain vulnerabilities such as buffer overflows, injection attacks, or improper access control. Code that follows best practices is more resilient against common security threats.
  3. Highly Maintainable: Well-structured code is easier to maintain. When changes or updates are needed, developers can make modifications with confidence, knowing they are less likely to introduce bugs.
  4. Scalable: Clean code is more scalable. As a project grows, maintaining clean code becomes increasingly important. It allows for the addition of new features and changes without causing excessive technical debt.

Avoid Magic Strings/Numbers

What are Magic Strings? They are strings that are directly specified within the application code which has a direct impact on the application behavior. In other words, do not use hardcoded strings or values in our application. It would be tough to keep track of such strings when your application grows. Also, these strings can be associated with some kind of external references, like a file name, file path, URL, etc. In such cases, when then the location of the resources change, all these magic strings would have to be updated, or else that application breaks. Consider the following example:

if(userRole == "Admin")
{
//logic here
}

Instead of this, you could write the following

const string ADMIN_ROLE = "Admin"
if(userRole == ADMIN_ROLE )
{
//logic here
}

Alternatively, you could also create an Enum for the User Roles and simply use it. This a much cleaner way to write code.

Remove Unused Code

There is often a practice of commenting out the unused code. This ultimately increases the lines of code when the application gets compiled. You do not want to do this. You could use source controls like Git to make sure that you can revert back at any time. Prefer using Git over commenting out code.

Use Method Chaining

This is a common technique used extensively in the default generated codes by Microsoft. Here, each method returns an object and these functions will be chained together in a single line. Recognize it? This is a good example of method chaining.

services.AddHealthChecks().AddSqlServer(_configuration.GetConnectionString("DefaultConnection"));

Here is a detailed example. We have a student class and another random method that creates and returns a data-filled student object.

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

public Student SomeMethod()
{
    Student testStudent = new Student();
    testStudent.Name = "Jay Krishna Reddy";
    testStudent.Age = 25;
    return testStudent;
}

Setting the values to the Student object may not be an issue for us developers. But, let’s say a unit test developer has to test on your class and really doesn’t get C#, or you want to please your client by making this whole process simple. This is where Fluent Interfaces come in. Create a new Fluent class as shown below:

public class StudentFluent
{
    private Student student = new Student();

    public StudentFluent AddName(string name)
    {
        student.Name = name;
        return this;
    }

    public StudentFluent AddAge(int age)
    {
        student.Age = age;
        return this;
    }
}

public StudentFluent SomeMethod()
{
    return new StudentFluent().AddName("Jay Krishna Reddy").AddAge(25);
}

This makes so much sense and improves the readability on a whole new level, right? Another simpler example of method chaining is as follows:

public string AnotherMethod()
{
    string name = "Jay Krishna";
    return name
        .Replace("J", "A")
        .Replace("A", "J")
        .Replace(".", string.Empty);
}

Use Async/Await

Asynchronous programming is the way to go! Asynchronous Programming helps improve the overall efficiency while dealing with functions that can take some time to finish computing. During such function executions, the complete application may seem to be frozen to the end-user. This results in bad user experience. In such cases, we use async methods to free the main thread.

Read more here.

Don’t use ‘throw ex’ in the catch block

You really don’t want to just ‘throw ex’ the exception after catching it and lose the stack trace data. Just use ‘throw’. By using this, you are able to store the stack trace as well, which is kind of vital for diagnostics purposes.

Bad Practice

try
{
    // Do something...
}
catch (Exception ex)
{
    throw;
}

Good Practice

try
{
// Do something..
}
catch (Exception ex)
{
throw;
}

Use Ternary Operator

Consider the following example. I am sure that many of you still follow this practice.

public string SomeMethod(int value)
{
    if (value == 10)
    {
        return "Value is 10";
    }
    else
    {
        return "Value is not 10";
    }
}

But what if there is a better and cleaner way? Introducing Ternary Operators.

Now the multiple lines of code that we wrote earlier can be shrunk to just one line using the ternary operators. You can start imagining how many lines of code this is going to save!

public string SomeMethod(int value)
{
    return value == 10 ? "Value is 10" : "Value is not 10";
}

Use Null Coalescing Operator

Similarly, we have yet another operator that can come in handy while you do null checks. ?? This operator is known as Null Coalescing Operator in C#. Read more here.

Consider another example. Here is a small function that takes in a Student object as a parameter and checks for the null object. If null, return a new Object with data, or else return the same object.

public Student SomeMethod(Student student)
{
    if (student != null)
    {
        return student;
    }
    else
    {
        return new Student() { Name = "Jay Krishna Reddy" };
    }
}

Let’s add the operator and shrink this function too!

public Student SomeMethod(Student student)
{
    return student ?? new Student() { Name = "Jay Krishna Reddy" };
}

Prefer String Interpolation

Every time you want to add dynamic values to strings, we preferred composite formatting or simply adding them with a plus operator.

public string SomeMethod(Student student)
{
    return "Student Name is " + student.Name + ". Age is " + student.Age;
}

Starting with C# 6, the String Interpolation feature was introduced. This provides a more readable and cool syntax to create formatted strings. Here is how you use interpolated strings.

public string SomeMethod(Student student)
{
    return $"Student Name is {student.Name}. Age is {student.Age}";
}

Use Expression Bodied Methods

Such methods are used in scenarios where the method body is much smaller than even the method definition itself. Why waste Brackets and lines of code, right? Here is how you would write an Expression Bodied Method.

public string Message() => "Hello World!"; public

Read more about Expression Bodied Methods here.

Avoid Too Many Parameters

Too many parameters is always a nightmare. If you tend to have more than 3 parameter inputs to any method, why not wrap it into a request object or something and then pass? Let’s see a small example.

public Student SomeMethod(string name, string city, int age, string section, DateTime dateOfBirth)
{
    return new Student()
    {
        Age = age,
        Name = name,
        // Other parameters too
    };
}

You would probably want it to be like this. Get the idea?

public Student SomeMethod(Student student)
{
    return student;
}

Don’t Ignore Caught Errors

This is something I kept on doing. There is a good chance that many of you too do this. We add a try-catch block and just ignore the error handling, right? It’s a good practice to handle such errors and log them to a table or disk.

public void SomeMethod()
{
    try
    {
        DoSomething();
    }
    catch
    {
        // Handle exceptions here if needed
    }
}

public void SomeMethod()
{
    try
    {
        DoSomething();
    }
    catch (Exception ex)
    {
        LogItSomewhere(ex);
    }
}

If you found this article helpful.

Keep learning!


Similar Articles