在 C# 中将 Task<T> 转换为 Task<object>,无需 T

问题描述 投票:0回答:8

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

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;
  }
}

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

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);
    }
  }
}

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

c# .net asynchronous casting
8个回答
35
投票

您可以分两步完成 -

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;
}

这是一个完整的运行示例,它将上述通过反射调用

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);
    }
}

30
投票

一般来说,要将

Task<T>
转换为
Task<object>
,我会简单地采用简单的连续映射:

Task<T> yourTaskT;

// ....

Task<object> yourTaskObject = yourTaskT.ContinueWith(t => (object) t.Result);

文档链接在这里


但是,您实际的具体需求是 通过反射调用

Task
并获取其(未知类型)结果

为此,您可以参考完整的dasblinkenlight的答案,它应该适合您的具体问题。


8
投票

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

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

给你:

/// <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;
}

6
投票

您不能将

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

var task   = (Task) mi.Invoke (obj, null) ;
var result = task.GetType ().GetProperty ("Result").GetValue (task) ;

这很慢且效率低下,但如果不经常执行此代码则可用。顺便说一句,如果您要阻塞等待其结果,那么异步 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();
    }

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


4
投票

最有效的方法是自定义等待者:

struct TaskCast<TSource, TDestination>
    where TSource : TDestination
{
    readonly Task<TSource> task;

    public TaskCast(Task<TSource> task)
    {
        this.task = task;
    }

    public Awaiter GetAwaiter() => new Awaiter(task);

    public struct Awaiter
        : System.Runtime.CompilerServices.INotifyCompletion
    {
        System.Runtime.CompilerServices.TaskAwaiter<TSource> awaiter;

        public Awaiter(Task<TSource> task)
        {
            awaiter = task.GetAwaiter();
        }

        public bool IsCompleted => awaiter.IsCompleted;    
        public TDestination GetResult() => awaiter.GetResult();    
        public void OnCompleted(Action continuation) => awaiter.OnCompleted(continuation);
    }
}

具有以下用法:

Task<...> someTask = ...;
await TaskCast<..., object>(someTask);

这种方法的局限性在于结果不是

Task<object>
而是一个可等待的对象。


3
投票

我根据dasblinkenlight的回答做了一个小小的扩展方法:

public static class TaskExtension
{
    public async static Task<T> Cast<T>(this Task task)
    { 
        if (!task.GetType().IsGenericType) throw new InvalidOperationException();

        await task.ConfigureAwait(false);

        // Harvest the result. Ugly but works
        return (T)((dynamic)task).Result;
    }
}

用途:

Task<Foo> task = ...
Task<object> = task.Cast<object>();

这样您就可以将

T
中的
Task<T>
更改为您想要的任何内容。


2
投票

对于最佳方法,不使用反射和动态丑陋语法,也不传递泛型类型。我将使用两种扩展方法来实现这个目标。

    public static async Task<object> CastToObject<T>([NotNull] this Task<T> task)
    {
        return await task.ConfigureAwait(false);
    }

    public static async Task<TResult> Cast<TResult>([NotNull] this Task<object> task)
    {
        return (TResult) await task.ConfigureAwait(false);
    }

用途:

    Task<T1> task ...
    Task<T2> task2 = task.CastToObject().Cast<T2>();

这是我的第二种方法,但不推荐

public static async Task<TResult> Cast<TSource, TResult>([NotNull] this Task<TSource> task, TResult dummy = default)
{
    return (TResult)(object) await task.ConfigureAwait(false);
}

用途:

Task<T1> task ...
Task<T2> task2 = task.Cast((T2) default);

// Or

Task<T2> task2 = task.Cast<T1, T2>();

这是我的第三种方法,但是不推荐(类似于第二种)

public static async Task<TResult> Cast<TSource, TResult>([NotNull] this Task<TSource> task, Type<TResult> type = null)
{
    return (TResult)(object) await task.ConfigureAwait(false);
}

// Dummy type class
public class Type<T>
{
}

public static class TypeExtension
{
    public static Type<T> ToGeneric<T>(this T source)
    {
        return new Type<T>();
    }
}

用途:

Task<T1> task ...
Task<T2> task2 = task.Cast(typeof(T2).ToGeneric());

// Or

Task<T2> task2 = task.Cast<T1, T2>();

0
投票

await
与动态/反射调用混合使用并不是一个好主意,因为
await
是一条编译器指令,它会围绕调用的方法生成大量代码,并且使用更多反射来“模拟”编译器工作并没有真正的意义,延续、包装等

因为您需要的是在运行时管理代码,然后忘记在编译时工作的

asyc await
语法糖。重写
SomeFunction
SomeOtherFunction
而不使用它们,并在运行时创建的您自己的任务中开始操作。您将得到相同的行为,但代码非常清晰。

© www.soinside.com 2019 - 2024. All rights reserved.