I love dotnet. I've been happily using it since the betas and even get to teach it professionally as my day job. However, dotnet has 20+ years of baggage for new learners and I'd like to try improving that experience for those coming into the field using Visual Studio features that are already present.
In this piece, I'll introduce a set of setting choices that takes aim at some default values, hides entire language features, and probably makes me a few enemies. I'll also provide an .editorconfig
file to make it easier to include these settings in your projects in the future.
This content is also available on YouTube.
The DotNet Learning Curve
I love dotnet, but every class I teach is a firm reminder that dotnet and Visual Studio are not as beginner-friendly as I wish it was.
Todays learners are learning the same syntax, concepts, and tooling that I learned two decades ago. However, these students also have 20+ years of new language features to learn. When learners search they get code snippets that contain a wide degree language features such as null-coalescing, nullable values, lambda expressions, pattern matching, records, and more.
While it's good for new learners to discover these language features and grow into these concepts, it is not good for them to be drowning in new syntax while still trying to internalize how to write a for loop.
This is something that I believe a set of consistent language rules for new learners could help focus the efforts of new learners onto the core of the language.
Beginner-Friendly Visual Studio Settings
Let's go over the individual choices I recommend for a more beginner-friendly C# learning experience.
To get to these choices, you'll need to open up Visual Studio and then select the Tools menu. From there, click Options... at the bottom.
Once the options dialog opens, expand Text Editor on the left sidebar, then find C# and expand it. Finally, select Code Style or the General tab inside of Code Style. This will give you a long list of rules.
We'll go over each group of rules and my recommendations for them in the following sections.
"This" Preferences
The four this
settings govern the use of the this
keyword in C#.
As a new dotnet developer I benefitted significantly from using the this
keyword to give me good IntelliSense suggestions on methods and properties I was inheriting.
I now teach dotnet professionally and have seen many students be more confused by the unnecessary presence of the this
keyword than helped by its functionality.
As a result, my policy is to recommend students use this
only in constructors to protect them against mistakes like the following code:
public class Car { private string color; public Car(string color) { color = color; } }
The code above is incorrect because color = color
refers to the same parameter on the left and the right. In this case this.color = color;
is the correct form of the code.
Visual Studio doesn't give us an option to require this
in constructors only, so my recommendations here are simply to leave all four this
preference settings as "Do not prefer 'this.'" and leave them as refactoring suggestions.

However, I could certainly entertain arguments that "Qualified field access" could be set to "Prefer 'this.'".
Predefined Type Preferences
The predefined type preferences are a minor detail focusing on whether you should use common types such as int
or their more formal Int32
type names.
Both ways the same thing and work, but it is far more common to see the shorthand types in dotnet code.
Because of this, I recommend preferring predefined types and treating deviations from this as warnings since they will actively distract new devs.

Var Preferences
The var
keyword in C# is often divisive for teams. My old stance on var
was to tolerate it only for complex type initialization such as the following code:
var playerGoals = new Dictionary<string, int>();
However, with C#'s target-typed new keyword this use case is no longer my recommendation and I instead recommend the following code:
Dictionary<string, int> playerGoals = new();
You can read my article on target-typed new for more details as to my reasoning, but it boils down to this: var
is distracting and takes a moment to mentally determine the type. Let's be nice to newer developers and not hide types behind var
keywords if we can help it.
There's an argument to be made for typing less when creating code, but the target-typed new keyword satisfies this need nowadays.
As a result, I advocate for preferring against var
and warning when it is present.

Code Block Preferences
Code block preferences focus on a few different aspects of C# code blocks.
To simplify the discussion for those skimming this, here's my table of recommendations:

Let's discuss each one of these a bit more.
Prefer Braces
Prefer braces governs whether you should include { }
scopes around single line statements.
For example, the following C# code is legal:
if (amount < 0) Console.WriteLine("You put in a negative number");
I have seen many students discover that the { }
scopes are optional around blocks of code and try to omit them (less code must be better, right?) only to be confused by their own program later on.
Whitespace and indentation are not syntactically meaningful in C#, and this is one of those early areas that new learners frequently get lost at sea.
By making Prefer braces set to Yes in all cases and warning when it is not, we help flag this behavior earlier on to help learners avoid troubled waters.
Namespace Definitions
Dotnet now gives us the ability to use a scope-free namespace declarations that are scoped to files. It's a newer feature and I rather like it. However, historical code and examples more commonly use the { }
scoped notation for namespaces.
My suspicion is that developers will ultimately have a better experience with file-scoped namespace definitions due to the reduced nesting level in the file making it less likely that learners will get lost in the indentation.
For consistency purposes, I'd recommend sticking with the old format until you make all of your code follow the new style at once, then switching over to the file-scoped option. However, I would not make this anything more than a refactoring suggestion.
Prefer auto properties
Auto-properties are a wonderful thing in dotnet that saved everyone a lot of time when they came around early on in C#'s history.
While properties are one of the more difficult aspects of learning the basics of dotnet classes, auto properties make for brief, readable, and maintainable code and also prevent common mistakes.
I frequently see new learners try to use manual properties incorrectly like the following:
public class Snack { private string name; public string Name { get { return Name; } set { Name = value; } } }
To a new learner, this code looks reasonable, but both the getter and setter for Name
contain an infinite loop since both are using the name of the property instead of the name of the backing field.
New learners are going to find this and it really is a bit of a rite of passage, but encountering stack overflow errors repeatedly distracts from the other learning attempts they're making. Trust me: they will eventually encounter stack overflows without the language making it easy for them.
My policy is to always prefer auto-properties and suggest them to developers, but do not warn.
Prefer simple 'using' statements
C# now gives us a simple "inferred scope" using statement that doesn't need the { }
scope notation.
However, I don't like this simplified using
statement for governing resource disposal, because it simplifies the scope of the resource too much.
Using the old syntax, it was clear where the IDisposable
would be disposed:
using (MyDisposableClass disposable = new MyDisposableClass()) { disposable.DoSomething(); }
The familiar { }
scope makes the resource disposal easier for learners to understand, which helps illustrate the point of the using
statement.
On the flipside, the using MyDisposableClass disposable = new MyDisposableClass()
syntax looks too much like a variable declaration that a new learner might neglect the importance of the using
keyword and omit it.
As a result, I recommend against simple using
statements and warning the user when they are used.
Prefer 'System.HashCode' in 'GetHashCode'
Your new learners are likely not overriding GetHashCode
, but if they do, the System.HashCode
option is a great way of avoiding beginner hash code mistakes and should be preferred at a suggestion or warning level.
Prefer method group conversion
In scenarios where you can provide a lambda expression, there are times when you are just calling a method directly and passing along an argument. For example: myList.Select(item => CalculateValue(item))
.
In these cases, the code can be simplified down instead to myList.Select(CalculateValue)
.
As an experienced developer, I like this. As a teacher of newer developers, I don't like this.
Newer developers are usually still getting used to lambda functions with "fat arrow" notation. When this is the case, repetition and following the same rules usually helps reinforce concepts.
Using method groups is a "special case" of a lambda expression and complicates that learning process. For this reason I recommend preferring lambda expressions, but do not recommend using anything more than a refactoring hint to reinforce this.
Prefer top-level statements
Top-level statements are a newer feature of C# and to my mind were actually built to support simplified scenarios for teaching C#.
As you might guess, I'm all in favor of supporting new people learning C#, so I agree with preferring top-level statements, but leaving this as a refactoring hint is fine.
Parentheses Preference
I am extremely opinionated when it comes to parentheses in code.
I see a high number of errors from new developers who misunderstand parentheses and do not include them when they should. This is particularly important when mixing &&
and ||
in the same line, because these never work the way a new developer's intuition says they ought to.
As a result, I advocate for always including parentheses for clarity at the warning level, because it really can be that much of an issue for new developers.

I am far more willing to tolerate excessive parentheses for added clarity or safety than I am willing to tolerate the bugs we see from not using parentheses.
Expression Preference
There are far too many things in expression preference to go over individually here, so here's my full list of settings:

