Задача 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 представляет собой вывод метода расширения.
Ответы
Ответ 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>
, а ожидаемый объект.