C#:继承自Control的多类型对象经SignalR序列化后调用Draw方法问题
我来给你梳理几个贴合你场景的可行实现方案,都是基于C#和SignalR实践的实用思路:
方案1:多态抽象(最符合OOP设计的常规做法)
既然所有控件都继承自Control,最直接的方式就是在父类定义抽象的Draw方法,让每个子类各自实现专属的绘制逻辑。这样不管是本地对象还是SignalR反序列化后的实例,都能直接调用Draw自动触发对应子类的实现。
步骤示例:
- 改造父类/定义接口:
// 给Control父类添加抽象Draw方法 public abstract class Control { // 公共属性保留 public string Id { get; set; } // 抽象绘制方法 public abstract void Draw(); } // 子类实现各自的Draw逻辑 public class VideoControl : Control { public string VideoUrl { get; set; } public override void Draw() { // 视频控件专属绘制逻辑,比如加载视频资源、渲染播放控件 Console.WriteLine($"渲染视频控件:{VideoUrl}"); } } public class HtmlControl : Control { public string HtmlContent { get; set; } public override void Draw() { // HTML控件专属绘制逻辑,比如解析HTML片段、渲染到容器 Console.WriteLine($"渲染HTML控件:{HtmlContent}"); } }
- 配置SignalR支持多态序列化:
因为SignalR默认序列化只会识别父类类型,需要配置让序列化器保留类型信息,这样反序列化时才能正确还原成子类实例。
- 用Newtonsoft.Json的配置方式:
services.AddSignalR() .AddNewtonsoftJsonProtocol(options => { options.PayloadSerializerSettings.TypeNameHandling = TypeNameHandling.Auto; });
- 用System.Text.Json的配置方式(.NET Core 3.0+):
services.AddSignalR() .AddJsonProtocol(options => { options.SerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { typeInfo => { if (typeInfo.Type == typeof(Control)) { typeInfo.PolymorphismOptions = new JsonPolymorphismOptions { DerivedTypes = { new JsonDerivedType(typeof(VideoControl)), new JsonDerivedType(typeof(HtmlControl)) } }; } } } }; });
- 调用绘制:
拿到SignalR传递的List<Control>后,直接遍历调用即可:
foreach (var control in receivedControls) { control.Draw(); // 自动触发对应子类的Draw实现 }
方案2:模式匹配(适合无法修改父类的场景)
如果Control父类是第三方库无法修改,没法添加抽象方法,就用C#的模式匹配来识别子类类型,再调用专属的Draw方法。
示例代码:
foreach (var control in receivedControls) { switch (control) { case VideoControl videoCtrl: videoCtrl.Draw(); // 调用视频控件的Draw break; case HtmlControl htmlCtrl: htmlCtrl.Draw(); // 调用HTML控件的Draw break; default: // 处理未知类型,比如打日志警告 Console.WriteLine($"未识别的控件类型:{control.GetType().Name}"); break; } }
⚠️ 注意:同样需要配置SignalR的多态序列化,否则反序列化出来的会是Control父类实例,子类专属属性和方法都会丢失。
方案3:策略模式(适合控件类型较多的场景)
如果后续还要新增很多控件类型,不想写一堆switch分支,可以用策略模式把每个类型的绘制逻辑封装成独立策略,用字典做类型映射。
示例代码:
// 定义绘制策略的委托 public delegate void DrawStrategy(Control control); // 初始化策略字典 private readonly Dictionary<Type, DrawStrategy> _drawStrategies = new() { { typeof(VideoControl), ctrl => ((VideoControl)ctrl).Draw() }, { typeof(HtmlControl), ctrl => ((HtmlControl)ctrl).Draw() } }; // 调用绘制逻辑 foreach (var control in receivedControls) { if (_drawStrategies.TryGetValue(control.GetType(), out var strategy)) { strategy(control); } else { Console.WriteLine($"未找到{control.GetType().Name}的绘制策略"); } }
新增控件时,只需要给字典添加新的策略项即可,不用修改遍历逻辑,扩展性很强。
关键注意事项
- 跨项目类型一致性:确保
Control子类在两个项目中是完全相同的全限定类型(即同一个类库/共享项目),否则序列化/反序列化会因为类型不匹配失败。 - 序列化配置必加:不管用哪个方案,SignalR的序列化器必须开启多态支持,这是反序列化出子类实例的核心前提。
内容的提问来源于stack exchange,提问作者Asim Khan
相关产品推荐
相关产品推荐

