J'ai une classe statique pleine de méthodes d'extension où chacune des méthodes est asynchrone et renvoie une valeur - comme ceci:
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;
}
}
Mon objectif est de pouvoir appeler l'une de ces méthodes à partir d'une seule méthode d'une autre classe et de renvoyer leur résultat sous forme d'objet. Cela ressemblerait à ceci:
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);
}
}
}
Le problème est que la distribution échoue. Mon dilemme est que je ne peux transmettre aucun paramètre de type à la méthode "InvokeContextExtension" car il fait partie d'un hub SignalR et est appelé par javascript. Et dans une certaine mesure, le type de résultat de la méthode d'extension ne me concerne pas, car elle va juste être sérialisée en JSON et renvoyée au client javascript. Cependant, je dois convertir la valeur renvoyée par Invoke en tant que tâche pour pouvoir utiliser l'opérateur wait. Et je dois fournir un paramètre générique avec cette "tâche", sinon le type de retour sera traité comme nul. Donc, tout se résume à comment réussir la conversion de Task avec le paramètre générique T en Task avec un paramètre générique d'objet où T représente la sortie de la méthode d'extension.
Vous pouvez le faire en deux étapes - await
la tâche en utilisant la classe de base, puis récolter le résultat en utilisant réflexion ou 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;
}
Voici un exemple complet complet qui met en contexte la technique ci-dessus consistant à appeler Task
par réflexion:
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);
}
}
En général, pour convertir un Task<T>
en Task<object>
, je choisirais simplement le mappage de continuation simple:
Task<T> yourTaskT;
// ....
Task<object> yourTaskObject = yourTaskT.ContinueWith(t => (object) t.Result);
Cependant, votre besoin spécifique actuel est d'appeler une Task
par réflexion et d'obtenir son résultat (de type inconnu).
Pour cela, vous pouvez vous référer à la réponse complète dasblinkenlight , qui devrait correspondre exactement à votre problème.
Vous ne pouvez pas convertir Task<T>
en Task<object>
, car Task<T>
n'est pas covariant (il n'est pas non plus contravariant). La solution la plus simple serait d’utiliser plus de réflexion:
var task = (Task) mi.Invoke (obj, null) ;
var result = task.GetType ().GetProperty ("Result").GetValue (task) ;
C'est lent et inefficace, mais utilisable si ce code n'est pas exécuté souvent. En passant, à quoi sert-il d'avoir une méthode MakeMyClass1 asynchrone si vous allez bloquer en attente de son résultat?
une autre possibilité est d’écrire une méthode d’extension à cette fin:
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();
}
C'est une solution non bloquante qui préservera l'état/l'exception d'origine de la tâche.
Pour la meilleure approche, sans utiliser de syntaxe laide et dynamique et sans passer de types génériques. J'utiliserais deux méthodes d'extension pour atteindre cet objectif.
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 deuxième approche _ _, mais non recommandé:
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 troisième approche _ _, mais non recommandé: (semblable au second)
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>();
J'ai créé une petite méthode d'extension basée sur la réponse de 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>();
De cette façon, vous pouvez remplacer T
dans Task<T>
par tout ce que vous voulez.
Ce n’est pas une bonne idée de mélanger await
avec un appel dynamique/réflexion puisque await
est une instruction du compilateur qui génère beaucoup de code autour de la méthode invoquée et qu’il n’ya aucun sens réel à "émuler" le travail du compilateur avec plus de réflexions, continuations, wrappers, etc. .
Puisque vous avez besoin de gérer votre code à l’exécution, oubliez le sucre de syntaxe asyc await
qui fonctionne au moment de la compilation. Réécrivez SomeFunction
et SomeOtherFunction
sans eux et démarrez les opérations dans vos propres tâches créées au moment de l'exécution. Vous obtiendrez le même comportement mais avec un code parfaitement clair.
L'approche la plus efficace serait l'attendeur personnalisé:
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);
}
}
avec l'usage suivant:
Task<...> someTask = ...;
await TaskCast<..., object>(someTask);
La limite de cette approche est que le résultat n'est pas un Task<object>
mais un objet à attendre.
Je voudrais fournir une mise en œuvre qui est à mon humble avis la meilleure combinaison des réponses précédentes:
Voici:
/// <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;
}