How to separate sync and async methods in C# type?

Lets say I design a generic read only repository IReadOnlyRepository. public interface IReadOnlyRepository { ... TEntity Find(object id) where TEntity : IEntity; IEnumerable Filter(Expression filter) where TEntity : IEntity; int Count(Expression filter) where TEntity : IEntity; ... } I already have a code base with several synchronous implementations of IReadOnlyRepository and want to extend my type (interface and it's implementations) with asynchronous versions of the same methods since today it is cool to async all the way. public interface IReadOnlyRepository { ... Task FindAsync(object id) where TEntity : IEntity; Task FilterAsync(Expression filter) where TEntity : IEntity; Task CountAsync(Expression filter) where TEntity : IEntity; } This may sound subjective, but ... Should I add async methods to the same file in a #region Async? public interface IReadOnlyRepository { ... #region Sync TEntity Find(object id) where TEntity : IEntity; IEnumerable Filter(Expression filter) where TEntity : IEntity; int Count(Expression filter) where TEntity : IEntity; #endregion #region Async Task FindAsync(object id) where TEntity : IEntity; Task FilterAsync(Expression filter) where TEntity : IEntity; Task CountAsync(Expression filter) where TEntity : IEntity; #endregion ... } ... or should I make my repository partial and add async methods to partial files of the same type? public partial interface IReadOnlyRepository { TEntity Find(object id) where TEntity : IEntity; IEnumerable Filter(Expression filter) where TEntity : IEntity; int Count(Expression filter) where TEntity : IEntity; } public partial interface IReadOnlyRepository { Task FindAsync(object id) where TEntity : IEntity; Task FilterAsync(Expression filter) where TEntity : IEntity; Task CountAsync(Expression filter) where TEntity : IEntity; } ... or should I add my async methods to a separate IRepositoryAsync interface? public interface IReadOnlyRepository { TEntity Find(object id) where TEntity : IEntity; IEnumerable Filter(Expression filter) where TEntity : IEntity; int Count(Expression filter) where TEntity : IEntity; } public interface IReadOnlyRepositoryAsync { Task FindAsync(object id) where TEntity : IEntity; Task FilterAsync(Expression filter) where TEntity : IEntity; Task CountAsync(Expression filter) where TEntity : IEntity; } public class EfRepository : IReadOnlyRepository, IReadOnlyRepositoryAsync ... or should I get rid of sync methods at all and go async only? public interface IReadOnlyRepository { Task FindAsync(object id) where TEntity : IEntity; Task FilterAsync(Expression filter) where TEntity : IEntity; Task CountAsync(Expression filter) where TEntity : IEntity; } What are the best practices of composing large types with both sync and async methods in a matter of file structure, code readability and reusability?

Jun 12, 2025 - 06:20
 0

Lets say I design a generic read only repository IReadOnlyRepository.

public interface IReadOnlyRepository
{
    ...    
    TEntity Find(object id) where TEntity : IEntity;
    IEnumerable Filter(Expression> filter) where TEntity : IEntity;
    int Count(Expression> filter) where TEntity : IEntity;
    ...
}

I already have a code base with several synchronous implementations of IReadOnlyRepository and want to extend my type (interface and it's implementations) with asynchronous versions of the same methods since today it is cool to async all the way.

public interface IReadOnlyRepository
{
    ...
    Task FindAsync(object id) where TEntity : IEntity;
    Task> FilterAsync(Expression> filter) where TEntity : IEntity;
    Task CountAsync(Expression> filter) where TEntity : IEntity;
}

This may sound subjective, but ...

Should I add async methods to the same file in a #region Async?

public interface IReadOnlyRepository
{
    ...    
    #region Sync

    TEntity Find(object id) where TEntity : IEntity;
    IEnumerable Filter(Expression> filter) where TEntity : IEntity;
    int Count(Expression> filter) where TEntity : IEntity;

    #endregion

    #region Async

    Task FindAsync(object id) where TEntity : IEntity;
    Task> FilterAsync(Expression> filter) where TEntity : IEntity;
    Task CountAsync(Expression> filter) where TEntity : IEntity;

    #endregion
    ...
}

... or should I make my repository partial and add async methods to partial files of the same type?

public partial interface IReadOnlyRepository
{
    TEntity Find(object id) where TEntity : IEntity;
    IEnumerable Filter(Expression> filter) where TEntity : IEntity;
    int Count(Expression> filter) where TEntity : IEntity;
}

public partial interface IReadOnlyRepository
{
    Task FindAsync(object id) where TEntity : IEntity;
    Task> FilterAsync(Expression> filter) where TEntity : IEntity;
    Task CountAsync(Expression> filter) where TEntity : IEntity;
}

... or should I add my async methods to a separate IRepositoryAsync interface?

public interface IReadOnlyRepository
{
    TEntity Find(object id) where TEntity : IEntity;
    IEnumerable Filter(Expression> filter) where TEntity : IEntity;
    int Count(Expression> filter) where TEntity : IEntity;
}

public interface IReadOnlyRepositoryAsync
{
    Task FindAsync(object id) where TEntity : IEntity;
    Task> FilterAsync(Expression> filter) where TEntity : IEntity;
    Task CountAsync(Expression> filter) where TEntity : IEntity;
}


public class EfRepository : IReadOnlyRepository, IReadOnlyRepositoryAsync

... or should I get rid of sync methods at all and go async only?

public interface IReadOnlyRepository
{
    Task FindAsync(object id) where TEntity : IEntity;
    Task> FilterAsync(Expression> filter) where TEntity : IEntity;
    Task CountAsync(Expression> filter) where TEntity : IEntity;
}

What are the best practices of composing large types with both sync and async methods in a matter of file structure, code readability and reusability?