Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#

C# Bad Practices: Learn How to Make a Good Code by Using Examples of Bad Code – Part 3

Rate me:
Please Sign up or sign in to vote.
4.71/5 (83 votes)
4 Jan 2017CPOL13 min read 123.7K   124   75
When you shouldn't use static classes?
In this article, I present code that creates some problems (in my opinion) because of using static classes and then I present my solution to solve these problems.

Previous Articles from this Series

This is the third article of my series: C# Bad Practices: Learn how to make a good code by using examples of bad code.

You can find the previous two articles by clicking on the links below:

To understand this article, you don't have to read any of my previous articles, but I encourage you to do that. :)

The Goal of the Article

I would like to show my opinion on a very interesting topic:

When You Shouldn't Use Static Classes?

In the article, I'm considering using static classes as a part of a business logic implementation.

I came across many different opinions on this topic so I decided to present my point of view on that.

The same as in previous articles in this series, I will present the code which is creating some problems (in my opinion) - because of using static classes and then I will present my solution for solving these problems.

What should the reader know before starting to read the article:

  • Basic knowledge of C# language
  • Basic knowledge of Dependency Injection design pattern
  • Basic knowledge of how IOC containers work
  • Basic knowledge of unit testing
  • Basic knowledge how mocking frameworks work

What is Static Class in C#?

Microsoft says:

A static class is basically the same as a non-static class, but there is one difference: a static class cannot be instantiated. In other words, you cannot use the new keyword to create a variable of the class type. Because there is no instance variable, you access the members of a static class by using the class name itself. For example, if you have a static class that is named UtilityClass that has a public method named MethodA, you call the method as shown in the following example:

C#
UtilityClass.MethodA();

And that's why many developers are using them very often. You don't need to create an instance of the class to use it, you just put a dot and voila, you can access its members. It's fast and clear and you save a time of object creating.

There are many rules for static classes, but I will not elaborate about them.

You can learn about them from the MSDN site:

MSDN

For this article, two rules of the static class are important:

  • static class is sealed – you cannot inherit from it
  • static class cannot implement an interface

Let's Go to the Code

Image 1

The example below was created only to present the problem on a concrete code. Sending e-mails is not related to the problem described by this article, it's only showing a usage of static classes in a wrong way (IN MY OPINION :)).

Here is the code which I want to set as an example:

C#
public class EmailManager
{
  public bool SendEmail(int emailId)
  {
    if (emailId <= 0)
    {
      Logger.Error("Incorrect emailId: " + emailId);
      return false;
    }

    EmailData emailData = DatabaseManager.GetEmailData(emailId);
    if (emailData == null)     
    {       
        Logger.Error("Email data is null (emailId: " + emailId + ")");       
        return false;     
    }

    EmailMessage emailMessage = EmailMessageCreator.CreateEmailMessage(emailData);

    if (!ValidateEmailMessage(emailMessage))
    {
      Logger.Error("Email message is not valid (emailId: " + emailId + ")");
      return false;
    }
 
    try
    {
      EmailSender.SendEmail(emailMessage);
      Logger.Info("Email was sent!");
    }
    catch(/* catch only exceptions, which you know that may occur and you can't 
    do anything to avid them, like: network related error, addressee server refused... etc.*/)
    {
      Logger.Exception(ex.ToString());
      return false;
    }

    return true;
  }
 
  private bool ValidateEmailMessage(EmailMessage emailMessage)
  {
    if(emailMessage == null) return false;

    if (string.IsNullOrEmpty(emailMessage.From) || string.IsNullOrEmpty(emailMessage.To) || 
    string.IsNullOrEmpty(emailMessage.Subject) || string.IsNullOrEmpty(emailMessage.Body)) 
    return false;
    
    if (emailMessage.Subject.Length > 255) return false;
    
    return true;
  }
}

Let's imagine that we have a component, which is sending e-mail messages. It is taking a message containing a message identificator from a queue (doesn't matter which implementation, might be MSMQ, RabbitMQ, etc.), creating an e-mail message and sending it to Addressee.

