Подтвердить что ты не робот

Задача Cast <T> Задача <объект> в С# без 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 как задачу, чтобы использовать оператор ожидания. И я должен предоставить общий параметр этой "Задачей", иначе он будет обрабатывать возвращаемый тип как void. Таким образом, все сводится к тому, как успешно передать задачу с общим параметром T в задачу с общим параметром объекта, где T представляет собой вывод метода расширения.

4b9b3361

Ответ 1

Вы можете сделать это в два этапа - 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);
    }
}

Ответ 2

В общем случае , чтобы преобразовать a Task<T> в Task<object>, я просто пошел бы на прямое отображение продолжения:

Task<T> yourTaskT;

// ....

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

(ссылка для документации здесь)


Однако ваша фактическая конкретная потребность вызывает Task путем отражения и получает результат (неизвестный тип).

Для этого вы можете обратиться к полному ответу dasblinkenlight, который должен соответствовать вашей точной проблеме.

Ответ 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();
    }

Это неблокирующее решение и сохранит исходное состояние/исключение задачи.

Ответ 4

Для наилучшего подхода, без использования отражения и динамического уродливого синтаксиса, и без прохождения общих типов. Для достижения этой цели я бы использовал два метода расширения.

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

Ответ 5

Не рекомендуется смешивать await с вызовом dynamic/reflection, так как await - это команда компилятора, которая генерирует много кода вокруг вызываемого метода, и нет никакого смысла "эмулировать" работу компилятора с большим количеством отражения, продолжения, обертки и т.д.

Так как вам нужно управлять своим кодом в RUN TIME, забудьте о синтаксическом сателлите asyc await, который работает во время компиляции. Перепишите SomeFunction и SomeOtherFunction без них и начните операции в своих собственных задачах, созданных во время выполнения. Вы получите то же поведение, но с кристально чистым кодом.

Ответ 6

Я сделал небольшой метод расширения, основанный на ответе 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> на все, что захотите.

Ответ 7

Самый эффективный подход - пользовательский 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>, а ожидаемый объект.