简体   繁体   English

演员任务 <T> 任务 <object> 在C#没有T

[英]Cast Task<T> to Task<object> in C# without having T

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. 我的困境是我无法将任何类型参数传递给“InvokeContextExtension”方法,因为它是SignalR中心的一部分并由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. 并且在某种程度上我不关心扩展方法的返回类型,因为它只是被序列化为JSON并被发送回javascript客户端。 However I do have to cast the value returned by Invoke as a Task in order to use the await operator. 但是我必须将Invoke返回的值强制转换为任务才能使用await运算符。 And I have to supply a generic parameter with that "Task" otherwise it will treat the return type as void. 我必须提供一个带有“Task”的泛型参数,否则它会将返回类型视为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. 所以这一切都归结为我如何成功地将具有泛型参数T的Task转换为具有对象的泛型参数的Task,其中T表示扩展方法的输出。

You can do it in two steps - await the task using the base class, then harvest the result using reflection or dynamic : 您可以分两步完成 - 使用基类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;
}

Here is a complete running example that puts in context the above technique of calling Task through reflection: 这是一个完整的运行示例,它将上述通过反射调用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);
    }
}

In general, to convert a Task<T> to Task<object> , I would simply go for the straightforward continuation mapping : 通常, 要将Task<T>转换为Task<object> ,我只需要进行简单的连续映射:

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 . 但是,您实际的特定需求是通过反射调用Task并获取其(未知类型)结果

For this, you can refer to the complete dasblinkenlight's answer , which should fit your exact problem. 为此,您可以参考完整的dasblinkenlight的答案 ,它应该适合您的确切问题。

You cannot cast Task<T> to Task<object> , because Task<T> is not covariant (it's not contravariant, either). 你不能将Task<T> Task<object>Task<object> ,因为Task<T>不是协变的(它也不是逆变的)。 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? 顺便说一句,如果要阻止等待其结果,那么使用异步MakeMyClass1方法有什么用?

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. 它是无阻塞解决方案,将保留Task的原始状态/异常。

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

This is not a good idea to mix await with dynamic/reflection invoke since await is a compiler instruction that generates a lot of code around invoked method and there is no real sense to "emulate" compiler work with more reflections, continuations, wrappers and etc. await与动态/反射调用混合起来并不是一个好主意,因为await是一个编译器指令,它围绕调用的方法生成大量代码,并且没有真正意义“模拟”编译器工作的更多反射,延续,包装等等。

Since what you need is to manage your code at RUN TIME then forget the asyc await syntax sugar which works at compile time. 因为你需要的是在RUN TIME管理你的代码然后忘记在编译时工作的asyc await语法糖。 Rewrite SomeFunction and SomeOtherFunction without them, and start operations in your own tasks created at run time. 在没有它们的情况下重写SomeFunctionSomeOtherFunction ,并在运行时创建的自己的任务中启动操作。 You will get the same behavior but with crystal clear code. 您将获得相同的行为,但具有清晰的代码。

I made a little extension method based on dasblinkenlight's answer: 我根据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;
    }
}

Usage: 用法:

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

This way you can change T in Task<T> to anything you want. 这样,您就可以改变TTask<T>给你想要的任何东西。

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

The most efficient approach would be custom awaiter: 最有效的方法是定制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. 这种方法的局限性在于结果不是Task<object>而是一个等待对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM