I like to be as specific as is possible with my types. When offering up a method encapsulating a query it's common to see List<T>
returned (or more specifically Task<List<T>>
.) No wonder with EF Core's .ToListAsync()
method. But that's not very accurate is it? List<T>
is a projection of a larger collection in SQL Server (or whatever persistence you're using). It's not modifiable.
I want to instead return IReadOnlyCollection<T>
from my methods. This seems a lot more accurate. I'm returning a projection from my source data and you can't modify the source data by modifying the projection. Nor can my result be modified erroneously without casting back to List<T>
at which point you've gone far enough out of your way I'm assuming you know what you're doing.
So I attempted to just return the interface like I would synchronously.
public Task<IReadOnlyCollection<TResult>> GetResultAsync() =>
_dbContext.Where(x => x.Property == true).ToListAsync();
I'm avoiding using the async
and await
operators here. I want to pass the Task unawaited to avoid the await
overhead. But when wrapped in Task<T>
C#'s type system doesn't recognize that the wrapped List<T>
is castable to IReadOnlyCollection<T>
. So Task<List<T>>
cannot be cast to Task<IReadOnlyCollection<T>>
. You can try an explicit cast, of course, but the compiler won't like that either and for the same reasons.
Well that's annoying. But we can peel off the Task<T>
with async wait
easy enough.
public async Task<IReadOnlyCollection<TResult>> GetResultAsync() =>
await _dbContext.Where(x => x.Property = true).ToListAsync();
In order to avoid the added code I considered an extension method.
public static async Task<IReadOnlyCollection<T>> ToReadOnlyCollectionAsync<T>(this IQueryable<T> query) =>
await query.ToListAsync();
This gets the await
out of my methods but of course still adds another await. Is there a way to return Task<IReadOnlyCollection<T>>
for "free" from EF Core 2.1?
No idea what ToListAsync()
does but going by the name, and if I'm guessing correctly, it iterates the results and converts them to a list on a background thread and returns the task that must be awaited
before the list is produced.
If that's the case, your question is
how to convert
Task<List<T>>
toTask<IReadonlyCollection<T>>
.
If so, you could in theory do something like this:
public Task<IReadOnlyCollection<string>> ToReadOnlyCollection()
{
return ToListAsync().ContinueWith(x => new ReadOnlyCollection<string>(x.Result) as IReadOnlyCollection<string>);
}
public Task<IReadOnlyList<string>> ToReadOnlyList()
{
// if you don't mind an IReadOnlyList, you can use this
// one which doesn't involve creating a new collection
return ToListAsync().ContinueWith(x => x.Result as IReadOnlyList<string>);
}
private Task<List<string>> ToListAsync()
{
return Task.Run(() =>
{
Task.Delay(1000);
return new List<string>
{
"1",
"2",
"3"
};
});
}
I don't believe Task<T>
allows contra-variance so a Task<TChild>
(where Child : Parent) cannot be automatically converted to Task<TParent>
so you'll need to do it yourself.