泛型类型困境:如何避免服务类泛型参数膨胀?
解决泛型参数冗长的几种实践方案
针对你遇到的泛型参数随Input属性类型增多而失控的问题,结合垂直用例的场景,推荐以下几种可行方案:
1. 拆分泛型职责,避免基类过度泛化
如果并非所有服务都需要直接操作Input的泛型属性,可以将服务基类拆分为两层:
- 基础服务基类:仅依赖
Result和Input的非泛型抽象,不关心内部属性类型 - 增强服务基类:针对需要处理特定泛型属性的场景,额外添加泛型参数
示例代码:
// 保留非泛型Input基类 abstract class Input { } abstract class Input<TPropType> : Input where TPropType : PropType { public TPropType Prop { get; set; } } // 基础服务接口与基类(无PropType参数) interface IService<TResult, TInput> where TResult : Result where TInput : Input { TResult Get(TInput input); } abstract class Service<TResult, TInput> : IService<TResult, TInput> where TResult : Result where TInput : Input { ... } // 仅对需要操作Prop的服务,提供增强基类 interface IServiceWithProp<TResult, TInput, TPropType> : IService<TResult, TInput> where TResult : Result where TInput : Input<TPropType> where TPropType : PropType { } abstract class ServiceWithProp<TResult, TInput, TPropType> : Service<TResult, TInput>, IServiceWithProp<TResult, TInput, TPropType> where TResult : Result where TInput : Input<TPropType> where TPropType : PropType { // 在这里实现需要操作TPropType的逻辑 } // 具体服务仅在需要时继承增强基类 class ConcreteService : ServiceWithProp<ConcreteResult, ConcreteInput, ConcretePropType>, IConcreteService { ... }
2. 利用C# 12+泛型别名简化重复参数
对于垂直用例中固定的泛型组合,可以用泛型别名将冗长的参数串封装为一个简洁的名称,减少重复书写:
// 为当前用例的服务基类创建别名 using ConcreteServiceBase = Service<ConcreteResult, ConcreteInput, ConcretePropType>; // 具体服务直接继承别名类 class ConcreteService : ConcreteServiceBase, IConcreteService { ... }
如果用例数量较多,可以将别名放在对应用例的命名空间下,进一步提升代码整洁度。
3. 用类型关联容器聚合垂直用例类型
针对垂直用例的特性,创建一个用例定义类,将当前用例的Result、Input、PropType等类型聚合在一起,再让服务基类依赖这个容器类,从而将多个泛型参数合并为一个:
// 用例定义接口 interface IUseCase { public abstract type ResultType : Result; public abstract type InputType : Input; public abstract type PropType : PropType; } // 具体用例的类型聚合 class ConcreteUseCase : IUseCase { public using ResultType = ConcreteResult; public using InputType = ConcreteInput; public using PropType = ConcretePropType; } // 基于用例容器的服务基类 abstract class Service<TUseCase> where TUseCase : IUseCase { public TUseCase.ResultType Get(TUseCase.InputType input) { // 若需要访问PropType,可通过TUseCase.PropType引用 var prop = ((Input<TUseCase.PropType>)input).Prop; // 业务逻辑实现 } } // 具体服务实现 class ConcreteService : Service<ConcreteUseCase>, IConcreteService { ... }
这种方式完美适配垂直用例的场景,所有关联类型都集中在一处,后续新增属性类型时,只需在IUseCase和具体用例类中扩展即可,不会扩散到服务的泛型参数列表。
4. 隐藏内部泛型参数,通过接口暴露非泛型入口
如果服务的调用方不需要感知内部泛型参数,可以定义一个非泛型的服务接口,让泛型服务类实现该接口,对外隐藏复杂的泛型细节:
// 非泛型服务接口 interface IConcreteService { ConcreteResult Get(ConcreteInput input); } // 泛型基类无需在对外接口中暴露 class ConcreteService : Service<ConcreteResult, ConcreteInput, ConcretePropType>, IConcreteService { // 实现非泛型接口方法,内部复用泛基类逻辑 public new ConcreteResult Get(ConcreteInput input) => base.Get(input); }
这种方式可以让调用方只依赖简洁的非泛型接口,同时内部保留泛型的灵活性。
内容的提问来源于stack exchange,提问作者Random12b3
相关产品推荐
相关产品推荐

