You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

反向代理服务类型转换错误:无法将枚举器转为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>时,因迭代器类型未实现该接口,导致转换失败。

解决方法

  1. 显式返回只读集合类型
    修改业务方法的返回语句,使用AsReadOnly()返回明确实现IReadOnlyCollection<T>的类型:

    return result.AsReadOnly();
    
  2. 修复代理服务序列化逻辑
    检查NetServiceProxy.Invoke方法的实现,确保在处理异步任务结果时,将集合类型序列化为具体的集合(如数组、List),而非仅保留枚举器。示例调整逻辑:

    // 在NetServiceProxy处理返回结果时添加转换
    var innerResult = await task;
    if (innerResult is IEnumerable enumerable)
    {
        return enumerable.Cast<object>().ToArray();
    }
    return innerResult;
    
  3. 临时调整控制器返回类型
    若无法修改业务方法或代理逻辑,可暂时将控制器返回类型改为Task<IEnumerable<NotificationMarkResultDto>>,避免强制转换为IReadOnlyCollection<T>。此为临时方案,建议优先修复根源问题。


内容的提问来源于stack exchange,提问作者Taya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 23:10:55