Cast Task to Task<object> in C# without h

2020-05-30 03:14发布

I have static class full of extension methods where each of the methods is asynchronous and returns some value - like this:

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

My goal is to be able to invoke any of these methods from a single method in another class and return their result as an object. It would look something like this:

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

The problem is that the cast fails. My dilemma is that I cannot pass any type parameters to the "InvokeContextExtension" method because it is part of a SignalR hub and is invoked by javascript. And to a certain extent I don't care about the return type of the extension method because it is just going to get serialized to JSON and sent back to the javascript client. However I do have to cast the value returned by Invoke as a Task in order to use the await operator. And I have to supply a generic parameter with that "Task" otherwise it will treat the return type as void. So it all comes down to how do I successfully cast Task with generic parameter T to a Task with a generic parameter of object where T represents the output of the extension method.

8条回答
兄弟一词,经得起流年.
2楼-- · 2020-05-30 03:32

You cannot cast Task<T> to Task<object>, because Task<T> is not covariant (it's not contravariant, either). The simplest solution would be to use some more reflection:

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

This is slow and inefficient, but usable if this code is not executed often. As an aside, what is the use of having an asynchronous MakeMyClass1 method if you are going to block waiting for its result?

and Another possibility is to write an extension method to this purpose:

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

It is none-blocking solution and will preserve original state/exception of the Task.

查看更多
我想做一个坏孩纸
3楼-- · 2020-05-30 03:36

I'd like to provide an implementation which is IMHO the best combination of the earlier answers:

  • precise argument handling
  • no dynamic dispatch
  • general purpose extension method

Here you go:

/// <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;
}
查看更多
放荡不羁爱自由
4楼-- · 2020-05-30 03:36

I made a little extension method based on dasblinkenlight's answer:

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

Usage:

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

This way you can change T in Task<T> to anything you want.

查看更多
贼婆χ
5楼-- · 2020-05-30 03:40

For the best approach, without using reflection and dynamic ugly syntax, and without passing generic types. I would use two extension methods for achieving this goal.

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

Usage:

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

This my second approach, but not recommended:

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

Usage:

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

// Or

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

This my third approach, but not recommended: (similar to second one)

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

Usage:

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

// Or

Task<T2> task2 = task.Cast<T1, T2>();
查看更多
劫难
6楼-- · 2020-05-30 03:41

The most efficient approach would be custom awaiter:

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

with the following usage:

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

The limitation of this approach is that the result is not a Task<object> but an awaitable object.

查看更多
戒情不戒烟
7楼-- · 2020-05-30 03:47

In general, to convert a Task<T> to Task<object>, I would simply go for the straightforward continuation mapping :

Task<T> yourTaskT;

// ....

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

(documentation link here)


However, your actual specific need is to invoke a Task by reflection and obtain its (unknown type) result .

For this, you can refer to the complete dasblinkenlight's answer, which should fit your exact problem.

查看更多
登录 后发表回答