BRO1314: Asynchronous method names should end with Async¶
A method that returns Task, Task<T>, ValueTask, ValueTask<T> or IAsyncEnumerable<T> has a name ending in
Async, as in .NET's own APIs. Like Roslynator's RCS1046 and Meziantou's MA0137. Off by default.
Example¶
root = true
[*.cs]
dotnet_diagnostic.BRO1314.severity = warning
Before¶
using System.Threading.Tasks;
internal interface IStore
{
Task Save(int id);
}
internal class Store : IStore
{
public Task Save(int id) => Task.CompletedTask;
public async Task<int> Copy(int id)
{
await Save(id);
return id;
}
}
After¶
using System.Threading.Tasks;
internal interface IStore
{
Task SaveAsync(int id);
}
internal class Store : IStore
{
public Task SaveAsync(int id) => Task.CompletedTask;
public async Task<int> CopyAsync(int id)
{
await SaveAsync(id);
return id;
}
}
Why¶
The suffix tells a caller the method has to be awaited, and keeps room for a synchronous Save next to SaveAsync.
Configuration¶
stylebro_rename_public_api = true also renames names other assemblies see (default false, see Not reported; the owner's decision in decisions.md).
How the fix works¶
The method is renamed everywhere in the solution, with its overrides and implementations (explicit ones too), like
BRO1309's renames. A method whose name also starts with a lower-case letter gets both changes in one
rename (load -> LoadAsync) while BRO1309 is on.
"Asynchronous" means the declared return type: Task, Task<T>, ValueTask, ValueTask<T> (System.Threading.Tasks)
or IAsyncEnumerable<T>. Not the async keyword (a method can return a Task without it), and not other awaitable
types (GetAwaiter()): a custom awaitable says little about how callers use it. async void methods aren't reported.
This renames public API: callers in other repositories break, so the rule is off by default.
Not reported¶
- Where IDE1006 is suppressed: after
#pragma warning disable IDE1006(up to itsrestore), or with[SuppressMessage("Style", "IDE1006")]on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names. - A new name your SDK naming rule rejects: when the first
dotnet_naming_rulethat covers the symbol is enforced (IDE1006, or the rule's own severity, at suggestion or above) and asks for another style, like_camelCasefor private properties, the name stays (renaming it would only trade this warning for IDE1006). - The name appears in code excluded by
#ifanywhere in the project: disabled code isn't compiled, so the rename can't follow it. - Public API (unless
stylebro_rename_public_api = true): Methods that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also withInternalsVisibleTo. - A name that
nameof(...)produces: the rename would change the string (Ocelot built an HTTP header name withnameof(X_RateLimit_Limit), and a test read a private field withGetField(nameof(_components))). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one. - Names that contain
Asyncanywhere (ReadAsyncCore,WithAsyncLock), andMain. - Overrides and implementations: they keep the base member's name (a library's
ExecuteAsync,Dispose); the base is reported when it is in your code. - Methods with attributes, except a few harmless ones (
[Obsolete],[MethodImpl],[DebuggerStepThrough],[DebuggerHidden],[DebuggerNonUserCode],[EditorBrowsable],[SuppressMessage],[ExcludeFromCodeCoverage],[Pure]): test methods ([Fact],[Test],[TestMethod]),[HttpGet],[JSInvokable],[OperationContract]and the like are found through their attribute, and some frameworks use the name. - Methods of ASP.NET controllers (a type named
...Controller, deriving fromController/ControllerBase, or with[ApiController]) and SignalR hubs (deriving fromHub): MVC trimsAsyncfrom action names, and hub clients call methods by name. - Event handlers:
(object sender, EventArgs e)or a type derived fromEventArgs. - Local functions, and a new name that is already taken (
Loadnext to an existingLoadAsync). - The same rename guards as BRO1309:
externand partial methods, members paired by name (ShouldSerializeX).
Reported but not fixed¶
If dotnet format leaves a finding, StyleBro kept it on purpose. The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a nameof (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and stylebro-migrate format lists each one with its reason
(Findings kept on purpose); its --verify-no-changes doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the baseline.
- The name appears as a string anywhere in the solution (
GetMethod("Save")): reflection would break. - An implementation also implements another interface's member or a library's with the old name: renaming it would break that.
- A derived type already has a member with the new name.
Not checked¶
The reverse (a method ending in Async that isn't asynchronous) isn't reported: such names are usually deliberate
(IAsyncDisposable-style wrappers, a synchronous method kept next to its old async name).
Compared with StyleCop¶
StyleCop has no such rule. The SDK can report it with a naming rule (required_modifiers = async), but dotnet format
can't fix IDE1006.
Real world (2026-10-06)¶
Run with every other rule and the repositories' tests (Invoke-RealWorld.ps1 -Enable BRO1314 -Tests): Jellyfin 496
findings (483 renamed, 13 kept by the string and interface guards, e.g. Stop), Polly 96 (95; PredicateResult.True
kept), FFMpegCore 7, Newtonsoft.Json 3, FluentValidation 2, Serilog 1, CsvHelper 0. All compile, converge in one run,
and no test that passed before fails.