๐ Introduction
We start our series with one of the most classic discussions about code styling in .NET: The usage of underscore prefix in private fields.
๐ The context
In c# classses, we have multiple type of memebers to define within a class: fields, properties, methods, constants...
The general rule has been always fo use camelCase for fields and variables names, and PascalCase for classes, methods and properties. However, in the case of private fields in a class scope, the usage of an underscore as a prefix has been followed since the beginning.
Among the years, the standards defined for this have been varying, both by the community and opinions in general, and by Microsoft in particular. Today, we can find it in Microsoft's general code style guides on naming identifiers, exposing the following:
Private instance fields start with an underscore (_).
However, at some point this was not the case, and the community has also sometimes followed its own guidelines and/or principles, as is natural in any healthy development environment/language/framework.
The arguments against of its usage argue the no real need to use them, as well as the use of this to differentiate the one defined in the global scope from that of the method scope, especially in their assignment in constructors, having then an unnecessary extra character at the beginning.
โญ My preference
I consider myself a developer with some traditions strongly stablished on me, even being a huge fan of all the new features and techniques coming with the c# language and the .NET framework, however, some of the rules that I have been following since the beginning of my career are still there, but there's only one rule: the tradition must have a reason to be.
Having said that, at this point you might have guessed that my preference is to use the underscore prefix in private fields, but why?
It highly important to remark, that at the end, following this rule or the other one will not make your code more robust, or more maintainable. So it's just a matter of preference thinking on how you organize your code and the way you interact with it and with the IDE for being more productive and automatic.
So, let's have a look on what do we have in case of having a class, in our example a Controller that is injecting a service on it. The code will be grossly simplified to focus on our aim:
public interface IUserService
{
//Contract methods
}
public class UserController
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
public void Create(UserCreationParams userCreationParams)
{
//Implementation goes here
}
}
In this case, using the underscore, I always expect to have the class scope services starting with an underscore. So given that, no matters the size of the class (In our example the UserController), If just start typing a _
I'll quickly have access to the class scope services, defined as private readonly
fields.
This also avoids the usage of this in the assignment during the initialization at the constructor.
So we have two advantages:
- Quick and concrete access to our private fields.
- Less characters typed to access: this are 4 characters while the underscore is simply one.
But perhaps you might thing, hey, you have to type an additional character out of the constructor since you don't need to differenciate it from the injected variable with the same name, and it's true, however in this case we can have less help distinguishing the services in our class from the method scope variables that starts with the same letter. In that case we will remove the underscore from the service, and then we will have this when IntelliSense prompts:
You are forced then to navigate through IntelliSense's suggestions to select the service.
But using this, does not completely help us to go directly to the service:
At this moment, we can also have some voices saying that, you can opt using private property, but let's check what the compiler generates:
- Original code:
public class UserController
{
private IUserService UserService { get; init; }
public UserController(IUserService userService)
{
UserService = userService;
}
public void Create(UserCreationParams userCreationParams)
{
//Implementation goes here
}
}
-
The compiler transform the above code as you see below:
[System.Runtime.CompilerServices.NullableContext(1)] [System.Runtime.CompilerServices.Nullable(0)] public class UserController { [CompilerGenerated] [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly IUserService <UserService>k__BackingField; private IUserService UserService { [CompilerGenerated] get { return <UserService>k__BackingField; } [CompilerGenerated] init { <UserService>k__BackingField = value; } } public UserController(IUserService userService) { UserService = userService; } public void Create(UserCreationParams userCreationParams) { } }
As we can see, the compiler generates extra code completely unnecessary for our need.
Once using again IntelliSense, it is even worse: it does not help us distinguish between our service easily since in this case it is not able to distinguish if we want a variable or a type:
Finally, in this case are not able to distinguish between properties that manage the state of the class, and our services:
๐ How to apply
I would like to start this last section of the post with a big disclaimer: This is just a matter of preference. The key aspect over this is to agree within the team the standard that makes everybody feel comfortable with, following and stick to it.
Having said that, I would like to share with you the way I apply this in my projects, using an .editorConfig file:
[*.cs]
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.prefix_underscore.capitalization = camel_case
dotnet_naming_style.prefix_underscore.required_prefix = _
I hope you liked this post, so please feel free to share your thoughts, opinions and/or experiences as well on the comments section below.
Happy coding! ๐ป
Top comments (1)
BTW, for those reading the post, sorry for the bad formatting of the code in the markdown... ๐
There must be some bug with the formatting since the markdown is properly done.
Already reported.