I will go on record as saying I have gotten a lot of value out of StyleCop. It trained me to write code in a very particular but consistent way, so that I don’t have to spend cycles thinking about where class members should go, how to format braces, and so on. Since the advent of StyleCop, however, a lot has changed in the state of the art of code styling in .NET. For example, we now have EditorConfig with full integration in Visual Studio as well as
dotnet build / MSBuild.
Does that mean StyleCop is obsolete? Well, not exactly. The existence and continued development of StyleCopAnalyzers shows that it is still an active, viable technology. I have used this incarnation of StyleCop and would recommend it to anyone who still prefers “the StyleCop way” on modern projects.
That being said, the .NET team — i.e., those who actually build and maintain .NET as a platform — has never really embraced StyleCop. The C# coding style adopted within the .NET Runtime itself has many differences from the StyleCop mandates. Let’s go through a few of these differences along with plausible explanations for why these style choices are defensible.
StyleCop says always use
this when accessing instance members. The .NET Runtime says never, unless absolutely necessary. Who is right? This rule is surprisingly controversial, and my anecdotal evidence suggests that most people don’t like overuse of
this. The best argument in favor of
this is consistency and clarity. But we can probably get by without it if we also adopt the .NET Runtime rule of prefixing instance fields with underscore, e.g.
_myVal = myVal. Now we have a clear delineation between instance data and locals or parameters and
_ serves as a shorter substitute for
this. We do however lose the ability to tell the difference between static and instance properties and methods, absent other context. But on the plus side, if we change whole code paths from instance to static members, the refactoring pain is minimal (since we don’t have to delete a bunch of
StyleCop says to put
using directives inside the namespace, whereas .NET says to do so outside. The StyleCop justification is documented in the corresponding rule SA1200 — basically that outer namespace directives can lead to ambiguity when there are type name clashes. But wait, the C# Coding Conventions seem to argue the opposite, that a “
using directive placed inside a namespace is context-sensitive and complicates name resolution.” Despite having written thousands of
usings in the StyleCop way, I actually side with the .NET Runtime here. Unless we’re willing to put
global:: in front of every namespace, it does seem clearer to just do it outside.
Order of members
StyleCop is rigid here — public, protected, private; fields before constructors before properties, etc. The .NET Runtime is mostly silent here, other than to recommend that fields “should be specified at the top.” I did like the StyleCop way because it was absolutely consistent and left little room for debate or choice. However, you (and Emerson) could say this is “a foolish consistency.” If we value cohesion then maybe we would want things that are related to be close together, even if the related items are of different visibilities (say, a large public method whose parts have been extracted into nearby private methods).
Alas, EditorConfig doesn’t even support element ordering. So this style rule may be moot in the modern world.
One type per file
This may be the most controversial style rule. Java was one of the first to mandate one (public) type per file. On the surface, the reasoning would seem to be one of tidiness, that it is simply easier to tell where things are in a large project if the file
MyClass.java has one class called
MyClass. But the actual history seems to be more practical:
“[I]t’s necessary for efficient package importation.” […] the compiler would have to make an additional pass through all the compilation units (.java files) to figure out what classes were where, and that would make the compilation even slower.
Given that we have better compilers now, what is the use of the rule? The consensus, if there is any, is that you should not have “too many” types in the same file. This is as true for C/C++ as it is for Rust.
So, then, how many types per file? StyleCop would say “exactly one” is the right answer here. The .NET folks do not specify a hard and fast rule. Most of the time you will see one type per file, but there are notable exceptions especially around extension methods where partial classes are favored (see: LINQ) and with delegate types (see: Action). But as with element ordering, this rule anyway cannot be enforced with EditorConfig alone.
We’re on .NET 7 now, and heading toward .NET 8. The world is different from the .NET Framework 2.X world in which StyleCop was born. I will personally start phasing out my use of StyleCop in favor of the more open and cross-language applicability of EditorConfig. My new code samples repo writeasync2 will reflect this choice going forward.