在没有T的情况下,在C#中将任务<T>转换为任务<object>

nca*_*ker 23 c# asynchronous casting

我有静态类充满扩展方法,其中每个方法都是异步的并返回一些值 - 像这样:

public static class MyContextExtensions{
  public static async Task<bool> SomeFunction(this DbContext myContext){
    bool output = false;
    //...doing stuff with myContext
    return output;
  }

  public static async Task<List<string>> SomeOtherFunction(this DbContext myContext){
    List<string> output = new List<string>();
    //...doing stuff with myContext
    return output;
  }
}
Run Code Online (Sandbox Code Playgroud)

我的目标是能够从另一个类中的单个方法调用这些方法中的任何一个,并将其结果作为对象返回.它看起来像这样:

public class MyHub: Hub{
  public async Task<object> InvokeContextExtension(string methodName){
    using(var context = new DbContext()){
      //This fails because of invalid cast
      return await (Task<object>)typeof(MyContextExtensions).GetMethod(methodName).Invoke(null, context);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

问题是演员表失败了.我的困境是我无法将任何类型参数传递给"InvokeContextExtension"方法,因为它是SignalR中心的一部分并由javascript调用.并且在某种程度上我不关心扩展方法的返回类型,因为它只是被序列化为JSON并被发送回javascript客户端.但是我必须将Invoke返回的值强制转换为任务才能使用await运算符.我必须提供一个带有"Task"的泛型参数,否则它会将返回类型视为void.所以这一切都归结为我如何成功地将具有泛型参数T的Task转换为具有对象的泛型参数的Task,其中T表示扩展方法的输出.

das*_*ght 20

您可以分两步完成 - await使用基类的任务,然后使用反射收集结果或dynamic:

using(var context = new DbContext()) {
    // Get the task
    Task task = (Task)typeof(MyContextExtensions).GetMethod(methodName).Invoke(null, context);
    // Make sure it runs to completion
    await task.ConfigureAwait(false);
    // Harvest the result
    return (object)((dynamic)task).Result;
}
Run Code Online (Sandbox Code Playgroud)

这是一个完整的运行示例,它将上述Task通过反射调用的技术置于上下文中:

class MainClass {
    public static void Main(string[] args) {
        var t1 = Task.Run(async () => Console.WriteLine(await Bar("Foo1")));
        var t2 = Task.Run(async () => Console.WriteLine(await Bar("Foo2")));
        Task.WaitAll(t1, t2);
    }
    public static async Task<object> Bar(string name) {
        Task t = (Task)typeof(MainClass).GetMethod(name).Invoke(null, new object[] { "bar" });
        await t.ConfigureAwait(false);
        return (object)((dynamic)t).Result;
    }
    public static Task<string> Foo1(string s) {
        return Task.FromResult("hello");
    }
    public static Task<bool> Foo2(string s) {
        return Task.FromResult(true);
    }
}
Run Code Online (Sandbox Code Playgroud)


Pac*_*ac0 12

一般来说,要转换Task<T>Task<object>,我只需要进行直接的连续映射:

Task<T> yourTaskT;

// ....

Task<object> yourTaskObject = yourTaskT.ContinueWith(t => (object) t.Result);
Run Code Online (Sandbox Code Playgroud)

(文档链接在这里)


但是,您实际的特定需求是通过反射调用Task并获取其(未知类型)结果.

为此,您可以参考完整的dasblinkenlight的答案,它应该适合您的确切问题.

  • OP的问题在于他正在通过反射调用任务,因此他没有`T`,并且不能执行`yourTaskT`。他能做的最好的事情是“任务”,但是他将无法在“ ContinueWith”中执行“ t.Result”。 (2认同)

M.R*_*ari 6

您不能转换Task<T>Task<object>,因为Task<T>不是协变的(也不是逆变的)。最简单的解决方案是使用更多反射:

var task   = (Task) mi.Invoke (obj, null) ;
var result = task.GetType ().GetProperty ("Result").GetValue (task) ;
Run Code Online (Sandbox Code Playgroud)

这很慢且效率低下,但如果不经常执行此代码,则可以使用。顺便说一句,如果您要阻塞等待其结果,那么异步 MakeMyClass1 方法有什么用呢?

另一种可能性是为此目的编写一个扩展方法:

  public static Task<object> Convert<T>(this Task<T> task)
    {
        TaskCompletionSource<object> res = new TaskCompletionSource<object>();

        return task.ContinueWith(t =>
        {
            if (t.IsCanceled)
            {
                res.TrySetCanceled();
            }
            else if (t.IsFaulted)
            {
                res.TrySetException(t.Exception);
            }
            else
            {
                res.TrySetResult(t.Result);
            }
            return res.Task;
        }
        , TaskContinuationOptions.ExecuteSynchronously).Unwrap();
    }
Run Code Online (Sandbox Code Playgroud)

它是非阻塞解决方案,将保留任务的原始状态/异常。

  • 虽然您的第一个解决方案肯定会起作用,但 OP 将无法利用您的第二个解决方案,因为他在调用的“this Task&lt;T&gt; task”部分没有“T”。 (2认同)
  • 这是一个很好的解决方案,因为它保留了取消状态。如果您不关心这一点,那么 `public static async Task&lt;object&gt; Convert&lt;T&gt;(this Task&lt;T&gt; t) =&gt; wait t;` 非常简洁。 (2认同)

JBS*_*rro 5

我想提供一个实现,恕我直言,这是早期答案的最佳组合:

  • 精确的参数处理
  • 没有动态调度
  • 通用扩展方法

干得好:

/// <summary> 
/// Casts a <see cref="Task"/> to a <see cref="Task{TResult}"/>. 
/// This method will throw an <see cref="InvalidCastException"/> if the specified task 
/// returns a value which is not identity-convertible to <typeparamref name="T"/>. 
/// </summary>
public static async Task<T> Cast<T>(this Task task)
{
    if (task == null)
        throw new ArgumentNullException(nameof(task));
    if (!task.GetType().IsGenericType || task.GetType().GetGenericTypeDefinition() != typeof(Task<>))
        throw new ArgumentException("An argument of type 'System.Threading.Tasks.Task`1' was expected");

    await task.ConfigureAwait(false);

    object result = task.GetType().GetProperty(nameof(Task<object>.Result)).GetValue(task);
    return (T)result;
}
Run Code Online (Sandbox Code Playgroud)

  • 我不知道你对“足够快”的定义。但如果您真的想消除这种性能损失,您可能一开始就不应该要强制执行任务。 (2认同)