2017-12-30 1 views
13

Ich habe statische Klasse voller Erweiterungsmethoden, wobei jede der Methoden asynchron ist und gibt einen gewissen Wert - wie folgt aus:Cast Aufgabe <T> zur Aufgabe <object> in C#, ohne T mit

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, um eine dieser Methoden von einer einzelnen Methode in einer anderen Klasse aufzurufen 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, weil sie Teil eines SignalR-Hubs ist und von JavaScript aufgerufen wird. Und bis zu einem gewissen Grad interessiert mich der Rückgabetyp der Erweiterungsmethode nicht, weil er einfach zu JSON serialisiert und an den Javascript-Client zurückgesendet wird. Allerdings muss ich den von Invoke als Aufgabe ausgegebenen Wert umwandeln, um den hawn-Operator zu verwenden. Und ich muss einen generischen Parameter mit dieser "Aufgabe" angeben, ansonsten wird der Rückgabetyp als ungültig behandelt. Es kommt also darauf an, wie Task erfolgreich mit dem generischen Parameter T zu einer Task mit einem generischen Parameter des Objekts umgesetzt wird, wobei T die Ausgabe der Erweiterungsmethode darstellt.

+2

Warum nicht die Basisklasse "Task" verwenden? Sie müssen Reflektionen machen, um das Ergebnis wieder heraus zu bekommen. Oder schreiben Sie eine Methode auf Papier über die Unterschiede für Sie: 'async Aufgabe GetResult (Aufgabe Aufgabe) {Rückkehr warten Aufgabe; } ' –

Antwort

6

Sie können es in zwei Schritten tun - await die Aufgabe, die Basisklasse verwenden, dann ernten das Ergebnis Reflexion oder dynamic mit:

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

Dies ist das komplette Lauf Beispiel, das die obige Technik in Zusammenhang setzt der Task durch Reflexion Aufruf:

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

Im Allgemeinen ein Task<T>-Task<object>, konvertieren ich einfach für das einfache Fortsetzung Mapping gehen würde:

Task<T> yourTaskT; 

// .... 

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

(documentation link here)


jedoch Ihr tatsächlicher spezifischer Bedarf , um eine Task durch Reflexion aufzurufen und sein Ergebnis (unbekannter Typ) zu erhalten.

Hierzu können Sie sich auf die vollständige dasblinkenlight's answer beziehen, die genau zu Ihrem Problem passen sollte.

+0

OPs Problem ist, dass er die Aufgabe durch Reflexion anruft, also hat er kein' T', und kann 'yourTaskT' nicht machen. Das Beste, was er tun kann, ist "Aufgabe", aber dann wäre er nicht in der Lage, '' Ergebnis "in' ContinueWith' zu tun. – dasblinkenlight

+0

ja. Leider scheint es, dass ich mich zu sehr auf die allgemeine Titelfrage konzentriert habe, anstatt auf das eigentliche OP-Problem. – Pac0

+0

Ich stimme zu, der Titel und die Frage sind nicht perfekt aufeinander abgestimmt. Der Titel ist einfach, aber die Frage hat eine wichtige Wendung. Während Ihre Antwort das Problem von OP nicht lösen kann, passt es perfekt zum aktuellen Titel. Ich würde es nicht löschen - stattdessen würde ich bearbeiten, um einen zweiten Teil hinzuzufügen, der zeigt, wie man eine Aufgabe mit unbekanntem Ergebnistyp durch Reflektion aufruft. – dasblinkenlight

2

Sie nicht Task<T>-Task<object> werfen kann, weil Task<T> nicht covariant ist (es ist nicht kontra, eith er). Die einfachste Lösung wäre es, etwas mehr Reflexion zu nutzen:

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

Diese langsam und ineffizient ist, aber brauchbar, wenn dieser Code nicht oft ausgeführt wird. Nebenbei: Was nutzt eine asynchrone MakeMyClass1-Methode, wenn Sie auf das Ergebnis warten wollen?

und andere Möglichkeit ist, eine Erweiterungsmethode zu diesem 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 keine blockierungs ist Lösung und wird Urzustand/Ausnahme der Aufgabe zu erhalten.Diese

+1

Während Ihre erste Lösung definitiv funktionieren wird, wäre OP nicht in der Lage, Ihre zweite Lösung zu nutzen, weil er kein 'T' für diesen Task-Teil des Aufrufs hat. – dasblinkenlight

+0

@dasblinkenlight es ist nur für die Zukunft :) vielleicht jemand anderes kommt an dieser Stelle und brauche diese. Ich wollte nur eine vollständige Antwort haben. natürlich sagte ich "Eine andere Möglichkeit". Danke für das ansprechende Duo. –

+1

Dies ist eine nette Lösung, da es den Stornierungsstatus beibehält. Wenn Sie das nicht interessiert, dann 'public static async Aufgabe Convert (diese Aufgabe t) => erwarten t;' ist sehr übersichtlich. –

0

ist keine gute Idee, await mit dynamischem/Reflexions seit await aufrufen zu mischen ist eine Compiler Anweisung, die eine Menge Code generiert rund Methode aufgerufen, und es gibt keinen wirklichen Sinn zu „emulieren“ Compiler Arbeit mit mehreren Reflexionen, Fortsetzungen , Wrapper und etc.

Da, was Sie brauchen, ist es, Ihren Code zu RUN TIME zu verwalten, dann vergessen Sie die asyc await Syntax Zucker, die zur Kompilierzeit funktioniert. Schreiben Sie SomeFunction und SomeOtherFunction ohne sie um und starten Sie Operationen in Ihren eigenen Aufgaben, die zur Laufzeit erstellt werden. Sie erhalten das gleiche Verhalten, aber mit kristallklarem Code.

+0

Ich schätze Ihre Einsicht und ich kann sehen, wo dies in vielen Situationen die ideale Umsetzung wäre. Leider ist es momentan keine praktische Lösung für mich, da das asynchrone Muster bereits etabliert ist und bei einer Reihe von Projekten erhebliche Auswirkungen hätte und ich hätte einige unglückliche Teammitglieder, wenn ich es jetzt ändere. – ncarriker

0

Der effizienteste Ansatz würde benutzerdefinierte Erwartenden sein:

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 folgenden Nutzung:

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

Die Einschränkung dieses Ansatzes ist, dass das Ergebnis ist keine Task<object> sondern ein awaitable Objekt.

Verwandte Themen