webentwicklung-frage-antwort-db.com.de

Task <T> zu Task <Objekt> in C # umsetzen, ohne T zu haben

Ich habe eine statische Klasse voller Erweiterungsmethoden, bei denen jede der Methoden asynchron ist und einen Wert zurückgibt - wie folgt:

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

Mein Ziel ist es, jede dieser Methoden von einer einzelnen Methode in einer anderen Klasse aufrufen zu können und ihr Ergebnis als Objekt zurückzugeben. Es würde ungefähr so ​​aussehen:

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

Das Problem ist, dass die Besetzung fehlschlägt. Mein Dilemma ist, dass ich keine Typparameter an die "InvokeContextExtension" -Methode übergeben kann, da sie Teil eines SignalR-Hubs ist und von JavaScript aufgerufen wird. Bis zu einem gewissen Grad kümmere ich mich nicht um den Rückgabetyp der Erweiterungsmethode, da sie einfach zu JSON serialisiert und an den Javascript-Client zurückgesendet wird. Ich muss jedoch den von Invoke als Task zurückgegebenen Wert umsetzen, um den wait-Operator verwenden zu können. Und ich muss einen generischen Parameter mit dieser "Task" angeben, sonst wird der Rückgabetyp als ungültig behandelt. Es kommt also darauf an, wie ich Task mit dem generischen Parameter T erfolgreich in einen Task mit einem generischen Parameter des Objekts umwandle, wobei T die Ausgabe der Erweiterungsmethode darstellt.

18
ncarriker

Sie können dies in zwei Schritten tun: await die Aufgabe mit der Basisklasse und dann das Ergebnis mit Reflektion oder dynamic sammeln:

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

Hier ist ein vollständiges laufendes Beispiel, das die obige Technik des Aufrufs von Task durch Reflektion in einen Kontext setzt:

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);
    }
}
15
dasblinkenlight

Im Allgemeinen, , um einen Task<T> in Task<object> umzuwandeln, würde ich einfach die unkomplizierte Fortsetzungszuordnung wählen: 

Task<T> yourTaskT;

// ....

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

( Dokumentationslink hier )


Ihr aktuelles spezifisches Bedürfnis ist jedoch , eine Task durch Reflektion aufzurufen und das Ergebnis (unbekannten Typ) zu erhalten. 

Dazu können Sie auf die vollständige Antwort von dasblinkenlight verweisen, die genau zu Ihrem Problem passen sollte.

11
Pac0

Sie können Task<T> nicht in Task<object> umwandeln, da Task<T> nicht kovariant ist (es ist auch nicht konträr). Die einfachste Lösung wäre, mehr Reflexion zu verwenden:

var task   = (Task) mi.Invoke (obj, null) ;
var result = task.GetType ().GetProperty ("Result").GetValue (task) ;

Dies ist langsam und ineffizient, aber es kann verwendet werden, wenn dieser Code nicht oft ausgeführt wird. Nebenbei, was ist die Verwendung einer asynchronen MakeMyClass1-Methode, wenn Sie das Warten auf das Ergebnis blockieren möchten?

eine andere Möglichkeit ist, eine Erweiterungsmethode für diesen Zweck zu schreiben:

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

Es ist eine nicht blockierende Lösung und behält den ursprünglichen Zustand/die Ausnahme der Task bei.

2
M.R.Safari

Für den besten Ansatz , ohne Reflexion und dynamische, hässliche Syntax zu verwenden und ohne generische Typen zu übergeben. Ich würde zwei Erweiterungsmethoden verwenden, um dieses Ziel zu erreichen.

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

Verwendungszweck:

    Task<T1> task ...
    Task<T2> task2 = task.CastToObject().Cast<T2>();

Dieser mein zweiter Ansatz , aber nicht empfohlen :

public static async Task<TResult> Cast<TSource, TResult>([NotNull] this Task<TSource> task, TResult dummy = default)
{
    return (TResult)(object) await task.ConfigureAwait(false);
}

Verwendungszweck:

Task<T1> task ...
Task<T2> task2 = task.Cast((T2) default);

// Or

Task<T2> task2 = task.Cast<T1, T2>();

Dieser mein dritter Ansatz , aber nicht empfohlen : (ähnlich dem zweiten)

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

Verwendungszweck:

Task<T1> task ...
Task<T2> task2 = task.Cast(typeof(T2).ToGeneric());

// Or

Task<T2> task2 = task.Cast<T1, T2>();
1
shtse8

Ich habe eine kleine Erweiterungsmethode erstellt, die auf der Antwort von dasblinkenlight basiert:

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

Verwendungszweck:

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

Auf diese Weise können Sie T in Task<T> beliebig ändern.

1
Mariusz Jamro

Dies ist keine gute Idee, await mit Dynamic/Reflection-Aufruf zu mischen, da await eine Compileranweisung ist, die viel Code um die aufgerufene Methode generiert und es keinen Sinn macht, die Compilerarbeit mit mehr Reflektionen, Fortsetzungen, Wrappern usw. zu "emulieren" . 

Da Sie Ihren Code bei RUN TIME verwalten müssen, vergessen Sie den asyc await-Syntaxzucker, der zur Kompilierzeit funktioniert. Schreiben Sie SomeFunction und SomeOtherFunction ohne diese um und starten Sie Vorgänge in Ihren eigenen zur Laufzeit erstellten Aufgaben. Sie erhalten dasselbe Verhalten, jedoch mit kristallklarem Code. 

1

Der effizienteste Ansatz wäre der benutzerdefinierte Erwerber:

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

mit der folgenden Verwendung:

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

Die Einschränkung dieses Ansatzes besteht darin, dass das Ergebnis kein Task<object> ist, sondern ein zu erwartendes Objekt.

0
Andrey Nasonov

Ich möchte eine Implementierung bereitstellen, die IMHO die beste Kombination der früheren Antworten ist: 

  • präzise Argumentbehandlung
  • kein dynamischer Versand
  • allzweck-Erweiterungsmethode

Bitte schön:

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