AI writes .NET code faster than anyone on your team.
It can also write code that looks good but can fall apart during careful review.
I have spent years improving the quality of code I write, long before the AI was around.
I learned to write code that is easy to read and maintain, and I have been refining my skills ever since.
Now I'm doing the same by teaching AI agents to write code with the same quality I do.
But most developers fail to get AI to write high-quality, clean code.
The reason is simple. The AI followed the rules it could see, and it never saw yours.
Most developers try to fix this by putting the rules in the prompt.
I tried that for many months.
The rules work for a few files, maybe a few iterations in a session.
But as the session goes on, the context gets loaded with information, and your rules start to be ignored.
So only two things actually work reliably that I want to show you today:
In this post, we will explore:
Let's dive in.
👉 Read original article on my newsletter: https://antondevtips.com/blog/how-to-make-ai-write-high-quality-code-in-dotnet
Every .NET solution should start with a Directory.Build.props file. This file defines project-wide settings that apply to all projects in your solution.
Without this file, you end up duplicating the same configuration across multiple .csproj files.
When you want to change a setting, you have to update every project file by hand. This leads to inconsistencies and wasted time.
Directory.Build.props solves this problem by centralizing configuration in a single file.
You create this file in the same directory as your .sln file, and MSBuild applies it to all projects in the solution.
Here is the configuration I use for every new project:
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>All</AnalysisMode>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project>
Here is what each setting does:
Nullable: Enables nullable reference types, which help prevent null reference exceptions. The compiler warns you when you might be using a null value incorrectly.
ImplicitUsings: Adds common namespace imports to every file. You don't need to write using System; or using System.Linq; anymore.
AnalysisLevel: Sets the code analysis level to the latest version. You get the most recent code quality checks from Microsoft.
AnalysisMode: Turns on all code analysis rules. This gives you the most feedback on code quality.
TreatWarningsAsErrors: Stops compilation if there are any warnings. This forces you to fix issues right away instead of letting them pile up.
CodeAnalysisTreatWarningsAsErrors: Applies the same strict treatment to code analysis warnings.
EnforceCodeStyleInBuild: Runs code style checks during build, not just in the IDE. Your CI/CD pipeline will catch style violations.
Now here is why this file matters more than ever.
An AI agent doesn't read your standards. It runs dotnet build, and it reads the output.
TreatWarningsAsErrors turns every analyzer rule into a build error. The agent detects the error, fixes it, and rebuilds. That loop runs before you ever open the diff.
Without this one line, the same rules become warnings. Warnings can get ignored by agents.
That is the pattern behind everything below. A rule the build enforces gets followed every time, and a rule that only lives in a document gets skipped as soon as the agent has busy context.
Code quality is something you need to care about from day 1.
It's easier to follow the best coding practices than to fix them later.
For this, we can use static code analysis.
Static code analyzers examine your code without running it.
They catch common mistakes, enforce coding standards, and find potential bugs before they reach production.
Some of the analyzers can even catch code quality issues.
They can detect:
The analyzers run during compilation, so you get feedback right away in your IDE and in your CI/CD pipeline.
Add these analyzer packages to your Directory.Build.props file:
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>All</AnalysisMode>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.257">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.16.0.128591">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Analyzers" Version="4.14.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.analyzers" Version="1.26.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
Here is what each analyzer provides:
SonarAnalyzer.CSharp: Focuses on code quality, security holes, and code smells. It finds complex methods, duplicated code, and potential bugs.
Meziantou.Analyzer: Catches performance issues, security holes, and incorrect API usage. It has hundreds of rules covering async/await patterns, LINQ usage, string handling, and more.
Roslynator.Analyzers: Provides code analysis and refactoring suggestions. It helps you write cleaner, more idiomatic C# code.
xunit.analyzers: Makes sure you write tests correctly. It catches common testing mistakes, such as missing assertions or wrong test attributes.
The IncludeAssets configuration makes sure that these analyzers run during compilation but don't get included in your published application.
With TreatWarningsAsErrors enabled, your build will fail if any analyzer finds an issue. This might seem strict, but it stops technical debt from piling up.
You should always aim for zero warnings in your project. Many developers ignore warnings, but warnings often point at real problems that will cause bugs later.
These four packages carry thousands of rules between them. That's thousands of rules an AI agent has to satisfy, and none of them cost you a single line in a prompt.
For more information about code quality best practices, check out this article.