FluentResults
FluentResults copied to clipboard
Don't understand, why an implicit conversion of matching generic types doesn't work
When using FluentResults with generic types like IReadOnlyList<T>
it seems that implicit conversation doesn't work and I can't understand why. Maybe someone can enlighten me by this simple example.
What works without any problems is this kind of code:
private async Task<string> FindMatchingItem()
{
await Task.Delay(1);
return "1";
}
public async Task<Result<string>> GetMatchingItem()
{
var item = await FindMatchingItem();
// Here we can simply return item, which will be automatically be converted into Result<string>
return item;
}
But, what doesn't work is this one here:
private async Task<IReadOnlyList<string>> FindMatchingItems()
{
await Task.Delay(1);
return new List<string> { "1", "2", "3" };
}
public async Task<Result<IReadOnlyList<string>>> GetMatchingItems()
{
var items = await FindMatchingItems();
// Doesn't compile. Has to be: return Result.Ok(items);
return items;
}
The compiler complains with this error message:
CS0266: Cannot implicitly convert type 'System.Collections.Generic.IReadOnlyList<string>' to 'FluentResults.Result<System.Collections.Generic.IReadOnlyList<string>>'. An explicit conversion exists (are you missing a cast?)
If I'm going to change the signature of FindMatchingItems()
to return Task<List<string>>
the error is gone:
// We changed the return type to be List instead of IReadOnlyList
private async Task<List<string>> FindMatchingItems()
{
await Task.Delay(1);
return new List<string> { "1", "2", "3" };
}
// But not here. It is still IReadOnlyList
public async Task<Result<IReadOnlyList<string>>> GetMatchingItems()
{
var items = await FindMatchingItems();
return items;
}
So can anyone explain, why List<string>
can be converted into Result<IReadOnlyList<string>>
, but the very same type IReadOnlyList<string>
can't be converted into the desired Result type?
If someone needs a simple workaround, return Result.Ok(items)
.
TLDR; You can also use items.ToList()
to make this work. It seems to be a limitation of the C# spec.
I was messing around with this today and this same thing happens if you use IEnumerable
instead of IReadOnly*
variants as well. I have a feeling this is related to this SO answer and this SO answer by Eric Lippert where he talks about implicit conversion of interface types. The spec doesn't support this and there aren't plans to add it. What does work currently is considered a bug that they are leaving in place to not break existing production systems.
Because the result is wrapped in Task
, it sort of obscures the defined implicit conversion that would work if it was a synchronous call. You can reproduce that same issue because Task<IEnumerable<string>> MyMethod() => Task.FromResult(["A"]);
won't compile for the same reason. We know []
is an IEnumerable
, but because it is wrapped in Task
, it cannot implicitly convert to that type and you either need to return Task.FromResult<IEnumerable<string>>(["A"])
or await
the task.