反射调用PagedList.Create返回类型不符引发类型转换异常
结论
这种行为是正常的,核心原因不是dynamic,而是泛型类型的不变性限制,dynamic只是让这个问题更直接地暴露出来了。
原因分析
反射调用的类型特性
反射调用PagedList<T>.Create时,虽然方法声明返回IPagedList<T>,但反射Invoke方法会返回实例的实际类型PagedList<T>——这和直接调用的运行时行为一致,只是直接调用时编译器会帮你隐式把PagedList<T>转换为IPagedList<T>。而用dynamic接收时,编译阶段不会做类型转换,会保留实例的具体类型信息。泛型不变性导致的转换失败
你的ApiResult<T>是泛型类(而非协变泛型接口),CLR对泛型类的类型参数是不变的:即使PagedList<ProgramResponse>实现了IPagedList<ProgramResponse>,ApiResult<PagedList<ProgramResponse>>和ApiResult<IPagedList<ProgramResponse>>也是完全独立的封闭泛型类型,无法直接互相转换。直接调用时没问题,是因为你拿到的是
IPagedList<T>类型的实例,包装进的是ApiResult<IPagedList<T>>;而反射用dynamic接收后,你实际包装的是ApiResult<PagedList<T>>,当要转成ApiResult<IPagedList<T>>时,就触发了InvalidCastException。
解决方案
方案1:将ApiResult改为协变泛型接口(推荐,若业务允许)
如果ApiResult<T>的类型参数T只作为输出使用(没有输入型的方法参数),可以定义协变接口:
public interface IApiResult<out T> { T Data { get; } // 其他只读属性或无T作为参数的方法 } public class ApiResult<T> : IApiResult<T> { public T Data { get; } // 构造函数及其他实现 }
之后就可以将IApiResult<PagedList<ProgramResponse>>隐式转换为IApiResult<IPagedList<ProgramResponse>>。
方案2:反射调用后显式转换为接口类型
不要用dynamic接收,直接将反射返回值强制转换为IPagedList<T>:
// 假设已获取到Create方法和参数 var createMethod = typeof(PagedList<ProgramResponse>).GetMethod("Create"); var pagedList = (IPagedList<ProgramResponse>)createMethod.Invoke(null, parameters); var apiResult = new ApiResult<IPagedList<ProgramResponse>>(pagedList);
方案3:使用dynamic时显式转换后再包装
如果必须用dynamic,在包装进ApiResult前手动转换类型:
dynamic dynamicPagedList = createMethod.Invoke(null, parameters); IPagedList<ProgramResponse> pagedList = dynamicPagedList; var apiResult = new ApiResult<IPagedList<ProgramResponse>>(pagedList);
异常栈示例
System.InvalidCastException: 无法将ApiResult
1[PagedList1[ProgramResponse]]转换为ApiResult1[IPagedList1[ProgramResponse]]。
在 Demo.Controllers.ProgramController.GetPrograms() 位置 D:\Projects\Demo\Controllers\ProgramController.cs:行号 42
在 Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
在 Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask actionResultValueTask)
内容的提问来源于stack exchange,提问作者Mammt