We have here manager class which takes as a parameter identificator of stored in the database message (messageId). It has its own logic like:

  • guard clause
  • validation of the message returned by EmailMessageCreator – it is divided to a private method – I'm treating it as a part of the SendEmail method of EmailManager class
  • managing program flow
  • error handling, as while sending e-mails, you can come across many known errors like:
    • network related error, addressee server refused... etc.

and is also calling a logic from another components like:

  • DatabaseManager static class – a component responsible for getting message information from DB by messageId
  • EmailMessageCreator static class – a component responsible for creating email message to send
  • EmailSender static class – a component responsible for sending a message to Addressee via SMTP
  • Logger static class – a component responsible for logging an information in a log file

To simplify a situation, assume that all the above classes are working like a services and there are no racing conditions here for their members.

It looks simple and clear. It's working, it's fast and we can easily, quickly implement it.

So what's wrong with this code??!!

I see a few problems in it..

Issues

Unit Testing

Imagine now that you want to write unit tests for EmailManager class, so you want to test own logic of SendEmail method: like:

  • check what value is returned if:
    • input is incorrect
    • CreateEmailMessage will return invalid data
  • check if exception is thrown by tested method if:
    • CreateEmailMessage method throws an exception
    • GetEmailData method throws an exception
    • SendEmail method from EmailSender class throws an exception
    • check if correct method of the Logger was called with proper parameters, in case:
      • input is incorrect
      • SendEmail method from EmailSender class has thrown exception
      • email was sent properly

This behavior should not change even if related classes will change their behavior.

IMPORTANT: Remember that unit test IS NOT an integration test. It should test only isolated piece of code. If there are relations to other pieces of code, then they should be replaced by mocks, stubs etc.

You cannot unit test this class as there are hard dependencies to another classes like:

  • DatabaseManager
  • EmailMessageCreator
  • EmailSender
  • Logger

Image 2

What's the problem with that?

While testing SendEmail, you will be testing their code as well. It will be integration test. In simple words, a bug in for example CreateEmailMessage should not affect a test result of the SendEmail method.

IMPORTANT: I don't want to say that we only need unit tests. Integration tests are also important and we should have them both, but we should separate them clearly. Why? Because all Unit Tests should always pass! Integration tests usually need some additional configuration, like fake database, fake SMTP server, etc.

Furthermore, while testing own code of SendEmail method from EmailSender class, you don't want to call the database and send real e-mail as you're not testing them and unit test has to be fast!

Another thing is that you're not able to check in your test, if the Logger class was called or not, because you're not able to mock the Logger class.

IMPORTANT

After Wonde Tadesse comment, I can agree that you can use Microsoft Fakes framework, to unit test SendEmail method from EmailSender class(version with dependencies to static classes). But:

  • (in the original version with dependencies to static classes) you still violate Dependency Inversion principle from SOLID principles, so this is a bad practice - EmailManager decide which implementation of related class to take.
  • This feature is only available with the highest versions of Visual Studio (Premium, Ultimate or Enterprise). Your company is dependent on a concrete version of Visual Studio now. Is it a good practice?

    Even if your company has the highest version of VS bought for every developer (very rare, and unlikely), what if the company will decide then to downgrade version of Visual Studio to Professional? Your tests will fail and you will have to refactor your code anyway. Great practice?

Future Changes

Image 3

Imagine now that after some period of time, EmailSender class needs a different behavior of the EmailMessageCreator but just a little bit. So you just want to change 10% of current EmailMessageCreator code and the rest can stay as is.

But at the same time, you don't want to break unit tests written for this class and you want to follow Open/Closed Principle from SOLID principles.

IMPORTANT

Wikipedia says about Open/Closed Principle:

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

We want to keep both EmailMessageCreator and EmailMessageCreatorExtended classes, also because there is another consumer of EmailMessageCreator which want to keep using its old version without modification.

We also don't want to touch the caller, in our example - EmailManager class.

