C#中如何为Shape派生类列表匹配对应的泛型IDrawService实现
C#中如何为Shape派生类列表匹配对应的泛型IDrawService实现
这个问题我之前也碰到过——当手里拿着一堆基类Shape的实例,却要调用每个实例对应的泛型IDrawService<T>时,直接用基类遍历很容易栽在类型转换的坑里,你遇到的InvalidCastException就是因为泛型接口的不变性导致的:IDrawService<Triangle>和IDrawService<Shape>是完全不同的类型,没法直接转换。
下面给你几个实用的解决方案,你可以根据自己的业务场景选:
方案一:用字典+反射动态调用(无需修改Shape类)
这种方法适合不想改动现有Shape子类的情况,核心是先把所有服务按Shape类型缓存起来,再用反射调用对应方法:
- 先给泛型接口加一个非泛型的基接口,方便统一注入:
// 新增非泛型标记接口 public interface IDrawService { } // 让泛型接口继承它 public interface IDrawService<T> : IDrawService where T : Shape { T DrawShape(T shape); }
- 在你的业务类里注入所有
IDrawService实现,并用字典缓存类型映射:
public class ShapeDrawer { private readonly Dictionary<Type, IDrawService> _serviceMap; // 构造函数注入所有Draw服务 public ShapeDrawer(IEnumerable<IDrawService> drawServices) { _serviceMap = drawServices.ToDictionary(service => // 获取服务对应的Shape类型 service.GetType().GetInterfaces() .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDrawService<>)) .GetGenericArguments()[0]); } public void DrawAllShapes(IEnumerable<Shape> shapes) { foreach (var shape in shapes) { if (_serviceMap.TryGetValue(shape.GetType(), out var service)) { // 用反射调用DrawShape方法 var drawMethod = service.GetType().GetMethod(nameof(IDrawService<Shape>.DrawShape)); drawMethod?.Invoke(service, new[] { shape }); } } } }
这个方法的优点是不用修改现有Shape子类,缺点是用到了反射,性能上会有一点点损耗,但普通业务场景完全够用。
方案二:双重分发(类型安全,无反射)
如果允许修改Shape类,这种方法更优雅,利用双重分发让子类自己找到对应的服务:
- 先定义一个服务提供者接口(也可以直接用.NET自带的
IServiceProvider):
public interface IDrawServiceProvider { T GetService<T>() where T : class; }
- 在
Shape基类里添加抽象方法,让子类实现自己的绘制逻辑:
public abstract class Shape { public int Id { get; set; } // 新增抽象方法,让子类自己处理服务调用 public abstract void DrawWith(IDrawServiceProvider provider); } public class Triangle : Shape { public int TriangleSpecificProps { get; set; } public override void DrawWith(IDrawServiceProvider provider) { var service = provider.GetService<IDrawService<Triangle>>(); service?.DrawShape(this); } } public class Square : Shape { public int SquareSpecificProps { get; set; } public override void DrawWith(IDrawServiceProvider provider) { var service = provider.GetService<IDrawService<Square>>(); service?.DrawShape(this); } }
- 然后在业务类里注入
IDrawServiceProvider,直接调用即可:
public class ShapeDrawer { private readonly IDrawServiceProvider _serviceProvider; public ShapeDrawer(IDrawServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void DrawAllShapes(IEnumerable<Shape> shapes) { foreach (var shape in shapes) { shape.DrawWith(_serviceProvider); } } }
这个方法完全类型安全,没有反射,缺点是Shape子类需要知道IDrawService的存在,可能稍微违反单一职责,但逻辑清晰,扩展性好。
方案三:模式匹配+泛型方法(适合子类较少的场景)
如果你的Shape子类不多,用模式匹配直接分情况处理也很简单:
- 业务类注入所有
IDrawService实现:
public class ShapeDrawer { private readonly IEnumerable<IDrawService> _drawServices; public ShapeDrawer(IEnumerable<IDrawService> drawServices) { _drawServices = drawServices; } private void DrawShape<T>(T shape) where T : Shape { var service = _drawServices.OfType<IDrawService<T>>().FirstOrDefault(); service?.DrawShape(shape); } public void DrawAllShapes(IEnumerable<Shape> shapes) { foreach (var shape in shapes) { switch (shape) { case Triangle triangle: DrawShape(triangle); break; case Square square: DrawShape(square); break; // 新增子类时在这里加case default: // 处理未知Shape类型 break; } } } }
这个方法类型安全,代码直观,缺点是新增Shape子类时需要修改switch语句,扩展性稍差,适合子类数量少的场景。
备注:内容来源于stack exchange,提问作者Ryan Gaudion
相关产品推荐
相关产品推荐

