Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

C# Modifiers Use and Abuse – Part 1

2.78/5 (3 votes)
14 Nov 2018CPOL8 min read 4.5K  
Modifiers in C# - use and abuse

It’s been a while since my last post so I want to make it up by writing about something I noticed that happens in real life about the C# modifiers and my thoughts on them.

What are the modifiers we’re going to discuss? Well, the list of modifiers can be found on MSDN, which we will try to follow and combine more than just what the modifiers do but also how they can be encountered in the wild and user, or abused (yes, not using the modifiers at all still counts as abuse in this case) of them.

A small disclaimer, I’m a big fan of what I would like to call “restrictive programming” (do not know if the term has been used before somewhere by someone else). What I mean by that is, I prefer implementing the most restrictive designs (by using private, abstract, constant, read-only and we will see them shortly) so that myself and others are forced in the present and future to consider “Why?”. Why do we need something to be public or mutable before doing it and if there isn’t something we’re missing or a different usually cleaner approach.

First and the bane of my experience are the access modifiers. These are amongst the first things that you will find in most courses and books at the very beginning and it seems they are the most neglected ones. We’re going to take a look at them in reverse order that they are enumerated on the MSDN page, and from my point of view, how they should be applied.

Private

It’s applicable only to class members, that includes properties, fields, methods, event and nested types, and they are only accessible by the enclosing class. Sadly, I found myself in code-bases which this is under-used. In my opinion, all classes should start with all members as private as default until proven otherwise, this is why C# interprets a missing access modifier on a member as implicitly private, also for those who remember C++, private was the default access permission as well.

Benefits of Following the Approach

  1. You will need to give it though (ideally) before elevating the member to a higher access.
  2. Any change done will be kept locally so any dependent classes or workflow won’t need to be changed as well.
  3. Easy to leverage your IDE (in my case Visual Studio + Resharper) to find when something needs to be elevated or not from compilations errors, compilation errors are our friends just like exceptions.

Consequences of Not Following the Approach

  1. If it’s accessible, Intellisense will find it. That means that other team members, client or even your future self might fall into the trap of using a member of a class that should have been private only later to realize that changing it will require changes throughout the codebase. This also drags along time costs in the way of finding a way around the issue, introducing new bugs or just letting the code rot.

Another scenario I met which I want to share is this, we all know we should not pass too many parameters in a method (at most 3) because it’s hard to keep them in context and keep track of them (right?), so what most of us do (yes, I have been known to fall into this trap too) is to create a class that holds the parameters, the mythical “….Options” class. Keep in mind that that works for public or internal facing APIs but for private methods, a private nested class is actually cleaner if it’s only used in the context of the current class.

An additional point for which I think affects C# especially, are properties, I use them as well, but…When we were doing C++ where we had to manually define getters and setters, we used to give more consideration to the implementation, now because of properties, they started to be abused sometimes as a replacement for public fields, either on classes or even interfaces. Don’t get me wrong, POCO classes are great, but only for that, POCO classes, no methods not logic based on the state of the class. Also, consider using structs instead of classes if the required parameters are small and sparsely used.

Internal

This the default access to all classes and structs that are defined at namespace level, it’s basically the same as a public declaration, but they are only accessible from the inside of the project or assembly. I think all classes should start like that instead of public unless proven otherwise. Most classes that are used throughout the code-bases I encountered were declared as public.

Benefits of Following the Approach

  1. Having classes by default as internal ensures that a workflow set by you as the developer will be respected.
  2. Classes declared as internal can also benefit from unit testing just like public ones by adding an attribute at the assembly level which declares which other projects can access the classes (see InternalsVisibleTo attribute).
  3. It’s a good practice that even Microsoft uses in their own internal implementations. If you never decompiled a .NET Framework assembly or looked at the online source code for the .NET Framework, please do so and you will see layers upon layers of internal classes.
  4. You can change the implementation of an assembly and never break a public API because they are never exposed as such.

Consequences of Not Following the Approach

  1. Not having your implementation private by default exposes yourself to creating an API that might be used in a way that it wasn’t intended. Here’s an example, in one of the projects I worked on, there was a data access assembly that had everything public, that leads to a whole bunch of projects creating ad-hoc connections all over the place because there were no restrictions in place to force you as a developer to follow a structured workflow.

Here’s a tip I found while working on one of my personal projects, you can have a public facing API using factory classes (we will see more about this in Part 2 when we discuss abstract classes) and interfaces. So, in essence, you could present the user with an abstract class that cannot be instantiated but has a factory method that returns internal implementations of derivates from that base class, this gives you control over how the objects are instantiated, and if you want to expose some well-tested classes that might require more than one step to instantiate them, then you can enforce their instantiation according to your own steps, if someone wants to derive from those base classes or implement those public interfaces, then that’s their prerogative.

Protected

By applying this modifier, we enforce that the members or classes can only be used inside hierarchy chains.

As a personal opinion, this is the first step I look at when it comes to hierarchy chains before even considering a public approach. As before consider applying this restriction before going towards public virtual members (in the case of methods and properties).

Protected Internal

This is the best of both worlds and sadly, I rarely saw it in the wild. When applying this modifier, the member/class can only be used either in a hierarchy chair (think abstract classes) or internally.

Public

This modifier sadly is the one that is most encountered and the one that causes the most issues and headache for long term development, there is nothing implicitly wrong with having public classes and members, they are even necessary especially for library and framework development, but please consider carefully why it needs to be public. Instead of having a lot of public facing APIs, consider if some of the functionality can be encapsulated and thoroughly tested before defaulting to public.

Conclusion

Remember the pillars of OOP programming Abstraction, Encapsulation, Inheritance, and Polymorphism. Along the way, you will meet people with the “white box” mentality, especially with in-house projects, but also consider that even though you know how a microwave works and you can take it apart, you don’t want to solder the pieces part by part and cheer if it works only to find out you also soldered the table to the pieces as well.

The restrictive approach offers more benefits in the long run and here is a re-enumeration of some of them:

  • Easier to update and modify, especially in your own project, if it’s a public framework, good luck with supporting backward compatibility for something that shouldn’t be a feature to begin with.
  • Abstracting the logic in their own projects and hiding the implementations makes you reconsider making a call directly to the database or services instead of going through the proper workflow (imagine calling an Entity Framework directly from the business layer or the UI instead of through a repository abstraction, what happens when you switch from Entity Framework to something else? What was the purpose of the repository to begin with?).
  • Makes inheritance easier when you don’t have to worry if a specialized derivation of a base class is neatly packaged and only accessible through proper mechanisms.
  • Makes you always question a decision (hopefully!!!) before going full speed and changing something to public.

I know it’s been a long post and it kind of lost steam towards the end but I wanted to bring up the most important parts as soon as possible.

Thank you and in Part 2, we will be taking a look at some of the other modifiers that are not used nearly enough as they should, and remember just because it compiles and works doesn’t mean you want to move the whole table with your newly soldered microwave oven.

Cheers and see you next time (soon, I promise ☺).

License

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