How the C# formatter works
The engine is CSharpier (WASM), an opinionated formatter for C# that parses with Roslyn, the same compiler front end Visual Studio and dotnet build use. Because the parse is real, anything modern C# allows is understood: records, file-scoped namespaces, top-level statements, pattern matching, switch expressions, raw string literals and primary constructors.
CSharpier deliberately has very few settings. It decides where lines break based on the line width, and it always produces the same output for the same input, which is what makes it useful in code review: two developers formatting the same file can never disagree. Typical changes include:
- Allman-style braces, each on its own line
- a blank line between class members
usingdirectives sorted, withSystemnamespaces first- long LINQ chains broken before each
.Where,.Selector.OrderBy - trailing commas added to multi-line switch expressions and initializers
Settings and keyboard shortcuts
C# has no extra switches on this page. The indent and line-width controls in the toolbar are passed to CSharpier as indentSize/useTabs and printWidth. Note the mismatch with .NET conventions: CSharpier and Visual Studio default to 4-space indentation and a width of 100, whereas the toolbar starts at 2 and 80. Change both if you are tidying code that will be committed to a repository that already uses CSharpier or dotnet format.
Ctrl/Cmd+Enter reformats, Ctrl/Cmd+Shift+C puts the result on the clipboard, and Ctrl/Cmd+K opens the command palette to switch to another language. C# is not a format that can be minified, so Ctrl/Cmd+Shift+M has nothing to do here.
Things worth knowing
CSharpier is a layout tool, not an analyser. It will not rename variables, add var, remove unused usings or apply .editorconfig code-style rules; that is the job of dotnet format or your IDE. It also does not resolve types, so a snippet that references classes you did not paste formats without complaint.
When the input does not compile at the syntax level you get Roslyn’s error code and text, for example CS1002: ; expected. The two numbers in parentheses before the code are the line and column, which is the quickest way to find the problem in a long file.
To protect a hand-aligned block, such as a lookup table, put // csharpier-ignore on the line above it, or wrap a region in // csharpier-ignore-start and // csharpier-ignore-end. Comments, #region markers and XML doc comments are kept.
Razor (.cshtml) and XAML are different languages and are not handled here; for project files such as .csproj use the XML formatter.
Examples
Switch expression with property patterns
Each switch arm lands on its own line with a trailing comma, and a blank line separates the class from the record.
public static class Pricing{public static decimal Discount(Customer c,decimal total)=>c switch{{IsVip:true}when total>500=>total*0.15m,{IsVip:true}=>total*0.1m,_=>0m};}
public record Customer(string Name,bool IsVip);public static class Pricing
{
public static decimal Discount(Customer c, decimal total) =>
c switch
{
{ IsVip: true } when total > 500 => total * 0.15m,
{ IsVip: true } => total * 0.1m,
_ => 0m,
};
}
public record Customer(string Name, bool IsVip);
Minimal API with top-level statements
Top-level statements need no class wrapper. The lambda and its conditional are broken across lines because they exceed the width.
var builder=WebApplication.CreateBuilder(args);var app=builder.Build();app.MapGet("/orders/{id:int}",async(int id,ShopDb db)=>await db.Orders.FindAsync(id) is Order o?Results.Ok(o):Results.NotFound());app.Run();var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet(
"/orders/{id:int}",
async (int id, ShopDb db) =>
await db.Orders.FindAsync(id) is Order o
? Results.Ok(o)
: Results.NotFound()
);
app.Run();
Async EF Core query, 4-space indent
With 4 spaces and width 100 the result matches CSharpier defaults; the LINQ chain breaks one call per line.
public class OrderQueries{private readonly ShopDb _db;public OrderQueries(ShopDb db)=>_db=db;public async Task<List<OrderDto>> RecentAsync(int days,CancellationToken ct){var since=DateTime.UtcNow.AddDays(-days);return await _db.Orders.Where(o=>o.Created>=since&&o.Status==Status.Paid).OrderByDescending(o=>o.Created).Select(o=>new OrderDto(o.Id,o.Total)).ToListAsync(ct);}}public class OrderQueries
{
private readonly ShopDb _db;
public OrderQueries(ShopDb db) => _db = db;
public async Task<List<OrderDto>> RecentAsync(int days, CancellationToken ct)
{
var since = DateTime.UtcNow.AddDays(-days);
return await _db
.Orders.Where(o => o.Created >= since && o.Status == Status.Paid)
.OrderByDescending(o => o.Created)
.Select(o => new OrderDto(o.Id, o.Total))
.ToListAsync(ct);
}
}
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
(1,32): error CS1002: ; expected | A statement is missing its semicolon. Roslyn reports the position just after the last token of that statement. | Add ; at the reported line and column (the numbers in parentheses). |
(1,19): error CS1026: ) expected | A parameter list, method call or condition opened ( and never closed it. | Close the parenthesis before the opening { or the end of the statement. |
(1,22): error CS1010: Newline in constant | A regular string literal runs to the end of the line without its closing quote. | Close the string, or use a verbatim @"..." or raw """...""" literal if it really spans lines. |
(1,28): error CS1022: Type or namespace definition, or end-of-file expected | There is an extra closing brace after the end of a namespace or class. | Delete the surplus }; the column points at it. |
Frequently asked questions
Is this the same as running dotnet csharpier?
Yes, it is CSharpier compiled to WebAssembly. Set the indent to 4 and the width to 100 to reproduce its defaults; the page starts at 2 and 80.
Does it apply my .editorconfig or .csharpierrc?
No. Only indentation and line width are configurable here, which are also the main options CSharpier itself exposes.
Can I format a snippet that is not a whole file?
Yes. Member declarations, classes and top-level statements all parse. A lone expression without a statement around it may be rejected.
Will it fix compiler warnings or naming rules?
No. CSharpier only changes whitespace, line breaks, using order and trailing commas. Analyzer rules need dotnet format or an IDE.