📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

How do you use .editorconfig to keep C# code style consistent across Visual Studio, VS Code and Rider, and enforce it in the build?

Using a hierarchical .editorconfig for formatting, code style and analyser severities that every IDE honours, and enforcing it with EnforceCodeStyleInBuild and dotnet format in CI.

Intermediate Theory Intermediate analyzers code-style dotnet-format editorconfig ide

🎯 Key points

  • `.editorconfig` is a plain-text, hierarchical settings file honoured by Visual Studio, VS Code (C# extension), Rider and dotnet format
  • `root = true` stops the search for parent .editorconfig files
  • Severities (none, silent, suggestion, warning, error) control what the IDE and build report
  • Code style rules (IDExxxx) only run during the build when EnforceCodeStyleInBuild is true
  • Analyser rules are configured with dotnet_diagnostic.<id>.severity
  • `dotnet format --verify-no-changes` enforces formatting in CI
  • Roll out with a single formatting commit and add it to .git-blame-ignore-revs

⏱ 30-second answer

I commit an .editorconfig at the repo root with root = true. It holds formatting (indentation, new lines), .NET code style preferences such as file-scoped namespaces, and severities for analyser and code style rules via dotnet_diagnostic.<id>.severity. Visual Studio, VS Code and Rider all read it, so style no longer depends on the editor. To enforce it, I set EnforceCodeStyleInBuild so IDE rules run during dotnet build, and run dotnet format --verify-no-changes in CI.

🗣 Full answer (senior)

How I would answer this in an interview

"I commit an .editorconfig at the repo root with root = true, which stops tools searching parent folders. It's a plain-text, hierarchical file that Visual Studio, VS Code, Rider and dotnet format all read, so style stops depending on which editor someone uses. It holds formatting such as indentation and new lines, code style preferences such as file-scoped namespaces, and severities through dotnet_diagnostic.<id>.severity, from none and silent up to suggestion, warning and error.

Because it's hierarchical, a closer file overrides a further one, so a tests folder can relax a rule like underscores in test names without touching production rules. Enforcement is where people get caught out: analyser rules run in the build by default, but the IDE code style rules only run in the build when EnforceCodeStyleInBuild is true, which I set in Directory.Build.props, and that difference between IDE and CA rules is worth knowing well. Then CI runs dotnet format --verify-no-changes, which fails if any file would be reformatted.

On an existing codebase I roll it out in one dedicated formatting commit and add that commit to .git-blame-ignore-revs. The mistake I see is turning hundreds of rules into warnings with TreatWarningsAsErrors on day one, so I raise severities in stages, bug-catching rules first. It's one part of making the whole developer environment match CI.

Style consistency is a tooling problem, not a review problem, and moving it into the build lets reviewers spend their time on design and correctness."


Why .editorconfig

Each IDE has its own formatter settings, so a mixed team without a shared file ends up with pull requests full of whitespace churn. .editorconfig is an open format that all three IDEs and the .NET SDK understand, and it lives in the repository, so it is reviewed and versioned like code.

What goes in it

  1. Core EditorConfig properties: indent_style, indent_size, insert_final_newline, charset, end_of_line.
  2. .NET formatting options: for example csharp_new_line_before_open_brace, dotnet_sort_system_directives_first.
  3. Code style preferences with severity: csharp_style_namespace_declarations = file_scoped:warning.
  4. Rule severities: dotnet_diagnostic.IDE0005.severity = warning (unnecessary using), dotnet_diagnostic.CA1062.severity = none.

Hierarchy

Tools look for .editorconfig in the file's folder and every parent folder until one has root = true. Closer files override further ones, and within a file later matching sections override earlier ones. That lets a tests/ folder relax a rule (for example underscores in test method names) without changing production code rules.

Enforcing it

  • In the IDE, warning and error severities show in the Error List and Problems panel.
  • In the build, analyser rules (CAxxxx) run by default, but code style rules (IDExxxx) run only when EnforceCodeStyleInBuild is true. Put it in Directory.Build.props.
  • IDE0005 (remove unnecessary usings) is only reported during build when GenerateDocumentationFile is also true.
  • In CI, dotnet format --verify-no-changes fails if any file would be reformatted.

Production example

A team of Visual Studio and Rider users had constant brace and using order changes in reviews. We exported the agreed settings to .editorconfig, ran dotnet format once in a dedicated commit, added that commit to .git-blame-ignore-revs, and added dotnet format --verify-no-changes to the pull request pipeline. Review comments about style dropped to almost zero.

🔍 What the interviewer is checking

The interviewer is checking whether you can make a multi-IDE team consistent without arguments in code review.

Green flags

  • Knows the hierarchy and root = true.
  • Knows EnforceCodeStyleInBuild and dotnet format --verify-no-changes.
  • Has a sensible rollout plan for an existing codebase.

Red flags

  • "Everyone should just use the same IDE settings."
  • Confuses .editorconfig with IDE-specific settings files.
  • Wants every rule as an error on day one.

📚 Learn in depth

1. How settings are resolved for one file

repo/
+-- .editorconfig            root = true; team-wide rules
+-- src/
|   +-- Orders.Api/Program.cs   <- uses repo rules
+-- tests/
    +-- .editorconfig        overrides for tests only
    +-- Orders.Tests/OrderTests.cs <- repo rules + tests overrides

Lookup for OrderTests.cs:
  tests/Orders.Tests/ -> tests/.editorconfig -> repo/.editorconfig (root = true, stop)
  nearest file wins on conflicts; within a file, the last matching section wins

2. The four kinds of setting

Kind Example Who reads it
Core EditorConfig properties indent_style = space, insert_final_newline = true, end_of_line = lf Every EditorConfig-aware editor, for every file type
.NET formatting options csharp_new_line_before_open_brace = all, dotnet_sort_system_directives_first = true Roslyn formatter, dotnet format, Rider
.NET code style preferences csharp_style_namespace_declarations = file_scoped:warning Roslyn IDE analysers (IDExxxx)
Rule severities dotnet_diagnostic.CA2007.severity = none Every Roslyn analyser, in the IDE and in the build

Rider also reads resharper_* keys for its own formatter and inspections.

3. Section globs

Sections use globs: [*], [*.cs], [*.{csproj,props,targets}], [src/Legacy/**.cs]. A path in a section is relative to the folder holding that .editorconfig. Later sections override earlier ones, so put general rules first and exceptions after them.

4. Severity levels

Severity In the editor In the build
none Off Off
silent Fix offered, nothing shown Nothing
suggestion Grey dots Nothing
warning Green squiggle, Error List warning Build warning
error Red squiggle Build error

default resets to the rule's own default. With TreatWarningsAsErrors, every warning fails the build.

5. How the compiler sees it

The .NET SDK passes the .editorconfig files that apply to each source file to the Roslyn compiler as analyser configuration. Analysers read dotnet_diagnostic.* severities and option values at compile time, which is why the same file affects the IDE (live analysis) and the build (csc).

Code style rules (IDExxxx) are special: they only run during the build when EnforceCodeStyleInBuild is true. IDE0005 (unnecessary using) additionally needs GenerateDocumentationFile=true to be reported in the build.

6. .globalconfig versus .editorconfig

.editorconfig .globalconfig
Applies to Files under its folder, by glob Every file in the project, regardless of path
Found by Walking up from each file Being in the project folder or a parent, or being added as GlobalAnalyzerConfigFiles (for example from a NuGet package)
Best for Repo style and folder-specific overrides Analyser severities shipped in a shared package
Precedence Wins over .globalconfig for the same key Lower; use global_level to order several

7. Rolling it out to an existing repo

  1. Agree the rules (export from the IDE the team already uses as a starting point).
  2. Commit .editorconfig with root = true.
  3. Run dotnet format once in a dedicated commit.
  4. Add that commit's hash to .git-blame-ignore-revs and run git config blame.ignoreRevsFile .git-blame-ignore-revs.
  5. Turn on EnforceCodeStyleInBuild and add dotnet format --verify-no-changes to CI.
  6. Raise severities gradually: correctness and security analysers first, cosmetic rules later.

8. dotnet format modes

Command Fixes
dotnet format whitespace Indentation, spacing, new lines (no build needed)
dotnet format style Code style rules (IDExxxx)
dotnet format analyzers Fixable analyser diagnostics (CAxxxx and third-party)
dotnet format All three
--verify-no-changes Fails with a non-zero exit code instead of changing files
--severity warn Only fixes diagnostics at or above that severity

9. Performance considerations

Turning every rule to warning with EnforceCodeStyleInBuild adds some build time and can overwhelm a legacy codebase. Introduce severities gradually, starting with rules that catch bugs.

10. Try it yourself

  1. Add the .editorconfig from the code example to a project with block-scoped namespaces.
  2. Watch the IDE flag IDE0161 (convert to file-scoped namespace).
  3. Run dotnet build and note no warning; add EnforceCodeStyleInBuild and build again.
  4. Add tests/.editorconfig that sets CA1707 to none and confirm only test files are affected.
  5. Run dotnet format, then dotnet format --verify-no-changes and check the exit code is 0.

💻 Code

.editorconfig at the repo root:

root = true

[*]
indent_style = space
insert_final_newline = true
charset = utf-8

[*.{csproj,props,targets,json,yml}]
indent_size = 2

[*.cs]
indent_size = 4
csharp_style_namespace_declarations = file_scoped:warning
csharp_style_var_for_built_in_types = true:suggestion
dotnet_sort_system_directives_first = true

# Unnecessary using directive
dotnet_diagnostic.IDE0005.severity = warning

# ConfigureAwait is not needed in ASP.NET Core application code
dotnet_diagnostic.CA2007.severity = none

tests/.editorconfig (overrides for test projects):

[*.cs]
# Allow underscores in test method names such as Submit_order_with_no_lines_fails
dotnet_diagnostic.CA1707.severity = none

Directory.Build.props to enforce code style in every build:

<Project>
  <PropertyGroup>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <!-- Do not fail the build for missing XML comments on public members -->
    <NoWarn>$(NoWarn);CS1591</NoWarn>
  </PropertyGroup>
</Project>

CI step:

dotnet format --verify-no-changes

⚠ Common mistakes

  • Mistake: expecting IDE code style warnings in CI without EnforceCodeStyleInBuild. IDExxxx rules only run in the editor by default, so CI passes code the IDE flags. Instead: set <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> in Directory.Build.props, and also GenerateDocumentationFile if you want IDE0005 in the build.

  • Mistake: forgetting root = true. Tools keep searching parent folders, so a stray .editorconfig higher up (or in the user's home folder) changes behaviour on one machine. Instead: put root = true at the top of the repo-root .editorconfig.

  • Mistake: writing inline comments after values. EditorConfig comments must be on their own line; indent_size = 4 # four spaces makes the value invalid and the setting is ignored. Instead: put comments on the line above the setting, starting with # or ;.

  • Mistake: turning on hundreds of warnings at once with TreatWarningsAsErrors. A legacy codebase suddenly fails to build and everyone is blocked. Instead: raise severities in stages, fix one rule family at a time, and turn on warnings-as-errors once the count is zero.

  • Mistake: reformatting the whole repo inside a feature pull request. The real change is buried in thousands of lines of noise, and git blame points at the wrong person. Instead: reformat in one dedicated commit, and add its hash to .git-blame-ignore-revs.

  • Mistake: keeping style only in personal IDE settings. Nobody else has them, so formatting churns between developers and IDEs. Instead: put every team rule in the committed .editorconfig, which Visual Studio, VS Code, Rider and dotnet format all read.

  • Mistake: putting analyser severities for a shared package in .editorconfig files copied between repos. The copies drift and every repo ends up with different rules. Instead: ship severities as a .globalconfig in an internal NuGet package (or a shared Directory.Build.props), and keep .editorconfig for repo-specific overrides.

🏛 Senior insight: trade-offs

Style consistency is a tooling problem, not a review problem. A senior engineer moves style out of human review into .editorconfig plus CI, so reviewers spend time on design and correctness. The order of rollout matters: agree the rules, apply them in one mechanical commit, exclude that commit from blame, then enforce in CI. Prioritise rules that catch bugs (nullable, CA2000-style resource rules, security analysers) over cosmetic ones, and make severities a team decision recorded in the file itself.

🔗 Follow-ups the interviewer may ask

  1. When: The candidate talks about consistency across IDEs. Why: Style is one part of a wider reproducible environment.
    How to answer

    I standardise below the IDE rather than forcing one IDE. The CLI and CI are the source of truth. I pin the SDK with global.json, put style and analyser severities in .editorconfig, centralise build properties in Directory.Build.props and versions with Central Package Management, and commit shared IDE files such as .vsconfig, .vscode/extensions.json and launchSettings.json while ignoring personal state. I offer a Dev Container or a setup script for onboarding, keep secrets in user secrets and Key Vault, and CI runs the same dotnet format, build and test commands developers run locally.

  2. When: Candidate mentions dotnet_diagnostic severities or EnforceCodeStyleInBuild. Why: Goes deeper on analysers configured through .editorconfig.
    How to answer

    Analysers are plug-ins for the Roslyn compiler. They report diagnostics in the editor and during the build. CA rules (CA1xxx to CA5xxx) are the SDK's code quality analysers, covering design, performance, reliability and security. The .NET analysers are enabled by default for net5.0+ projects (EnableNETAnalyzers), but only a default subset of rules reports as warnings; AnalysisLevel and AnalysisMode set how many apply. IDE rules (IDE0xxx) are code style rules, such as var usage or file-scoped namespaces. They show in the editor but only run in the build when EnforceCodeStyleInBuild is true, which is why style issues often pass CI. Severities for both are set in .editorconfig (or a .globalconfig) with dotnet_diagnostic.<id>.severity. Third-party analysers come in as NuGet packages. For a team, I put the levels in Directory.Build.props, the severities in one root config file, and tighten them step by step.

↩ Usually asked after

More in IDEs and Editors

Last updated 1 Nov 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me