Code opinion: why I prefer avoiding the Async suffix in C# asynchronous methods
Should every asynchronous method name end with Async? Does it bring more benefits or more downsides? Let’s reason about this habit, which is common among C# developers.
Table of Contents
Just a second! 🫷
If you are here, it means that you are a software developer. So, you know that storage, networking, and domain management have a cost .
If you want to support this blog, please ensure that you have disabled the adblocker for this site. I configured Google AdSense to show as few ADS as possible - I don't want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.
Thank you for your understanding.
- Davide
Having clear, easy-to-read names is important for helping other developers understand what a method does. Also, each method should do what you expect it to do: this obvious statement is known as the Principle of least surprise.
So, it actually would make sense for all the asynchronous methods to have names that end with Async, right? After all, we are making it obvious that that specific method is asynchronous, without any doubt.
(Before starting: do you know what asynchronous programming is, in C#? If not, here’s a quick recap!)
However, I think there are other important aspects that lead me to be more inclined to avoid the Async suffix, improving the readability of the whole codebase.
In this article, I’ll present my perspective and discuss how I would respond to some objections.
The mental burden of having “Async” repeated over and over again
As you know, once you add an asynchronous method, you must update the entire method call chain down to that specific method, marking every other method as asynchronous (yes, I’m ignoring on purpose the possibility to call .GetAwaiter().GetResult() in a synchronous method!).
This means that not only will you have to add the async keyword to every method signature, but you will also have to make them return a Task instead of a “simpler” object and use the await keyword in the method body to call the asynchronous method in an asynchronous way. Unfortunately, we have no other choice: the compiler requires these keywords to work with asynchronous methods.
But then, you’ll also have to rename each method by adding the Async suffix. Every method, public or private, and also the related definition in the interface. Or do you?

Now, let’s consider this simple sequence of operations:
Set<Role> roles = await GetRoles();
bool isValidUser = await CheckIfUserHasRoles(user, roles);
if(!isValidUser)
await MarkAsBanned(user);
In my opinion, that snippet of code is perfectly readable. You can understand that the method is asynchronous due to the presence of all those await keywords.
There’s no need to write:
Set<Role> roles = await GetRolesAsync();
bool isValidUser = await CheckIfUserHasRolesAsync(user, roles);
if(!isValidUser)
await MarkAsBannedAsync(user);
Now, think of your whole codebase stuffed with this Async suffix: it’s not a problem with the compiler or the performances - the compiler really doesn’t care; it’s only a matter of readability and wasted mental energies.
A method name should express WHAT it does, not HOW
For this point, let’s reason backwards.
Consider this snippet of pseudo-code:
foreach (user in users) {
storage.MarkAsBanned(user);
}
How would you name a method that wraps that foreach loop? BanAllUsers? Sounds reasonable, it clearly expresses what the method does.
Would you call it BanAllUsersForeach, just because internally you are using a foreach loop? No, I don’t think so.
Similarly, it doesn’t make sense to call it BanAllUsersAsync just because it internally performs asynchronous operations.
The method name should express WHAT the method does, and not HOW.
That method could even perform all the operations synchronously, but then, for some reason, return a Task; even though it doesn’t make the whole method asynchronous, it will force the caller to await the returned Task.
One single interface may have multiple concrete classes
We know it all, all .NET developers (and I too) sin in creating interfaces even though it’s not necessary, just to use them for Dependency Injection and mocking. After all, there is no other way to create tests for classes without exposing them via an interface, right? 🤦♂️ Joking - there are at least 4 ways to create Unit Tests without interfaces!
Let’s remember that interfaces exist to create a layer of abstraction above the concrete classes. And that one single interface can be implemented by multiple classes.
Suppose you have this interface (again, in pseudo-code):
interface IUserValidator
{
BanUser(User);
}
And now - plot twist! - the same interface is implemented by two classes, one with a synchronous method, one with an asynchronous method:
class SyncUserValidator: IUserValidator
{
BanUser(User user) => db.BanUser(user);
}
class AsyncUserValidator: IUserValidator
{
BanUser(User user) => await db.BanUserAsync(user);
}
So, how should we actually name the method? BanUser or BanUserAsync?
If we name it BanUserAsync, then the implementation in the SyncUserValidator class lies, because the method is not asynchronous. On the contrary, if we call it BanUser, both implementations are right, because BanUser expresses what the method does, not how.
Clearly, you have the problem of the return type: T or Task<T>?
In this case, it depends on your context, the limitations and strategies for breaking changes, and the effort required to adapt all the caller methods.
Replying to some objections
I know what you are thinking. And I know what you are ready to object.
So, let me try to reply to you!
Objection 1: But in Microsoft they use the suffix!
In all the Microsoft documentation, you can find the Async suffix, like here in the Async return types page.
The Async suffix also appears in their code, as in the HttpClient class.
Why did they do that? Well, you should ask them! 😅 But, seriously, I suppose that’s because they are publishing code that can have both a synchronous and an asynchronous usage, so, for backwards compatibility, it makes sense to use it explicitly.
What would happen if you released a synchronous method that is being used by thousands of projects, and then you made it asynchronous, changing both the behaviour and the return type? You’ll see crowds of developers with forks and torches because of the breaking changes!

Surely, for them, it’s better to have both versions explicitly separated - and, I suppose, once they started with this approach, they kept using the Async suffix for the new developments.
So, are you in the same situation?
Also, as my mother used to say,
If they jump off a bridge, will you jump too?
It’s an Italian adage about not blindly following what everyone else does, but using your own head.
Objection 2: Then what about fire-and-forget methods?
In C#, you can define methods to be fire-and-forget by making them return void instead of Task. This means that the caller does not await the method, but can only launch it.
private async void SendMessages(string[] messages)
{
Task[] tasks = messages.Select(m => Send(m));
await Task.WhenAll(tasks);
}
private void Notify()
{
string[] messages = ["hey", "you"];
SendMessages(messages);
}
So, should these methods be marked as Async? Honestly, I don’t have an answer: it all depends on your personal taste.
On the one hand, having the suffix makes it evident that the operation is asynchronous. On the other hand, maybe the caller does not care about how the method is implemented. So, it’s up to you!
Objection 3: How can I be sure that the method is asynchronous if there is no suffix?
Let’s consider the first example I showed (the one with GetRoles and CheckIfUserHasRoles). Imagine you are the developer who has to use those methods. How can you choose between awaiting and not awaiting these methods if you don’t know if they are asynchronous?
You can simply have a look at the method signatures of GetRoles, CheckIfUserHasRoles, and MarkAsBanned to verify that these methods return a Task, which is a clear indicator that you need to await them.
Every advanced IDE has some sort of hint/tooltip/window that shows the method signature. If the method returns a Task, then you most likely need to await it.
A good reason to actually use the Async suffix
I think there is one exception that would make me use the Async suffix: if you are exposing your code in public repositories (such as a GitHub project or a NuGet package) and you need to maintain both the synchronous and asynchronous versions of a method.
The Async suffix will then prevent breaking changes, making sure that the caller knows that nothing has changed.
You may end up exposing two similar methods, one for synchronous and one for asynchronous operations, and maybe, internally, one method calls the other one:
public void Notify()
{
string[] messages = ["hey", "you"];
SendMessages(messages);
}
public async Task NotifyAsync(){
Notify();
}
Or, on the contrary,
public void Notify()
{
NotifyAsync().GetAwaiter().GetResult();
}
public async Task NotifyAsync(){
string[] messages = ["hey", "you"];
SendMessages(messages);
}
Further readings
All in all, I think that readability and maintainability are probably even more important than performance: if you need to bring value to your customers, having an easy-to-modify code is more beneficial than saving 5 milliseconds for every HTTP operation. But I already discussed this topic thoroughly in another article:
🔗 Code opinion: performance or clean code?
This article first appeared on Code4IT 🐧
So, just remember that approaches applied blindly are not always the best choice.
Do you want another example? Here we are: are you sure that reaching 100% code coverage makes your code robust?
🔗 Why reaching 100% Code Coverage must NOT be your testing goal
But, in the end, what is “asynchronous programming”? Well, if you’ve never worked with it (or you need to brush up your knowledge), here’s a quick article in which I talk about Tasks, ValueTasks, and how synchronous and asynchronous methods can work together.
🔗 First steps with asynchronous programming in C# | Code4IT
Wrapping up
Yes, I know, this is against the advice you see almost everywhere. You know, as developers, we tend to follow the “we always did it this way” in our niche.
And, even worse, we see what Big Names™ do and evangelise, and we apply their statements blindly, just because “they said so” (yes, I’m talking about you, “Microservices and CQRS everywhere”!).
I’d really like to hear what your objections are to this topic. I think it’s a controversial topic, even though it shouldn’t be.
I hope you enjoyed this article! Let’s keep in touch on LinkedIn, Twitter or BlueSky! 🤜🤛
Happy coding!
🐧