Learnixo
Back to blog
AI Systemsintermediate

Strict Analyzers and Code Style — Enforcing Consistency Across the Solution

How to configure Roslyn analyzers, .editorconfig, TreatWarningsAsErrors, and nullable reference types in a Clean Architecture .NET project to enforce code quality and consistency automatically.

Asma Hafeez KhanMay 16, 20264 min read
Clean Architecture.NETAnalyzersCode QualityEditorConfigNullable
Share:𝕏

Why Automated Code Style Enforcement

Code reviews that focus on formatting and style are wasted engineering time. Automated tools enforce consistency faster and with no disagreement. The goal: reviewers focus on logic and architecture; tools handle everything else.

Production issue I've seen: A team had no nullable reference type enforcement. Over 18 months, NullReferenceException was the #1 exception type in their error monitoring system. Every one of those crashes could have been a compile-time error. Enabling <Nullable>enable</Nullable> and <TreatWarningsAsErrors>true</TreatWarningsAsErrors> across the solution would have surfaced all of them at build time.


TreatWarningsAsErrors in Every Project

XML
<!-- Applied to ALL project files in the solution -->
<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

Apply this at the Directory.Build.props level so it applies automatically to every project:

XML
<!-- Directory.Build.props (solution root) -->
<Project>
  <PropertyGroup>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <AnalysisMode>All</AnalysisMode>
  </PropertyGroup>
</Project>

Nullable Reference Types

C#
// Without nullable enabled — the compiler cannot help you
public Patient? GetPatient(Guid id)
{
    // Could return null — caller has no idea
}

// Caller:
var patient = GetPatient(id);
var name = patient.Name;   // NullReferenceException — no warning

// With nullable enabled — everything is explicit
public Patient? GetByIdAsync(PatientId id, CancellationToken ct);   // ? = may be null

// Caller:
var patient = await GetByIdAsync(id, ct);
var name = patient.Name;   // CS8602: Dereference of a possibly null reference
// Fix: check first
if (patient is null)
    return Result.Failure<PatientResponse>(PatientErrors.NotFound);
var name = patient.Name;   // safe — compiler knows patient is non-null here

.editorconfig

INI
# .editorconfig (solution root)
root = true

[*.cs]
# Indentation
indent_style = space
indent_size  = 4
tab_width    = 4

# New lines
end_of_line               = crlf
insert_final_newline      = true
trim_trailing_whitespace  = true

# C# formatting
csharp_new_line_before_open_brace                                     = all
csharp_new_line_before_else                                           = true
csharp_new_line_before_catch                                          = true
csharp_new_line_before_finally                                        = true
csharp_indent_case_contents                                           = true
csharp_space_after_cast                                               = false
csharp_space_before_colon_in_inheritance_clause                       = true
csharp_preserve_single_line_statements                                = false

# Naming rules
dotnet_naming_rule.private_fields_should_be_camel.symbols             = private_fields
dotnet_naming_rule.private_fields_should_be_camel.style               = camel_underscore_prefix
dotnet_naming_rule.private_fields_should_be_camel.severity            = error

dotnet_naming_symbols.private_fields.applicable_kinds                 = field
dotnet_naming_symbols.private_fields.applicable_accessibilities       = private

dotnet_naming_style.camel_underscore_prefix.capitalization            = camel_case
dotnet_naming_style.camel_underscore_prefix.required_prefix           = _

# Code quality
dotnet_analyzer_diagnostic.category-Performance.severity             = error
dotnet_analyzer_diagnostic.category-Reliability.severity             = error

# Using directives
dotnet_sort_system_directives_first                                   = true
csharp_using_directive_placement                                      = outside_namespace

# Prefer var
csharp_style_var_for_built_in_types                                   = false:suggestion
csharp_style_var_when_type_is_apparent                                = true:suggestion

Roslyn Analyzers

XML
<!-- Directory.Build.props -->
<ItemGroup>
  <!-- Microsoft's recommended analyzers -->
  <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.*" PrivateAssets="all" />
  
  <!-- Async code correctness -->
  <PackageReference Include="AsyncFixer" Version="1.*" PrivateAssets="all" />
  
  <!-- Security analysis -->
  <PackageReference Include="SecurityCodeScan.VS2019" Version="5.*" PrivateAssets="all" />
</ItemGroup>

Common analyzer rules enabled:

CA1822:  Mark members as static when possible
CA2016:  Forward CancellationToken when available
CA1062:  Validate parameter before use (avoids NullReferenceException)
CA1031:  Do not catch general Exception types
CS8600:  Converting null literal or possible null value
CS8601:  Possible null reference assignment
CS8602:  Dereference of a possibly null reference
ASYNC001: Async methods should end in Async

Suppressing False Positives

When an analyzer warning is a false positive, suppress it with context and reason:

C#
// ✓ Suppression with explanation
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex) when (ex is not OperationCanceledException)
{
    // Intentional broad catch in middleware — all unexpected failures logged and 500 returned
    _logger.LogError(ex, "Unhandled exception");
    await WriteErrorResponseAsync(context);
}
#pragma warning restore CA1031

PRO TIP: Never use [SuppressMessage] without a Justification parameter. A suppression with no justification is a code smell that future developers will not know whether to trust or fix.

C#
[SuppressMessage(
    "Reliability",
    "CA2007:Consider calling ConfigureAwait on the awaited task",
    Justification = "ASP.NET Core does not use a SynchronizationContext")]

File-Scoped Namespaces

Enforce the modern C# 10 file-scoped namespace style:

INI
# .editorconfig
csharp_style_namespace_declarations = file_scoped:error
C#
// Old style (warning with above setting)
namespace SystemForge.Application.Patients.Commands.CreatePatient
{
    public sealed class CreatePatientCommand { ... }
}

// New style (preferred)
namespace SystemForge.Application.Patients.Commands.CreatePatient;

public sealed class CreatePatientCommand { ... }

EnforceCodeStyleInBuild

XML
<!-- Directory.Build.props -->
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>

This makes .editorconfig rules fail the build if violated — not just show warnings in the IDE. Without this setting, style rules are advisory only.


CI Verification

YAML
# .github/workflows/ci.yml
- name: Build with strict settings
  run: dotnet build --configuration Release
  # TreatWarningsAsErrors + EnforceCodeStyleInBuild means any style or quality
  # violation fails this step and blocks the PR from merging

Key Takeaway

Strict analyzers and code style enforcement are not bureaucracy — they are automation. Every NullReferenceException that Nullable catches at compile time is a production incident that does not happen. Every naming convention enforced by .editorconfig is a code review comment that does not need to be written. The initial setup takes a few hours; the compounding benefit over a year of development is hundreds of hours of debugging and review time saved.

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.