反向代理服务类型转换错误:无法将枚举器转为IReadOnlyCollection
反向代理服务中IReadOnlyCollection类型转换异常问题
问题现象
- 仅在反向代理服务器中触发类型转换错误,正常服务器运行无异常
- 错误提示:
Unable to cast object of type 'd__1[NotificationMarkResultDto]' to type 'System.Collections.Generic.IReadOnlyCollection`1[NotificationMarkResultDto]'.
相关代码
代理服务创建逻辑
public INotificationService GetNotificationService() { return _host.Get<INotificationService>(INotificationService.NetServiceId, NetServiceFactory.Instance); } private sealed class NetServiceFactory : INetServiceFactory { public static readonly INetServiceFactory Instance = new NetServiceFactory(); /// <inheritdoc /> public string ResolveServiceAddress(string serviceKey) { throw new NotSupportedException(); } /// <inheritdoc /> public TServiceContract CreateProxy<TServiceContract>(INetServiceProxy netServiceProxy) { return NetServiceDispatchProxy.Create<TServiceContract>(netServiceProxy)!; } } internal class NetServiceDispatchProxy : DispatchProxy { private INetServiceProxy? _netServiceProxy; public static TServiceContract? Create<TServiceContract>(INetServiceProxy netServiceProxy) { object? proxy = DispatchProxy.Create<TServiceContract, NetServiceDispatchProxy>() as NetServiceDispatchProxy; if (proxy == null) return default; ((NetServiceDispatchProxy)proxy).Initialize(netServiceProxy); return (TServiceContract)proxy; } /// <inheritdoc /> protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) { var cts = new CancellationTokenSource(30000); return _netServiceProxy?.Invoke(targetMethod, args, cts.Token); } private void Initialize(INetServiceProxy netServiceProxy) { _netServiceProxy = netServiceProxy; } }
业务方法(返回IReadOnlyCollection)
public async Task<IReadOnlyCollection<NotificationMarkResultDto>> MarkNotifications(AccountId accountKey, NotificationDto[] notificationsToUpdate) { var result = new List<NotificationMarkResultDto>(notificationsToUpdate.Length); var notificationKeys = notificationsToUpdate.Select(x => x.NotificationKey).ToHashSet(); var notifications = await _uow.Notifications.GetNotifications(notificationKeys, accountKey); foreach (var updateItem in notificationsToUpdate) { var foundNotification = notifications.FirstOrDefault(x => x.NotificationKey == updateItem.NotificationKey); if (foundNotification != null) { foundNotification.Status = updateItem.Status ?? foundNotification.Status; foundNotification.Importance = updateItem.Important switch { true => NotificationImportance.High, false => NotificationImportance.Low, _ => foundNotification.Importance, }; result.Add(new NotificationMarkResultDto(updateItem.NotificationKey, foundNotification.Status, foundNotification.Importance)); } else { result.Add(new NotificationMarkResultDto(updateItem.NotificationKey, "Notificaton not found")); } } await _uow.SaveAsync(); return result; }
控制器调用代码
[HttpPost("marks")] [Produces(MediaTypeNames.Application.Json)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ValidationProblemDetails))] [ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(ProblemDetails))] [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(ProblemDetails))] public async Task<IReadOnlyCollection<NotificationMarkResultDto>> MarkNotifications([FromBody, Required] NotificationDto[] update) { return await _notificationService.MarkNotifications(params); }
完整错误堆栈
{ "type": "https://httpstatuses.com/500", "title": "Unable to cast object of type '<Enumerate>d__1[Acp.Domain.Queries.Dtos.Notifications.NotificationMarkResultDto]' to type 'System.Collections.Generic.IReadOnlyCollection`1[Notifications.NotificationMarkResultDto]'.", "status": 500, "detail": "System.InvalidCastException: Unable to cast object of type '<Enumerate>d__1[NotificationMarkResultDto]' to type 'System.Collections.Generic.IReadOnlyCollection`1[Notifications.NotificationMarkResultDto]'.\n at Net.NetCommHost.NetServiceProxy.TaskWrapper`1.<>c.<Wrap>b__0_0(Task`1 x) \n at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()\n at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)\n--- End of stack trace from previous location ---\n at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)\n at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)\n--- End of stack trace from previous location ---\n at Controllers.NotificationController.MarkNotifications(NotificationDto[] update) in /source/src/Controllers/NotificationController.cs:line 101\n at lambda_method305(Closure, Object)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|26_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)" }
疑问与额外信息
List<T>明明实现了IReadOnlyCollection<T>接口,为何会出现类型转换失败?- 是否是代理服务初始化或序列化环节存在异常?
- IDE提示显式转换代码
return (IReadOnlyCollection<NotificationMarkResultDto>)result;为冗余代码(IDE0004)
问题分析与解决方案
核心原因
错误中的<Enumerate>d__1是LINQ查询返回的迭代器状态机类型,并非实际的List<T>实例。这说明反向代理在序列化/反序列化返回结果时,没有正确还原为IReadOnlyCollection<T>兼容的类型,而是返回了一个仅实现IEnumerable<T>的迭代器对象。当控制器尝试将其强制转换为IReadOnlyCollection<T>时,因迭代器类型未实现该接口,导致转换失败。
解决方法
显式返回只读集合类型
修改业务方法的返回语句,使用AsReadOnly()返回明确实现IReadOnlyCollection<T>的类型:return result.AsReadOnly();修复代理服务序列化逻辑
检查NetServiceProxy.Invoke方法的实现,确保在处理异步任务结果时,将集合类型序列化为具体的集合(如数组、List),而非仅保留枚举器。示例调整逻辑:// 在NetServiceProxy处理返回结果时添加转换 var innerResult = await task; if (innerResult is IEnumerable enumerable) { return enumerable.Cast<object>().ToArray(); } return innerResult;临时调整控制器返回类型
若无法修改业务方法或代理逻辑,可暂时将控制器返回类型改为Task<IEnumerable<NotificationMarkResultDto>>,避免强制转换为IReadOnlyCollection<T>。此为临时方案,建议优先修复根源问题。
内容的提问来源于stack exchange,提问作者Taya
相关产品推荐
相关产品推荐

