PROGRAMMING

Let's refactor C# code — 2

Continue flowing SOLID Principles

Shiljo Paulson
3 min readOct 13, 2024

In the previous blog post we discussed the importance of readability, maintainability, testability, and robustness against future changes and future potential bugs. Let's discuss what else can be done so that it is more enterprise ready.

In case if you have not read my previous blog post, please have a look at https://shiljopaulson.medium.com/lets-refactor-c-code-cce70446a093

The Code

Here is the refactored code from the previous blog post & we will see what further refactoring can be done on it.

using System;
using System.Collections.Generic;

namespace RefactoredCodeExample
{
class Program
{
static void Main(string[] args)
{
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
print(numbers);
}

static void print(int[] numbers)
{
var numberAnalyser = new NumberAnalyser();
foreach (int number in numbers)
{
Console.WriteLine(numberAnalyser.GetEvenOddMessage(number));
}
}
}

public class NumberAnalyser
{
public string GetEvenOddMessage(int number)
{
return number % 2 == 0
? $"Number is even: {number}"
: $"Number is odd: {number}";
}
}
}

What does the code do?

  • It has a int[] and it is initialized.
  • It invokes print method and passes the List as arguments.
  • Inside print method it creates instance for NumberAnalyser class, and it uses foreach-loop it iterates over by invoking GetEvenOddMessage method and prints the string in the console.
  • Inside GetEvenOddMessage method it checks if number is even or odd, and it is going to print “Number is even: item” else “Number is odd: item”.

Issues with the code

  • Incase if you want to change the implementation NumberAnalyser class to something else it will be difficult because instance creation is happening in the middle of the code. What if multiple instances are created at various parts of the project? To overcome this, we need to introduce dependency injection.

Round 1 — Refactored code

We need to introduce the following

  • Interface
  • Dependency Injection mechanism.

yes, there is going to be some extra lines of code 😎.

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;

namespace RefactoredCodeExample
{
public interface INumberAnalyzer
{
string GetEvenOddMessage(int number);
}

public class NumberAnalyzer : INumberAnalyzer
{
public string GetEvenOddMessage(int number)
{
return number % 2 == 0
? $"Number is even: {number}"
: $"Number is odd: {number}";
}
}

class Program
{
private readonly INumberAnalyzer _analyzer;

public Program(INumberAnalyzer analyzer)
{
_analyzer = analyzer;
}

static void Main(string[] args)
{
// Set up DI
var serviceProvider = new ServiceCollection()
.AddSingleton<INumberAnalyzer, NumberAnalyzer>()
.AddSingleton<Program>()
.BuildServiceProvider();

// Resolve and run the program
var program = serviceProvider.GetService<Program>();
program.Run();
}

public void Run()
{
int[] numbers = new int[] { 1, 2, 3, 4, 5 };

foreach (int number in numbers)
{
Console.WriteLine(_analyzer.GetEvenOddMessage(number));
}
}
}
}

As discussed, we have introduced a new interface INumberAnalyzer and it is being inherited by the implementation class NumberAnalyzer.

So why do you want interface?

If you notice in the Run method the variable _analyzer which is an interface is being invoked & not the NumberAnalyzer class. So, in future if you want to introduce a new implementation class say NumberAnalyzer2 class. It can be changed without many code changes.

Basically, need to change

.AddSingleton<INumberAnalyzer, NumberAnalyzer>()

to

.AddSingleton<INumberAnalyzer, NumberAnalyzer2>()

Also, the interface helps you to mock INumberAnalyzer using mocking packages. Some of the known packages are Moq & NSubstitute.

We can discuss more about mocking on another upcoming blog post.

Round 2 — Refactored Code

I felt the code is bit difficult to read hence refactoring further

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;

namespace RefactoredCodeExample
{
public interface INumberAnalyzer
{
string GetEvenOddMessage(int number);
}

public class NumberAnalyzer : INumberAnalyzer
{
public string GetEvenOddMessage(int number)
{
return number % 2 == 0
? $"Number is even: {number}"
: $"Number is odd: {number}";
}
}

public interface INumberProcessor
{
void Process(int[] numbers);
}

public class NumberProcessor : INumberProcessor
{
private readonly INumberAnalyzer _analyzer;

public NumberProcessor(INumberAnalyzer analyzer)
{
_analyzer = analyzer;
}

public void Process(int[] numbers)
{
foreach (int number in numbers)
{
string message = _analyzer.GetEvenOddMessage(number);
Console.WriteLine(message);
}
}
}

class Program
{
static void Main(string[] args)
{
// Set up DI
var serviceProvider = new ServiceCollection()
.AddSingleton<INumberAnalyzer, NumberAnalyzer>()
.AddSingleton<INumberProcessor, NumberProcessor>()
.BuildServiceProvider();

// Resolve dependency
var _processor = serviceProvider.GetService<INumberProcessor>();
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
_processor.Process(numbers);
}
}
}

Here is what I have done to the code

  • Have introduced a class NumberProcessor and interface INumberProcessor which will remove the clutter from the Program class’s Run method & it is not its responsibility to print it in the console.
  • Accordingly added NumberProcessor & INumberProcessor to the dependency injection.

Let me know your valuable feedback and please clap and subscribe 😊.

--

--

Shiljo Paulson
Shiljo Paulson

Written by Shiljo Paulson

System Design, Full stack Developer, good at OOPs, .Net, C#, TypeScript, JavaScript, SQL, HTML. Recent times interest is on Cloud, System Design and GoLang.

No responses yet