Unfortunately, we can't achieve that, because:

  • if we want to keep the current implementation of EmailMessageCreator and only extend it, the best choice would be to create a class EmailMessageCreatorExtended (forgive me for this name, it's only to differentiate them) which will inherit from the EmailMessageCreator class. We can't do it, as static class are sealed so you can't inherit from them.
  • You also cannot replace the implementation of EmailMessageCreator class without changing the caller, as static classes cannot implement an interface. So even if you are injecting EmailMessageCreator via constructor of EmailManager class, to change it to use another static class like EmailMessageCreatorVersion2 (forgive me for this name, it's only to differentiate them) you will have to change the code of the caller.

How to Fix That??!!

Well, the solution is VERY SIMPLE.

Image 4

First Step

Just remove static from the following classes:

  • DatabaseManager
  • EmailMessageCreator
  • EmailSender
  • Logger

and from their methods!

Second Step

Create an interface for each of the static classes:

  • IDatabaseManager
  • IEmailMessageCreator
  • IEmailSender
  • ILogger

and make them implement those interfaces.

Third Step

Change implementation of the EmailManager class as below:

C#
public class EmailManager
{
  private readonly IDatabaseManager _databaseManager;
  private readonly IEmailMessageCreator _emailMessageCreator;
  private readonly IEmailSender _emailSender;
  private readonly ILogger _logger;
  public EmailManager(IDatabaseManager databaseManager, 
  IEmailMessageCreator emailMessageCreator, IEmailSender emailSender, ILogger logger)
  {
    _databaseManager = databaseManager;
    _emailMessageCreator = emailMessageCreator;
    _emailSender = emailSender;
    _logger = logger;
  }
 
  public bool SendEmail(int emailId)
  {
    if (emailId <= 0)
    {
      _logger.Error("Incorrect emailId: " + emailId);
      return false;
    }

    EmailData emailData = _databaseManager.GetEmailData(emailId);
    if (emailData == null)     
    {       
      _logger.Error("Email data is null (emailId: " + emailId + ")");       
      return false;     
    }

    EmailMessage emailMessage = _emailMessageCreator.CreateEmailMessage(emailData);
 
    if (!ValidateEmailMessage(emailMessage))
    {
      _logger.Error("Email message is not valid (emailId: " + emailId + ")");
      return false;
    }
 
    try
    {
      _emailSender.SendEmail(emailMessage);
      _logger.Info("Email was sent!");
    }
    catch (/* catch only exceptions, which you know that may occur and you can't 
    do anything to avid them, like: network related error, addressee server refused... etc.*/)
    {
      _logger.Exception(ex.ToString());
      return false;
    }

    return true;
  }
 
  private bool ValidateEmailMessage(EmailMessage emailMessage)
  {
    if(emailMessage == null) return false;

    if (string.IsNullOrEmpty(emailMessage.From) || string.IsNullOrEmpty(emailMessage.To) || 
    string.IsNullOrEmpty(emailMessage.Subject) || string.IsNullOrEmpty(emailMessage.Body)) 
    return false;
    
    if (emailMessage.Subject.Length > 255) return false;

    return true;
  }
}

Fourth Step

Now you can set up one of the IOC containers like Ninject or Autofac for your project, and just bind the above implementations to adequate interface, like:

DatabaseManager to IDatabaseManager

in your IOC container configuration.

Here is, for example, a wiki page which describe how to create a configuration for Ninject:

IOC container will inject then proper implementations into the EmailManager class.

You can inject implementations to EmailManager class manually as well.

And voila, we've got it!

Image 5

What Did We Gain After This Change?

Unit Testing Problem - SOLVED

So, do you remember our first problem with unit testing? Now we can unit test our EmailManager class without any problems. We can inject into it any implementation we want, mock, stub, etc.

We can use mocking framework like Moq, to mock classes:

  • DatabaseManager – then, for the GetEmailData method we can:
    • bypass connecting to the database,
    • return whatever value we want from memory,
    • simulate throwing exception – to check how our SendEmail method from EmailManager class will behave,
  • EmailMessageCreator – then, for the CreateEmailMessage method we can:
    • return whatever value we want – to check how our SendEmail method from EmailManager class will behave
    • simulate throwing exception – to check how our SendEmail method from EmailManager class will behave
  • EmailSender – then, for its SendEmail method we can:
    • bypass sending emails
    • return whatever value we want – to check how our SendEmail method from EmailManager class will behave
    • simulate throwing exception – to check how our SendEmail method from EmailManager class will behave
  • Logger – then, for its Info, Error and Exception methods, we can:
    • check if they were called and if yes, with what parameters – it will allow us to check if the information about event or that exception occurred is logged or not.

I will not present an API of Moq library as it isn't the main topic of this article, and it may be a topic for the separate article. It would distract from the main topic of the article which is usage of the static classes. All you need to implement above mocks in your unit tests is mentioned here:

Future Changes Problem - SOLVED

The next problem we had was a lack of ability to extend static classes and a lack of ability to change implementation of related classes without changing the consumer code.

Do we still have these issues in our new code?

NO.

Why?

Because, thanks to programming to abstraction (introducing interfaces), dependency injection and removing static from related classes, we can now:

  • extend functionality of related classes using inheritance
  • replace implementation of related classes in the caller (SendEmail method of EmailManager class) in IOC container configuration without touching the caller at all

Static Classes Will Be faster...

How could I not say about the difference between those two solutions in case of performance?

People who are implementing solutions similar to example with static classes given in the article will say:

Static classes will be faster as while using them, you don't have to create an instance of the class on every call to its method”.

But...

In the solution proposed by myself, do we really need to create an instance of the class every time we want to use a method from it?

Obviously NO.

We can just create an object at the application start and treat it as a singleton. It can be very easily achieved by configuration of IOC container. In all of its implementations like Ninject, Autofac, etc. you can set an object lifecycle to a singleton and that's it. Everytime, we will want to access the implementation of the interface, IOC container will return the same object.

Here, you can find an information about object lifecycles in Ninject:

It's extremely easy as you will have to only add one method call to each Interface binding:

C#
.InSingletonScope()

But if we don't want to use any ready to use implementations of IOC container, we can implement singleton by ourselves.

To be 100% honest, there is one other factor, which is the fact that we replaced also static method to instance methods. Does it change anything? Let's check what Microsoft says about it:

Microsoft says:

A call to a static method generates a call instruction in Microsoft intermediate language (MSIL), whereas a call to an instance method generates a callvirt instruction, which also checks for a null object references. However, most of the time the performance difference between the two is not significant.

In my opinion, unless there we have a huge performance problem, we shouldn't care about it. We are gaining much for a very little (very likely invisible) performance decrease.

Where Should We Use Static Classes Then?

Image 6

So should we resign from static classes at all? Of course NO.

In my opinion, we can implement a business using static class, if:

  • we don't care about unit tests – don't say it loudly :) and we are sure that we will not modify the code – it is the final version and it won't be extended

OR

  • it is extremely simple project – only few classes and we want to keep it as simple as possible.

OR

  • you're implementing an extremely simple tool which has no reason to be modified or extended and has no side-effects, like modification of state or exception throwing - in fact, it's extremely hard to find this kind of situation in a real world. For example, almost always you will have to validate an input and often throw an exception if it is invalid.
    IMPORTANT: In case of built in framework static classes containing some basic tools, like System.Math, etc.:

    I'm treating them as an exception to the rule. I'm thinking about them as built in language instructions. They are tested properly by Microsoft, they won't be modified, so it's completely acceptable to use them in my opinion.

We can also use static classes to implement constants for reference projects. For example:

C#
public class Point
{
  public Point (int x, int y)
  {
    X = x;
    Y = y;
  }
  public int X{get;private set;}
  public int Y{get;private set;}
}
 
public static class Constants
{
  public static Point StartPoint = new Point(0, 0);
}

Conclusion

I'm always trying to create a code as flexible as possible. If you will implement suggested by myself, “new” solution instead of using static classes, you won't lose anything (if we skip a very little, probably invisible performance decrease) and at the same time you can gain much, like unit testable code and flexibility for the future changes. This ability will be extremely beneficial for the huge, long-term projects.

So to sum up, I would say:

“Avoid using static classes while implementing a business logic of the application unless there is a strong reason for using them!”.

Thanks!

If you have any questions related to the article, don't hesitate to contact me!

Sources

Used in the article information:

Used in the article images:

History

  • 12th December, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Ireland Ireland
Passionate, Microsoft Certified Software Developer(Senior),
having Masters Degree in Information Technology.

Comments and Discussions

 
GeneralRe: Few Points Pin
Radosław Sadowski17-Dec-16 0:24
Radosław Sadowski17-Dec-16 0:24 
GeneralRe: Few Points PinPopular
Wonde Tadesse17-Dec-16 4:31
professionalWonde Tadesse17-Dec-16 4:31 
GeneralRe: Few Points Pin
Radosław Sadowski17-Dec-16 7:54
Radosław Sadowski17-Dec-16 7:54 
GeneralRe: Few Points PinPopular
Wonde Tadesse17-Dec-16 12:34
professionalWonde Tadesse17-Dec-16 12:34 
GeneralRe: Few Points Pin
Radosław Sadowski17-Dec-16 22:29
Radosław Sadowski17-Dec-16 22:29 
GeneralRe: Few Points PinPopular
Wonde Tadesse18-Dec-16 3:52
professionalWonde Tadesse18-Dec-16 3:52 
GeneralRe: Few Points Pin
Radosław Sadowski18-Dec-16 4:36
Radosław Sadowski18-Dec-16 4:36 
GeneralRe: Few Points PinPopular
Wonde Tadesse18-Dec-16 6:07
professionalWonde Tadesse18-Dec-16 6:07 
Quote:
I know that you're trying so hard to prove that what you say makes sense
Not at all. You introduced possible bugs once you refactored the original EmailManager class. These changes will affect callers of this class.
Quote:
checking if instance is null and then throwing exception - your code is useless.
Hahaha. And that is why ArgumentNullException[^] is created. To use it appropriately in situation like this.
Quote:
Maybe you should override this rule
I did. He talks, I sample(C#). Wink | ;)
Wonde Tadesse

GeneralMy vote of 5 Pin
Hareesh Gadudas15-Dec-16 22:04
Hareesh Gadudas15-Dec-16 22:04 
GeneralRe: My vote of 5 Pin
Radosław Sadowski15-Dec-16 22:18
Radosław Sadowski15-Dec-16 22:18 
GeneralIOC containers were not the rescue to performance, but the enemy Pin
wmjordan15-Dec-16 12:07
professionalwmjordan15-Dec-16 12:07 
GeneralRe: IOC containers were not the rescue to performance, but the enemy Pin
Radosław Sadowski15-Dec-16 19:07
Radosław Sadowski15-Dec-16 19:07 
GeneralRe: IOC containers were not the rescue to performance, but the enemy Pin
wmjordan16-Dec-16 12:20
professionalwmjordan16-Dec-16 12:20 
GeneralRe: IOC containers were not the rescue to performance, but the enemy Pin
Radosław Sadowski17-Dec-16 1:36
Radosław Sadowski17-Dec-16 1:36 
GeneralRe: IOC containers were not the rescue to performance, but the enemy Pin
wmjordan17-Dec-16 21:01
professionalwmjordan17-Dec-16 21:01 
GeneralRe: IOC containers were not the rescue to performance, but the enemy Pin
Radosław Sadowski17-Dec-16 21:41
Radosław Sadowski17-Dec-16 21:41 
SuggestionRemove this line Pin
Clifford Nelson15-Dec-16 10:46
Clifford Nelson15-Dec-16 10:46 
GeneralRe: Remove this line Pin
Radosław Sadowski15-Dec-16 19:40
Radosław Sadowski15-Dec-16 19:40 
QuestionI would argue... Pin
Marc Clifton15-Dec-16 2:22
mvaMarc Clifton15-Dec-16 2:22 
AnswerRe: I would argue... Pin
Radosław Sadowski15-Dec-16 3:09
Radosław Sadowski15-Dec-16 3:09 
GeneralRe: I would argue... Pin
Marc Clifton15-Dec-16 7:43
mvaMarc Clifton15-Dec-16 7:43 
GeneralRe: I would argue... Pin
wmjordan15-Dec-16 11:46
professionalwmjordan15-Dec-16 11:46 
GeneralRe: I would argue... Pin
Marc Clifton15-Dec-16 12:27
mvaMarc Clifton15-Dec-16 12:27 
GeneralRe: I would argue... Pin
Radosław Sadowski15-Dec-16 19:36
Radosław Sadowski15-Dec-16 19:36 
GeneralRe: I would argue... Pin
Marc Clifton16-Dec-16 2:11
mvaMarc Clifton16-Dec-16 2:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.