繁体   English   中英

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

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

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

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返回的值强制转换为任务才能使用await运算符。 我必须提供一个带有“Task”的泛型参数,否则它会将返回类型视为void。 所以这一切都归结为我如何成功地将具有泛型参数T的Task转换为具有对象的泛型参数的Task,其中T表示扩展方法的输出。

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

通常, 要将Task<T>转换为Task<object> ,我只需要进行简单的连续映射:

Task<T> yourTaskT;

// ....

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

文档链接在这里


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

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

你不能将Task<T> Task<object>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();
    }

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

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

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

干得好:

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

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

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

我根据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>();

这样,您就可以改变TTask<T>给你想要的任何东西。

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

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

最有效的方法是定制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);
    }
}

具有以下用途:

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

这种方法的局限性在于结果不是Task<object>而是一个等待对象。

暂无
暂无

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

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