Introduction
In modern software development, maintaining a clean and consistent codebase is not just about aesthetics—it's about maintainability, readability, and scalability. As teams grow and projects evolve, enforcing consistent coding practices becomes essential.
.NET developers can harness the power of two complementary tools.
- .editorconfig: A configuration file that enforces coding style rules in your IDE and build process.
- Roslyn Analyzers: Static code analyzers that work during compilation to flag and even fix issues like bad practices, threading bugs, and inefficient code.
Together, these form a robust foundation for automated code quality enforcement. This article walks you through setting up and leveraging both tools, including writing your own custom analyzers tailored to your team’s needs.
Why use coding rules?
- Catch bugs and anti-patterns early.
- Enforce consistent code style across teams and projects.
- Integrate seamlessly into CI/CD pipelines.
- Improve developer onboarding by codifying standards.
Setting Up .editorconfig for Style Enforcement
.editorconfig files define and enforce coding style rules such as indentation, naming conventions, and whitespace usage. They are supported natively by Visual Studio and respected during build time (when configured properly).
Placing .editorconfig in Your Solution: Place the .editorconfig file at the solution root to apply rules across all projects as given in the below image.
![Solution root]()
Sample .editorconfig: Now we will add coding rules in this file.
root = true
[*.cs]
# Indentation
indent_style = space
indent_size = 4
# New line at end of file
insert_final_newline = true
# C# specific rules
dotnet_sort_system_directives_first = true
dotnet_style_qualification_for_field = true:suggestion
dotnet_style_qualification_for_property = true:suggestion
dotnet_style_qualification_for_method = true:suggestion
dotnet_style_explicit_tuple_names = true:warning
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
csharp_new_line_before_open_brace = all:warning
You can change the severity level as per your project requirements, e.g., suggestion, warning, error.
Enforce Style in Build: To make style violations fail the build,d add the below given code in your .csproj file.
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
Microsoft Roslyn Analyzers for Code Quality
Roslyn analyzers provide compile-time feedback by analyzing syntax and semantic code information. They catch issues like poor exception handling, misuse of APIs, and threading problems.
Installing Roslyn Analyzers
dotnet add package Microsoft.CodeAnalysis.NetAnalyzers
Note. Update the version regularly to benefit from new rules and fixes.
Writing Custom Roslyn Analyzers
Microsoft's analyzers are powerful but general-purpose. If your team has domain-specific rules, you can create custom analyzers.
Creating a Custom Analyzer Project
You can create an analyzer project. Visual Studio provides a predefined template for this.
![Custom Analyzer]()
There will be five projects in this template, but we will focus on only two projects: CodeAnalyzer and CodeAnalyzer.CodeFixes as of now. We have also added a Web API project, CustomAnalyzerPOC in the solution to test the rule sets.
- CodeAnalyzer
- CodeAnalyzer.CodeFixes
![Web API project]()
Example: Custom Rule — Avoid Console.WriteLine in Production Code
We will create a custom rule to avoid using Console.writeLine in code. Lets add the below given code in the CodeAnalyzer class.
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AvoidConsoleWriteLineAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "HCA002";
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
DiagnosticId,
"Avoid Console.WriteLine",
"Avoid using 'Console.WriteLine' in non-test code",
"Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression);
}
private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var invocation = (InvocationExpressionSyntax)context.Node;
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
memberAccess.Expression.ToString() == "Console" &&
memberAccess.Name.ToString() == "WriteLine")
{
context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation()));
}
}
}
Above is the code to implement the rule to avoid the Console.WriteLine() in the running code. We can set the DiagnosticSeverity level as per our project requirement.
Now we will add the code to fix this error. Add the below given code in the CodeFix project.
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public class AvoidConsoleWriteLineCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(AvoidConsoleWriteLineAnalyzer.DiagnosticId);
public override FixAllProvider GetFixAllProvider() =>
WellKnownFixAllProviders.BatchFixer;
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var diagnostic = context.Diagnostics.First();
var node = root.FindNode(diagnostic.Location.SourceSpan);
context.RegisterCodeFix(
CodeAction.Create(
"Remove Console.WriteLine",
ct => Task.FromResult(
context.Document.WithSyntaxRoot(
root.RemoveNode(node, SyntaxRemoveOptions.KeepNoTrivia))),
nameof(AvoidConsoleWriteLineCodeFixProvider)),
diagnostic);
}
}
Note. Both classes are bound by the DiagnosticId. That should be the same for errors and fixes.
public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(AvoidConsoleWriteLineAnalyzer.DiagnosticId);
Now, add a reference to the analyzer to the project where you want it to be implemented.
<ItemGroup>
<ProjectReference
Include="..\CodeAnalyzer\CodeAnalyzer\CodeAnalyzer.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
/>
<ProjectReference
Include="..\CodeAnalyzer\CodeAnalyzer.CodeFixes\CodeAnalyzer.CodeFixes.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
/>
</ItemGroup>
Output
![Output]()
Best Practices for Analyzer Usage
Practice |
Why It Matters |
Use .editorconfig + Roslyn |
Covers both style and logic errors |
Treat critical rules as errors |
Prevents risky code from entering the codebase |
Keep .editorconfig at root |
Ensures rules apply to the entire solution |
Review and update rules regularly |
Adapts to evolving code standards |
Document custom rules |
Helps with onboarding and consistent application |
Conclusion
By combining .editorconfig, Microsoft Roslyn Analyzers, and your own custom rules, you gain fine-grained control over style, correctness, and consistency in your .NET Core projects.
- Catch bugs early.
- Enforce consistency.
- Level up code quality.
Thank You, and Stay Tuned for More!
More Articles from my Account