跨平台移植下C#反序列化:客户端跳过特定字段组的解决方案
这个问题我之前在跨平台项目里也碰到过,核心就是要让序列化逻辑根据运行环境(编辑器/客户端)动态调整——既要让编辑器能完整读写所有字段,又要让客户端跳过那些依赖Windows专属组件的字段,避免反序列化时抛出类型找不到的异常。下面给你几个实用的解决方案:
方案一:条件编译+序列化属性标记
这是最直接的方式,利用C#的条件编译指令,让编辑器和客户端对同一个字段采用不同的序列化规则。
首先,你需要给编辑器项目添加编译符号(比如EDITOR):在编辑器项目的属性→生成→条件编译符号里输入EDITOR;客户端项目则不添加这个符号。
然后修改你的_ev结构体:
[Serializable] public struct _ev { public int evntID; public int PositionX; public int PosotionY; public List<string> ComCode; public byte[] EventGraphics; #if EDITOR // 编辑器环境下,该字段正常参与序列化 public List<Graphics.Node> NodeGraph; //Editor only information #else // 客户端环境下,标记为不序列化,避免反序列化报错 [NonSerialized] public List<Graphics.Node> NodeGraph; //Editor only information #endif public List<pages> Pages; public List<EventItem> Event; }
这样一来,编辑器编译时会把NodeGraph当作普通可序列化字段处理;客户端编译时,这个字段会被标记为[NonSerialized],序列化和反序列化过程都会自动跳过它,也就不会因为找不到Graphics.Node类型而抛出异常了。
方案二:自定义序列化逻辑(实现ISerializable接口)
如果需要更精细的控制(比如字段较多,或者要处理复杂的序列化场景),可以让结构体实现ISerializable接口,手动控制哪些字段参与序列化/反序列化。
using System.Runtime.Serialization; [Serializable] public struct _ev : ISerializable { public int evntID; public int PositionX; public int PosotionY; public List<string> ComCode; public byte[] EventGraphics; public List<Graphics.Node> NodeGraph; //Editor only information public List<pages> Pages; public List<EventItem> Event; // 常规构造函数(根据你的需求初始化字段) public _ev(int evntId, int posX, int posY, /* 其他参数 */) { evntID = evntId; PositionX = posX; PosotionY = posY; ComCode = new List<string>(); EventGraphics = Array.Empty<byte>(); NodeGraph = new List<Graphics.Node>(); Pages = new List<pages>(); Event = new List<EventItem>(); } // 反序列化构造函数(必须是private) private _ev(SerializationInfo info, StreamingContext context) { // 读取所有客户端和编辑器共用的字段 evntID = info.GetInt32(nameof(evntID)); PositionX = info.GetInt32(nameof(PositionX)); PosotionY = info.GetInt32(nameof(PosotionY)); ComCode = (List<string>)info.GetValue(nameof(ComCode), typeof(List<string>)); EventGraphics = (byte[])info.GetValue(nameof(EventGraphics), typeof(byte[])); Pages = (List<pages>)info.GetValue(nameof(Pages), typeof(List<pages>)); Event = (List<EventItem>)info.GetValue(nameof(Event), typeof(List<EventItem>)); // 只有编辑器环境下才读取NodeGraph字段 #if EDITOR NodeGraph = (List<Graphics.Node>)info.GetValue(nameof(NodeGraph), typeof(List<Graphics.Node>)); #else NodeGraph = null; #endif } // 序列化方法 public void GetObjectData(SerializationInfo info, StreamingContext context) { // 写入所有共用字段 info.AddValue(nameof(evntID), evntID); info.AddValue(nameof(PositionX), PositionX); info.AddValue(nameof(PosotionY), PosotionY); info.AddValue(nameof(ComCode), ComCode); info.AddValue(nameof(EventGraphics), EventGraphics); info.AddValue(nameof(Pages), Pages); info.AddValue(nameof(Event), Event); // 只有编辑器环境下才写入NodeGraph字段 #if EDITOR info.AddValue(nameof(NodeGraph), NodeGraph); #endif } }
这种方式的优势是完全可控,你可以根据不同的StreamingContext或者环境变量做更复杂的判断,适合字段多、逻辑复杂的场景。
方案三:使用DataContractSerializer(适合WCF/跨语言场景)
如果你的项目用的是DataContractSerializer而非BinaryFormatter,可以结合[DataMember]属性和条件编译来实现:
using System.Runtime.Serialization; [DataContract] public struct _ev { [DataMember] public int evntID; [DataMember] public int PositionX; [DataMember] public int PosotionY; [DataMember] public List<string> ComCode; [DataMember] public byte[] EventGraphics; #if EDITOR [DataMember] // 编辑器环境下标记为可序列化字段 #endif public List<Graphics.Node> NodeGraph; //Editor only information [DataMember] public List<pages> Pages; [DataMember] public List<EventItem> Event; }
只有当EDITOR符号被定义时,NodeGraph才会被标记为[DataMember],参与序列化和反序列化;客户端环境下这个字段会被自动忽略。
注意事项
- 确保编辑器和客户端的编译符号设置正确,这是所有方案生效的前提;
- 如果你用的是
BinaryFormatter,注意结构体作为值类型的序列化特性——默认情况下所有字段都会被序列化,必须通过[NonSerialized]或自定义逻辑排除; - 如果
Graphics.Node本身不是可序列化类型,编辑器端也需要确保它实现了[Serializable]接口或ISerializable接口,否则编辑器序列化时也会报错。
内容的提问来源于stack exchange,提问作者Joe Martiniello