Now, let's talk about some high-level themes and noteworthy recommendations.
I recommend not preferring the object or collection initializers - at least at first. Constructors are a confusing topic, but an important one. It is healthy for new learners to thing about initialization as going through the constructor when just starting out in C#.
Once a new learner is comfortable with constructors and constructor chaining, it's fine to expand into initializers. In fact, I'd say it's even important to do so because so much of the newer C# features are focusing on using object initializers. A recent example of this would be the new required keyword.
You'll also notice here that I am strongly against expression-bodied members for new learners due to their added syntactical complexity. I recently wrote at length on expression-bodied members and my opinions on learning there, so check that article out for more details.
Pattern Matching Preferences
The pattern matching syntax popping up more frequently in recent C# releases is nice, but should not be in a new developer's first exposure to C# code. As a result, I recommend avoiding it in beginner-friendly codebases.

Variable Preferences
I do not recommend including inlined variables for new developers due to the added complexity in reading the code and minimal benefit.

Null Checking
The null checking section deals with null coalescing (??
) and null propagation (?
) operators.
I am cautiously optimistic about their use, particularly null propagation, but they should be used with caution.

The one case I do not like null coalescing is in throwing ArgumentNullException
on a parameter being null
.
In my experience the myParam = myParam ?? throw new ArgumentNullException
sytnax is more confusing for new developers than it is helpful, so I recommend against its usage here.
Using Preferences
I have no strong opinions on the using
directive placement and am fine to leave it in its default placement.

Modifier Preferences
This is a small section dealing with local functions and the readonly
keyword.

I find that readonly
slightly confuses new learners for a short period of time and then they're fine with it, so I'm not opposed to its inclusion as long as the tooling is subtle about it.
static
, on the other hand, almost always severely confuses new learners and I do not find the slight performance benefits you might get from static
to be worth the productivity penalties it presents in sidetracking a new developer. You may disagree, and that's fine, but this is my personal preference from my observations teaching.
Parameter Preferences
The only option in parameter preferences involves hinting that unused parameters should be removed. I'm in favor of this because this can often help new learners find unused code or bugs lurking in their applications.

New Line Preferences
I don't have any strong preferences for new line placement that might differ from the standard C# coding conventions.

In general I find a lot of value in line breaks for comprehension for new developers.
Other Changes
There are a few other setting changes that might be worth discussing to help the new developer experience in Visual Studio 2022+.
Naming Changes
Inside of the Text Editor > C# > Code Style > Naming section, there are three options for class, interface, and member naming standards.
I recommend moving the severity of these issues up to warnings as many mistakes I see new learners make could have been prevented by following dotnet naming standards.

Inline Diagnostics
I would also consider pairing these changes with the new experimental inline diagnostics feature for C# code in Visual Studio to help learners see warnings in their code as they go.
See my article on inline diagnostics for more details on what that is and how to get started with it.
Final Thoughts and a Parting Gift
All told, I believe that these settings lead to a more beginner-friendly dotnet development experience.
What I have done in this article is highlight a set of settings for learners that I believe enhances the learning or onboarding journey for that particular developer. I believe that focusing new developers on these settings and codebases written in them will help them achieve success as a developer faster.
These settings are particularly written with individual learners or teams of learners in mind. I'm not necessarily saying that you and your organization should adopt these settings department-wide for the benefit of your teams, but they may inspire some form of organizational standards you adopt.
Finally, I have a parting gift for you all for being so patient with this long article.
Because configuring things can be tedious, I have a .editorconfig
file available with my recommendations for beginner-friendly C# coding standards saved into the file.
If you download the .editorconfig
file from its GitHub repository, you can add it to your projects and have these settings enforced across your team automatically. This means that developers on your team will not need to make any custom configuration changes and Visual Studio will guide them towards more beginner-friendly coding standards.
I realize that a lot of my advice here is going to be contentious or divisive, as most discussions on code style tend to be. I do not want to cause unnecessary strife, but my hope is that some of these ideas spark improvements in the beginner-friendliness of your code.
If you have other advice for folks or other opinions, please share them in the comments. If you know of other settings that might help new learners, I'd also love to hear them. I have plenty of opinions on ways we could make dotnet easier to get into and am passionate about growing and enriching our technical community.
1 comment
[…] Refactoring in the Enterprise talks about organizational challenges in refactoring, including getting buy-in and time to change code, dealing with larger scale refactorings, and enforcing code standards through EditorConfig files. […]