在没有T

时间:2017-12-30 12:03:35

标签: 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;
  }
}

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

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

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

8 个答案:

答案 0 :(得分:17)

您可以分两步完成 - 使用基类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);
    }
}

答案 1 :(得分:12)

一般来说,要将Task<T>转换为Task<object> ,我只想进行简单的延续映射:

Task<T> yourTaskT;

// ....

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

documentation link here

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

为此,您可以参考完整的dasblinkenlight's answer,它应该符合您的确切问题。

答案 2 :(得分:3)

您无法将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();
    }

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

答案 3 :(得分:1)

library(dplyr) a123_r <- a123 %>% group_by(a1) %>% mutate(a3 = sort(a3, decreasing = TRUE)[rank(-a2, ties.method = "last")]) %>% ungroup() %>% as.data.frame() a123_r # a1 a2 a3 # 1 A 10 123 # 2 B 8 78 # 3 C 11 76 # 4 A 6 55 # 5 B 4 33 # 6 B 7 34 # 7 A 9 85 # 8 C 1 33 # 9 A 3 23 # 10 C 2 34 # 11 B 7 74 与动态/反射调用混合起来不是一个好主意,因为await是一个编译器指令,它会围绕调用的方法生成大量代码,而且#34没有真正的意义。 ;模拟&#34;编译器使用更多反射,延续,包装等。

因为您需要在RUN TIME管理代码,所以忘记在编译时工作的await语法糖。在没有它们的情况下重写asyc awaitSomeFunction,并在运行时创建的自己的任务中开始操作。您将获得相同的行为,但具有清晰的代码。

答案 4 :(得分:1)

我根据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>更改为所需的任何内容。

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

我想提供的实现是恕我直言,它是先前答案的最佳组合:

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

您在这里:

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

答案 7 :(得分:0)

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

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>,而是一个等待的对象。