Last updated: October 5, 2026 · Tested with CSharpier 1.3.0 (dotnet tool and CSharpier.MSBuild), dotnet format from SDK 10.0.401, Windows 11
Short answer: CSharpier is an opinionated formatter: it rewraps long lines, sorts usings and gives every file the same layout, with almost nothing to configure. dotnet format ships with the SDK and fixes spacing, indentation and code-style rules from .editorconfig, but it never wraps a long line. Use CSharpier for layout and keep dotnet format style and analyzers for code rules. Don't run dotnet format whitespace on top of CSharpier: with a common .editorconfig setting the two tools undo each other and your CI check fails either way. Below are the real before/after files, timings on 3,050 files, and the setup that worked.
Install CSharpier (1.x Commands)
Install it as a local tool so everyone on the team and the build server use the same version:
dotnet new tool-manifest
dotnet tool install csharpier
dotnet csharpier format .
dotnet csharpier check .
On SDK 10 that creates dotnet-tools.json in the current folder, with the version pinned. Commit it:
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.3.0",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}
Upgrading from 0.x? Version 1.0 moved everything to subcommands. The old dotnet csharpier . and dotnet csharpier --check . now fail with exit code 1:
$ dotnet csharpier .
'.' was not matched. Did you mean one of the following?
-h
Required command was not provided.
$ dotnet csharpier --check .
'--check' was not matched. Did you mean one of the following?
check
Required command was not provided.
Replace them with dotnet csharpier format . and dotnet csharpier check . in scripts, hooks and CI.
Same File, Two Formatters
I wrote one deliberately messy class:
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo{
public class OrderService{
private readonly Dictionary<int,decimal> _prices=new Dictionary<int, decimal>();
public decimal CalculateTotal(IEnumerable<(int productId,int quantity)> lines,decimal discountPercentage,bool includeTax,string countryCode){
if(lines==null) throw new ArgumentNullException(nameof(lines));
var total=lines.Where(l=>l.quantity>0).Sum(l=>_prices.TryGetValue(l.productId,out var price)?price*l.quantity:0m);
if (includeTax) { total = total * (countryCode == "NL" ? 1.21m : countryCode == "DE" ? 1.19m : 1.0m); }
return Math.Round(total*(1-discountPercentage/100),2);
}
public string Describe(int id) => new StringBuilder().Append("Order ").Append(id).ToString();
}
}
After dotnet csharpier format OrderService.cs ("Formatted 1 files in 495ms"):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public class OrderService
{
private readonly Dictionary<int, decimal> _prices = new Dictionary<int, decimal>();
public decimal CalculateTotal(
IEnumerable<(int productId, int quantity)> lines,
decimal discountPercentage,
bool includeTax,
string countryCode
)
{
if (lines == null)
throw new ArgumentNullException(nameof(lines));
var total = lines
.Where(l => l.quantity > 0)
.Sum(l =>
_prices.TryGetValue(l.productId, out var price) ? price * l.quantity : 0m
);
if (includeTax)
{
total =
total
* (
countryCode == "NL" ? 1.21m
: countryCode == "DE" ? 1.19m
: 1.0m
);
}
return Math.Round(total * (1 - discountPercentage / 100), 2);
}
public string Describe(int id) =>
new StringBuilder().Append("Order ").Append(id).ToString();
}
}
After dotnet format whitespace on the same input:
using System.Text;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
public class OrderService
{
private readonly Dictionary<int, decimal> _prices = new Dictionary<int, decimal>();
public decimal CalculateTotal(IEnumerable<(int productId, int quantity)> lines, decimal discountPercentage, bool includeTax, string countryCode)
{
if (lines == null) throw new ArgumentNullException(nameof(lines));
var total = lines.Where(l => l.quantity > 0).Sum(l => _prices.TryGetValue(l.productId, out var price) ? price * l.quantity : 0m);
if (includeTax) { total = total * (countryCode == "NL" ? 1.21m : countryCode == "DE" ? 1.19m : 1.0m); }
return Math.Round(total * (1 - discountPercentage / 100), 2);
}
public string Describe(int id) => new StringBuilder().Append("Order ").Append(id).ToString();
}
}
What the comparison shows:
- Long lines: CSharpier wrapped the parameter list, the LINQ chain and the nested ternary at its 100-character default.
dotnet formatleft a 152-character line exactly as it was; it has no line-length rule. - Usings: CSharpier sorted them (
System.Textmoved to the end).dotnet format whitespacedid not touch the order. - Blank lines: CSharpier added one between members and after the usings.
dotnet formatonly fixed indentation and spaces around operators. - Code style: neither added braces to
if (lines == null) throw .... That is a code-style rule (IDE0011), which isdotnet format style's job, not a formatter's.
Do They Conflict?
With no .editorconfig, no: dotnet format --verify-no-changes passed on CSharpier's output. Then I added an .editorconfig that I see in a lot of repositories:
root = true
[*.cs]
indent_style = space
indent_size = 4
max_line_length = 80
csharp_new_line_before_open_brace = none
CSharpier honored max_line_length = 80 (the longest line dropped to 73 characters) and ignored csharp_new_line_before_open_brace = none, because brace placement is not configurable in CSharpier. dotnet format reads that setting, so it rejected CSharpier's output:
$ dotnet format whitespace demo.csproj --verify-no-changes
OrderService.cs(6,15): error WHITESPACE: Fix whitespace formatting. Replace 1 characters with '\s'.
OrderService.cs(8,30): error WHITESPACE: Fix whitespace formatting. Replace 5 characters with '\s'.
OrderService.cs(18,10): error WHITESPACE: Fix whitespace formatting. Replace 9 characters with '\s'.
OrderService.cs(29,28): error WHITESPACE: Fix whitespace formatting. Replace 13 characters with '\s'.
And after letting dotnet format move the braces, CSharpier rejected that:
$ dotnet csharpier check OrderService.cs
Error .\fight/OrderService.cs - Was not formatted.
----------------------------- Expected: Around Line 6 -----------------------------
namespace Demo
{
----------------------------- Actual: Around Line 6 -----------------------------
namespace Demo {
public class OrderService {
Run both whitespace checks in CI and one of them always fails. The fix that passed for me: let CSharpier own layout and run only the other two parts of dotnet format. On the CSharpier-formatted file, with the same .editorconfig, both of these exited with 0:
dotnet format style --verify-no-changes
dotnet format analyzers --verify-no-changes
Also delete layout settings that CSharpier ignores (brace and new-line options) from .editorconfig, so the IDE doesn't reformat code differently when someone presses Format Document.
Configuration: What You Can Change
Very little, on purpose. The settings that matter are print width, indentation and line endings. CSharpier reads them from .csharpierrc.json or from .editorconfig:
{
"printWidth": 140
}
I tested which one wins with the same messy file:
| Config present | Longest line after formatting |
|---|---|
| None (default print width 100) | 93 |
.editorconfig with max_line_length = 80 | 73 |
Both, .csharpierrc.json with printWidth: 140 | 101 (CSharpier's own file wins) |
If you keep both files, set the width in one place only.
Keep Hand-Aligned Code with csharpier-ignore
CSharpier collapses manual alignment. A // csharpier-ignore comment protects the next statement or member:
namespace Demo;
public static class Rates
{
// csharpier-ignore
public static readonly (string Country, decimal Vat)[] Aligned =
[
("NL", 0.21m),
("DE", 0.19m),
("LU", 0.17m),
];
public static readonly (string Country, decimal Vat)[] NotIgnored =
[
("NL", 0.21m),
("DE", 0.19m),
("LU", 0.17m),
];
}
That is the file after formatting: the ignored table kept its spacing, and the second one, which had the same spacing before, was collapsed to single spaces. To skip whole files (generated code, migrations), list them in a .csharpierignore file, which uses .gitignore syntax. A .csharpierignore containing Migrations/ left a deliberately messy migration file untouched while the rest of the folder was formatted.
Format on Build with CSharpier.MSBuild
dotnet add package CSharpier.MSBuild
With the package referenced, a normal dotnet build formatted my messy file in place (the usings came out sorted) and the build succeeded. With -p:CSharpier_Check=true it checks instead of writing, and fails the build:
$ dotnet build -p:CSharpier_Check=true
D:\...\msb\OrderService.cs : error : Was not formatted.
Build FAILED.
0 Warning(s)
1 Error(s)
The file was left unchanged in check mode. One gotcha: the command-line tool refuses to run when a project references a different CSharpier.MSBuild version. With the tool at 1.3.0 and the package at 1.2.0:
$ dotnet csharpier format msb
Error The csproj at D:\...\msb\msb.csproj uses version 1.2.0 of CSharpier.MsBuild which is a mismatch with version 1.3.0
The exit code is 1, so a pre-commit hook or CI step stops there. Upgrade both together; --no-msbuild-check skips the check if you really need to.
Speed on 3,050 Files
I copied the C# files from my test projects 50 times (3,050 files, 171,400 lines, most of them already tidy) and timed each command twice from a fresh copy:
| Command | Time |
|---|---|
dotnet csharpier format --no-cache | 8.6–9.8 s |
dotnet csharpier format again, cache populated | 0.8 s |
dotnet csharpier check | 5.2–5.6 s |
dotnet format whitespace --folder | 10.4–11.0 s |
dotnet format whitespace --folder --verify-no-changes | 9.1–9.5 s |
CSharpier keeps a cache of files it has already formatted, so repeated local runs only touch changed files. The first cached run after a --no-cache run still took the full 5 seconds; the cache is written by normal runs. In CI, where the cache is empty, check is the number that counts, and it was about twice as fast as dotnet format --verify-no-changes, while doing more (line wrapping and using order).
A CI Step That Works
These are the two commands I ran from a clean folder with only the tool manifest and an unformatted file. check printed the diff and exited with 1:
dotnet tool restore
dotnet csharpier check .
In GitHub Actions they go in a normal run step after actions/setup-dotnet. If you also run dotnet format style --verify-no-changes, put it in a separate step so the log shows which tool failed. The GitHub Actions guide for .NET covers the rest of the pipeline.
Which Should You Use?
| Situation | Choice |
|---|---|
| New project or team tired of style debates in reviews | CSharpier for layout + dotnet format style/analyzers for code rules |
| Large existing codebase, can't accept a one-time diff of every file | dotnet format only, or CSharpier introduced folder by folder |
| Team needs a specific brace or wrapping style | dotnet format with .editorconfig; CSharpier won't do it |
| No extra tools allowed | dotnet format (ships with the SDK) |
The first CSharpier run on an existing repository touches almost every file. Do it in one commit with nothing else in it and add that commit to .git-blame-ignore-revs, so git blame still points at real changes. The C# code review checklist and Git practices for .NET teams cover what to review once formatting is no longer a topic.
FAQ
Does CSharpier remove unused usings? No. A file with unused System.IO and System.Text usings came back with both still there, just sorted. Unused usings are an analyzer rule (IDE0005), so dotnet format style or the IDE handles them.
Can CSharpier put braces on the same line? No. Brace placement is not an option, and csharp_new_line_before_open_brace in .editorconfig is ignored.
Is formatting the same in Visual Studio and Rider? Only if the IDE runs CSharpier on save (there are official extensions); the IDE's own Format Document follows .editorconfig and can produce different output.
This article is part of the C# tutorials guide (language features, patterns, performance and testing).
References: CSharpier documentation · CSharpier configuration · CSharpier ignore rules · CSharpier.MSBuild on NuGet · Microsoft: dotnet format
Comments
Post a Comment