【问题标题】:Making generic function async fails on type conversion due to using Task<T>由于使用 Task<T>,使泛型函数异步在类型转换时失败
【发布时间】:2020-09-04 08:29:33
【问题描述】:

这是给定接口和类的结构:

public abstract class Entity
{ }

public class Group : Entity
{ }

public interface ICollectionPage<T>
{ }

public interface IGraphServiceGroupsCollectionPage : ICollectionPage<Group>
{ }

这里有两种使用这些接口的方法。一个是同步的,另一个是异步的,你使用Task&lt;T&gt; 作为参数:

private static Task<IEnumerable<T>> GetAllAsync<T>(Task<ICollectionPage<T>> collectionTask)
    where T : Entity
{
    throw new NotImplementedException();
}

private static Task<IEnumerable<T>> GetAll<T>(ICollectionPage<T> collectionTask)
    where T : Entity
{
    throw new NotImplementedException();
}

如果我们尝试在代码中使用同步方法,一切都会按预期工作:

var collectionPage = (IGraphServiceGroupsCollectionPage)null;
var groups = GetAll(collectionPage);

但是如果我们尝试使用 async 方法,就会出现类型转换错误:

var collectionPageTask = (Task<IGraphServiceGroupsCollectionPage>)null;
var groupsFromAsync = GetAllAsync<IGraphServiceGroupsCollectionPage>(collectionPageTask);

错误 CS1503:参数 1:无法从 'System.Threading.Tasks.Task' 转换为 'System.Threading.Tasks.Task>'

知道如何在异步调用中避免这个错误吗?

完整代码:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public interface IGraphServiceClient
{
}

public abstract class Entity
{
}

public class Group : Entity
{
}

public interface ICollectionPage<T>
{
}

public interface IGraphServiceGroupsCollectionPage : ICollectionPage<Group>
{
}

public class Program
{
    private static Task<IEnumerable<T>> GetAllAsync<T>(Task<ICollectionPage<T>> collectionTask)
        where T : Entity
    {
        throw new NotImplementedException();
    }

    private static Task<IEnumerable<T>> GetAll<T>(ICollectionPage<T> collectionTask)
        where T : Entity
    {
        throw new NotImplementedException();
    }

    static void Main(string[] args)
    {
        var collectionPage = (IGraphServiceGroupsCollectionPage)null;
        var groups = GetAll(collectionPage);
        var collectionPageTask = (Task<IGraphServiceGroupsCollectionPage>)null;
        var groupsFromAsync = GetAllAsync<IGraphServiceGroupsCollectionPage>(collectionPageTask);
    }
}

更新原因:您为什么要这样做?

原因很简单。第一步,我们想为另一个异步方法编写一个扩展方法。此方法具有以下签名:

public interface IGraphServiceUsersCollectionRequest
{
    public Task<IGraphServiceGroupsCollectionPage> GetAsync();
}

当我们喜欢这样称呼它时,我们喜欢这样做:

var request = (IGraphServiceUsersCollectionRequest)null;
var result = await request.GetAsync().GetAllAsync();

为了让它工作,我们需要一个扩展方法,它接受一个Task&lt;IGraphServiceGroupsCollectionPage&gt; 作为输入并返回一个Task&lt;IEnumerable&lt;Group&gt;&gt;。通过尝试构建此扩展方法,我们遇到了上述问题,我们将其归结为上述示例。

【问题讨论】:

  • 返回类型不同,这与async和Task关系不大。该错误抱怨将IGraphServiceGroupsCollectionPage转换为ICollectionPage&lt;IGraphServiceGroupsCollectionPage&gt;
  • 为什么 GetAllAsync 接受 Task&lt;ICollectionPage&lt;T&gt;&gt; collectionTask 而不是 ICollectionPage&lt;T&gt; ?为什么不await 之前的任何任务并将结果传递给方法?即使您使用.ContinueWith,您也会将结果传递给任何后续方法
  • 是的,你不能像那样将参数强制放入`Task
  • 异步调用没有区别。 async 关键字不会影响任何东西,它只是一个标记,告诉编译器该方法可以使用await。但是,将任务作为参数传递给另一个方法是非常罕见的。你为什么要这样做?
  • 忘记异步,这是打字问题而不是 TPL 问题

标签: c# type-inference


【解决方案1】:

您需要使Task 参数更通用。

private static Task<IEnumerable<T>> GetAllAsync<T,T2>(Task<T> collectionTask)
   where T : ICollectionPage<T2>
   where T2 : Entity
{
   throw new NotImplementedException();
}

...

var collectionPageTask = (Task<IGraphServiceGroupsCollectionPage>)null;
var groupsFromAsync = GetAllAsync<IGraphServiceGroupsCollectionPage,Entity>(collectionPageTask);

【讨论】:

  • 感谢您提供“这是要做什么” - 我的回答是“这就是它目前无法编译的原因”。
  • @JonSkeet alley oop, slamdunk
  • 是的,这行得通,但现在类型推断已经消失了。因此,如果您尝试在未显式指定泛型参数的情况下调用此方法,则会失败。
  • @Oliver 这确实是问题所在,请参阅 jons 解释为什么不能这样做
【解决方案2】:

问题在于Task&lt;T&gt;(作为一个类)是不变的。来自问题中的 cmets:

IGraphServiceGroupsCollectionPage 实现ICollectionPage&lt;Group&gt; 并且该方法适用于Task&lt;ICollectionPage&lt;T&gt;&gt;

您希望 Task&lt;IGraphServiceGroupsCollectionPage&gt;Task&lt;ICollectionPage&lt;Group&gt;&gt; 兼容。它不是。你可以很简单地说:

Task<IGraphServiceGroupsCollectionPage> task1 = null;
Task<ICollectionPage<Group>> task2 = task1;

这给出了一个错误:

错误:无法将类型“System.Threading.Tasks.Task”隐式转换为“System.Threading.Tasks.Task>”

另外,这个调用:

GetAllAsync<IGraphServiceGroupsCollectionPage>(collectionPageTask)

... 为T 提供IGraphServiceGroupsCollectionPage 的显式类型参数,并且参数的类型为ICollectionPage&lt;T&gt;,因此编译器正在尝试将参数转换为Task&lt;ICollectionPage&lt;IGraphServiceGroupsCollectionPage&gt;&gt;

Michael Randall has shown the solution to this, making the call generic in two type parameters instead of one..

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多