tipos qué que objeto metodos listas clases c# asynchronous casting

qué - tipos de clases en c#



Repita la tarea<T> a la tarea<objeto> en C#sin tener T (8)

El abordaje más eficiente sería el personalizador personalizado:

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

con el siguiente uso:

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

La limitación de este enfoque es que el resultado no es un Task<object> sino un objeto que se puede esperar.

Tengo una clase estática llena de métodos de extensión donde cada uno de los métodos es asíncrono y devuelve algún valor, como este

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

Mi objetivo es poder invocar cualquiera de estos métodos desde un solo método en otra clase y devolver su resultado como un objeto. Se vería algo como esto:

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

El problema es que el reparto falla. Mi dilema es que no puedo pasar ningún tipo de parámetros al método "InvokeContextExtension" porque es parte de un concentrador SignalR y es invocado por javascript. Y, hasta cierto punto, no me importa el tipo de devolución del método de extensión, ya que simplemente se serializará a JSON y se enviará de vuelta al cliente javascript. Sin embargo, tengo que convertir el valor devuelto por Invocar como una Tarea para usar el operador de espera. Y tengo que proporcionar un parámetro genérico con esa "Tarea", de lo contrario tratará el tipo de devolución como nulo. Entonces, todo se reduce a cómo puedo convertir con éxito la Tarea con el parámetro genérico T a una Tarea con un parámetro genérico del objeto donde T representa la salida del método de extensión.


En general, para convertir una Task<T> en la Task<object> , simplemente iría por la asignación de continuación directa:

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

( enlace de documentación aquí )

Sin embargo, su necesidad específica real es invocar una Task por reflexión y obtener su resultado (tipo desconocido) .

Para esto, puede consultar la respuesta completa de dasblinkenlight , que debe encajar con su problema exacto.


Esta no es una buena idea mezclar await con invocar dinámico / reflejo ya que await es una instrucción del compilador que genera una gran cantidad de código alrededor del método invocado y no tiene sentido real "emular" el trabajo del compilador con más reflejos, continuaciones, envoltorios, etc. .

Ya que lo que necesita es administrar su código en RUN TIME, entonces olvide que asyc await sintaxis de azúcar que funciona en tiempo de compilación. Reescriba SomeFunction y SomeFunction sin ellos, e inicie operaciones en sus propias tareas creadas en el tiempo de ejecución. Obtendrá el mismo comportamiento pero con código claro como el cristal.


Hice un pequeño método de extensión basado en la respuesta 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; } }

Uso:

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

De esta manera puedes cambiar T en la Task<T> a lo que quieras.


Me gustaría proporcionar una implementación que sea, en mi humilde opinión, la mejor combinación de las respuestas anteriores:

  • manejo preciso de argumentos
  • sin despacho dinámico
  • método de extensión de propósito general

Aqui tienes:

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


No puedes lanzar la Task<T> al Task<object> , porque la Task<T> no es covariante (tampoco es contravariante). La solución más simple sería usar un poco más de reflexión:

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

Esto es lento e ineficiente, pero se puede usar si este código no se ejecuta con frecuencia. Aparte de esto, ¿de qué sirve tener un método asíncrono MakeMyClass1 si va a bloquear esperando su resultado?

Y otra posibilidad es escribir un método de extensión para este propósito:

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 una solución sin bloqueo y conservará el estado / excepción original de la Tarea.


Para obtener el mejor enfoque , sin utilizar la reflexión y la sintaxis fea y dinámica, y sin pasar tipos genéricos. Yo usaría dos métodos de extensión para lograr este objetivo.

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

Uso:

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

Este es mi segundo enfoque , pero no recomendado :

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

Uso:

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

Este es mi tercer enfoque , pero no recomendado : (similar al segundo)

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

Uso:

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


Puede hacerlo en dos pasos: await la tarea usando la clase base, luego cosechar el resultado usando reflexión o 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; }

Este es un ejemplo completo de ejecución que pone en contexto la técnica anterior de llamar a la Task través de la reflexión:

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