如何使用反射调用通用异步方法

use*_*864 20 c# reflection async-await

public interface IBar {
}
public class Bar : IBar {
}
public class Bar2 : IBar {
}
public interface IFoo {
  Task<T> Get<T>(T o) where T : IBar;
}
public class Foo : IFoo {
  public async Task<T> Get<T>(T o) where T : IBar {
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

然后我可以使用反射调用此方法:

var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar2.GetType());
var task = generic.Invoke(foo, new [] { bar2 });
Run Code Online (Sandbox Code Playgroud)

我该如何等待Task?以及如何将其投射到Task<bar2.GetType()>

Sco*_*ain 35

由于Task<T>派生自Task你可以只是等待,一旦任务等待你可以使用反射来安全地访问.Result通过反射特性.

获得结果后,您需要将其存储在a中IBar并使用其上的方法和属性,或者在测试后转换为特定类型以使用特定于类型的方法.

这是一个完整的MCVE

using System;
using System.Reflection;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Test().Wait();
            Console.ReadLine();
        }

        static async Task Test()
        {
            var foo = new Foo();
            var bar2 = new Bar2();

            object resultObject = await CallGetByReflection(foo, bar2);

            IBar result = (IBar)resultObject;
            result.WriteOut();

            //or

            if (resultObject is Bar)
            {
                ((Bar)resultObject).Something();
            }
            else if (resultObject is Bar2)
            {
                ((Bar2)resultObject).SomethingElse();
            }
        }

        private static async Task<object> CallGetByReflection(IFoo foo, IBar bar)
        {
            var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
            var generic = method.MakeGenericMethod(bar.GetType());
            var task = (Task) generic.Invoke(foo, new[] {bar});

            await task.ConfigureAwait(false);

            var resultProperty = task.GetType().GetProperty("Result");
            return resultProperty.GetValue(task);
        }

        public interface IBar
        {
            void WriteOut();
        }
        public class Bar : IBar
        {
            public void Something()
            {
                Console.WriteLine("Something");
            }
            public void WriteOut()
            {
                Console.WriteLine(nameof(Bar));
            }
        }
        public class Bar2 : IBar
        {
            public void SomethingElse()
            {
                Console.WriteLine("SomethingElse");
            }
            public void WriteOut()
            {
                Console.WriteLine(nameof(Bar2));
            }
        }
        public interface IFoo
        {
            Task<T> Get<T>(T o) where T : IBar;
        }
        public class Foo : IFoo
        {
            public async Task<T> Get<T>(T o) where T : IBar
            {
                await Task.Delay(100);
                return o;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:这是一个简化过程的扩展方法

public static class ExtensionMethods
{
    public static async Task<object> InvokeAsync(this MethodInfo @this, object obj, params object[] parameters)
    {
        var task = (Task)@this.Invoke(obj, parameters);
        await task.ConfigureAwait(false);
        var resultProperty = task.GetType().GetProperty("Result");
        return resultProperty.GetValue(task);
    }
}
Run Code Online (Sandbox Code Playgroud)

这将打开CallGetByReflection

private static Task<object> CallGetByReflection(IFoo foo, IBar bar)
{
    var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
    var generic = method.MakeGenericMethod(bar.GetType());
    return generic.InvokeAsync(foo, new[] {bar});
}
Run Code Online (Sandbox Code Playgroud)

更新2:这是一个新的扩展方法,可以使用dynamic和使用任何等待类型而不是任务GetAwaiter()

public static class ExtensionMethods
{
    public static async Task<object> InvokeAsync(this MethodInfo @this, object obj, params object[] parameters)
    {
        dynamic awaitable = @this.Invoke(obj, parameters);
        await awaitable;
        return awaitable.GetAwaiter().GetResult();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Rzassar 不,不会,因为我们在 `return waitable.GetAwaiter().GetResult();` 之前做了 `await waitable;` 行,我们保证任务处于已完成状态,因此 `GetResult()` 将是一个非阻塞调用,因为结果可用。 (5认同)
  • @ygoe将包Microsoft.CSharp添加到您的项目 (2认同)
  • @ScottChamberlain 对于将扩展方法的返回类型更改为 Task&lt;object?&gt; 并返回 @ this.ReturnType == typeof(Task) 有何看法?null :awaitable.GetAwaiter().GetResult(); (2认同)

Mar*_*ini 7

除了@ScottChamberlain 的回答(这很棒)之外,我建议对InvokeAsyncreturnTask<T>而不是Task<object>. 除此之外,有第二种方法返回 会很有用Task,但InvokeAsync.

using System.Threading.Tasks;

namespace System.Reflection
{
    public static class MethodInfoExtensions
    {
        public static async Task<T> InvokeAsync<T>(this MethodInfo methodInfo, object obj, params object[] parameters)
        {
            dynamic awaitable = methodInfo.Invoke(obj, parameters);
            await awaitable;
            return (T)awaitable.GetAwaiter().GetResult();
        }

        public static async Task InvokeAsync(this MethodInfo methodInfo, object obj, params object[] parameters)
        {
            dynamic awaitable = methodInfo.Invoke(obj, parameters);
            await awaitable;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Fab*_*bio 5

根据您的示例,您知道在编译时返回的对象的类型-> IFoo,因此可以使用常规转换(IFoo)

var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(typeof(IBar));
var task = (Task<IBar>)generic.Invoke(foo, new [] { bar2 });

IBar result = await task;
Run Code Online (Sandbox Code Playgroud)

如果您在编译时不知道类型,则只需使用dynamic关键字

var method = typeof(IFoo).GetMethod(nameof(IFoo.Get));
var generic = method.MakeGenericMethod(bar2.GetType());
dynamic task = generic.Invoke(foo, new [] { bar2 });

IBar result = await task;
Run Code Online (Sandbox Code Playgroud)

但是,如果任务的类型不是Task<iFoo>在运行时-将会抛出异常
。如果您需要具体的类型,IBar

var concreteResult = Convert.ChangeType(result, bar2.GetType()); 
Run Code Online (Sandbox Code Playgroud)