BRO1111: Generic type constraints should be on their own line¶
Each where clause starts its own line, one level deeper than the declaration. Replaces StyleCop SA1127.
Example¶
Before¶
using System;
using System.Collections.Generic;
public class Cache<TKey, TValue> where TKey : IComparable<TKey> where TValue : class
{
private readonly Dictionary<TKey, TValue> items = new Dictionary<TKey, TValue>();
public TValue Get(TKey key) => items[key];
public static TOut Convert<TOut>(TValue value) where TOut : class => value as TOut;
}
After¶
using System;
using System.Collections.Generic;
public class Cache<TKey, TValue>
where TKey : IComparable<TKey>
where TValue : class
{
private readonly Dictionary<TKey, TValue> items = new Dictionary<TKey, TValue>();
public TValue Get(TKey key) => items[key];
public static TOut Convert<TOut>(TValue value)
where TOut : class
=> value as TOut;
}
Why¶
Constraints are part of the signature, but a long declaration with constraints at the end of the line hides them. On their own lines, each constraint is easy to find and to change.
How the fix works¶
Each where gets a new line, indented one level deeper than the line with the declaration's name. The indentation
comes from .editorconfig (indent_style, indent_size, tab_width) and defaults to four spaces. For an
expression-bodied member, the => after the last constraint gets its own line too, like StyleCop's fix.
Checked: classes, structs, interfaces, records, methods, delegates and local functions.
Not reported¶
- A comment right before
where(class Box<T> /* why */ where T : class).
Configuration¶
[*.cs]
stylebro_constraint_placement = own_line
own_line(default): like StyleCop's SA1127, as described above.same_line: everywhereclause goes on the declaration's line, after one space:
// Before
public class Cache<TKey, TValue>
where TKey : IComparable<TKey>
where TValue : class
{
}
// After
public class Cache<TKey, TValue> where TKey : IComparable<TKey> where TValue : class
{
}
With same_line, a declaration's clauses are joined all together or not at all (and only then reported). They aren't
when:
- the joined line, up to the last clause's end, would be longer than
max_line_length. A body after the clauses ({ },=> ...) isn't counted. Withoutmax_line_lengththere is no limit, so set it when you usesame_line. - a clause spans several lines,
- anything but whitespace sits before a clause (a comment, a directive).
The => of an expression body stays where it is (where T : class then => ... on the next line keeps that line;
BRO1521 leaves an => after a constraint alone). A ) on its own line before the first clause that
BRO1110 moves to the last parameter moves in the same edit. The joined line is measured as the other
fixes in the same run leave it, when those rules are on: BRO1404/BRO1007 add an access
modifier, BRO1109 moves ( up, BRO1108 puts each parameter on its own line,
BRO1110 moves ). So the decision doesn't depend on which fix runs first. Not counted: renames
(BRO1304, BRO1305, ...) and rewrites inside the line (BRO1115 int?,
BRO1123 tuple syntax, ...), whose results depend on checks made elsewhere (a rename can be skipped).
Right at the limit, one of those can still leave a join for a second dotnet format run, or a joined line a few
characters too long.
Compared with StyleCop¶
Same results as StyleCop, with the same fixed output. StyleCop has no same_line setting; teams that prefer it turn
SA1127 off.