Подтвердить что ты не робот

Невозможно преобразовать тип "Задача <Производный>" в "Задача <Интерфейs>"

У меня есть следующая функция с параметром делегата, которая принимает тип одного интерфейса и возвращает задачу другого.

public void Bar(Func<IMessage, Task<IResult>> func)
{
    throw new NotImplementedException();
}

У меня также есть функция с параметром в качестве экземпляра IMessage и возвращает задачу. Message и Result являются реализациями IMessage и IResult соответственно.

private Task<Result> DoSomething(Message m) { return new Task<Result>(() => new Result()); }

Я получаю сообщение об ошибке при передаче DoSomething в Bar.

Bar(m => DoSomething((Message)m));
// Cannot convert type 'Task<Result>' to 'Task<IResult>'

Почему не будет Result неявно конвертировать в IResult?

Я бы подумал, что это проблема с ковариацией. Однако в этом случае Result реализует IResult. Я также попытался решить проблему ковариации, создав интерфейс и отметив TResult как ковариантный.

public interface IFoo<TMessage, out TResult>
{
    void Bar(Func<TMessage, Task<TResult>> func);
}

Но я получаю ошибку:

Неверная дисперсия: параметр типа "TResult" должен быть инвариантным действует на IFoo<TMessage, TResult>.Bar(Func<TMessage, Task<TResult>>). "TResult" является ковариантным.

Теперь я застрял. Я знаю, что у меня проблема с ковариацией, но я не уверен, как ее решить. Любые идеи?

Изменить: этот вопрос относится к задачам. Я столкнулся с этой проблемой, выполнив async await в своем приложении. Я столкнулся с этой общей реализацией и добавил Task. Другие могут иметь одинаковые проблемы во время этого типа преобразования.

Решение: Здесь решение основано на ответах ниже:

Func<Task<Result>, Task<IResult>> convert = async m => await m;
Bar(m => convert(DoSomething((Message)m)));
4b9b3361

Ответ 1

С# не допускает отклонения от классов, только интерфейсы и делегаты, которые параметризуются ссылочными типами. Task<T> - класс.

Это несколько неудачно, так как Task<T> является одним из тех редких классов, которые могут быть безопасно ковариантными.

Однако достаточно легко преобразовать a Task<Derived> в Task<Base>. Просто создайте вспомогательный метод/лямбда, который принимает Task<Derived> и возвращает Task<Base>, ожидая переданную задачу и вернет значение, отличное от Base. Компилятор С# позаботится об остальном. Конечно, вы теряете ссылочную идентификацию, но вы никогда не узнаете это с классом.

Ответ 2

Кажется, что для этого нужен более чистый способ, но можно создать задачу упаковки правильного типа. Я ввел новую функцию под названием GeneralizeTask().

Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task) 
    where TDerived : TBase 
{
    var newTask = new Task<TBase>(() => {
        if (task.Status == TaskStatus.Created) task.Start();
        task.Wait();
        return (TBase)task.Result;
    });
    return newTask;
}

Edit:

Как указывает @EricLippert, это можно значительно упростить. Сначала я попытался найти такой способ реализации этого метода, но не смог найти тот, который был скомпилирован. Как оказалось, реальное решение было еще проще, чем я себе представлял.

async Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task) 
    where TDerived : TBase 
{
    return (TBase) await task;
}

Затем вы можете вызвать Bar() следующим образом.

Bar(m => GeneralizeTask<IResult, Result>(DoSomething((Message)m)));