F#结构体反序列化报错:TypedReferences字段不可为静态或仅初始化
我之前也碰到过类似的问题,结合你的场景和测试代码,这个错误的根源和解决方法可以拆解如下:
问题原因
这个报错Field in TypedReferences cannot be static or init only,本质是BinaryFormatter对F#结构体中嵌套判别联合的底层处理限制。F#的判别联合(比如你的Node类型)编译后会生成带有init only(只读)标记的底层字段,当这个判别联合作为结构体的成员时,BinaryFormatter在通过TypedReference反序列化字段时,无法绕过这些只读属性——哪怕你给结构体加了[<CLIMutable>],这个特性只能让结构体自身的记录字段变为可写,但嵌套的判别联合内部的只读字段并没有被修改,所以问题依然存在。
解决方案
1. 替换BinaryFormatter(优先推荐)
首先得提一句:BinaryFormatter早已被官方标记为过时,不仅有严重的安全风险(反序列化攻击),对F#的功能性支持也很有限。换成现代的序列化库是一劳永逸的方案,比如Newtonsoft.Json、System.Text.Json、MessagePack或者Protobuf-net,这些库对F#的判别联合、结构体兼容性更好,性能也不差。
比如用Newtonsoft.Json的示例(需要先安装Newtonsoft.Json和FSharp.Json NuGet包):
module JsonSerializer = open Newtonsoft.Json type Node = Leaf | Node of Node * Node [<Struct>] [<CLIMutable>] type Struct = { Term: byte; Node: Node } type Uni = A | B | S of Struct let test() = let data = S({ Term = 0uy; Node = Leaf }) // 开启TypeNameHandling是为了支持判别联合的类型识别 let json = JsonConvert.SerializeObject(data, JsonSerializerSettings(TypeNameHandling = TypeNameHandling.All)) let deserialized = JsonConvert.DeserializeObject<Uni>(json, JsonSerializerSettings(TypeNameHandling = TypeNameHandling.All)) printfn "Deserialized successfully: %A" deserialized JsonSerializer.test()
2. 若必须保留BinaryFormatter
如果因为项目限制一定要用BinaryFormatter,有两种变通方法:
方法一:把结构体改成类
去掉[<Struct>]特性,让Struct变成F#默认的类类型——类的成员处理逻辑和结构体不同,不会触发TypedReference的只读字段问题,代价是失去结构体的值类型性能优势:
module BinSerializerFixed = type Node = Leaf | Node of Node * Node // 移除[<Struct>],转为类 [<CLIMutable>] type Struct = { Term: byte; Node: Node } type Uni = A | B | S of Struct open System.IO open System.Runtime.Serialization.Formatters.Binary let test() = let formatter = BinaryFormatter() use stream = new MemoryStream() formatter.Serialize(stream, S({ Term = 0uy; Node = Leaf })) stream.Flush() stream.Position <- 0L let result = formatter.Deserialize(stream) :?> Uni printfn "Deserialized successfully: %A" result BinSerializerFixed.test()
方法二:给判别联合加类包装
把Node判别联合包装在一个类中,让结构体的成员变成类引用,这样BinaryFormatter处理时不会触发只读字段的问题,代价是增加一点内存开销:
module BinSerializerWrapper = type Node = Leaf | Node of Node * Node // 用类包装判别联合 type NodeWrapper(node: Node) = member _.Value = node [<Struct>] [<CLIMutable>] type Struct = { Term: byte; NodeWrapper: NodeWrapper } type Uni = A | B | S of Struct open System.IO open System.Runtime.Serialization.Formatters.Binary let test() = let formatter = BinaryFormatter() use stream = new MemoryStream() let data = S({ Term = 0uy; NodeWrapper = NodeWrapper(Leaf) }) formatter.Serialize(stream, data) stream.Flush() stream.Position <- 0L let result = formatter.Deserialize(stream) :?> Uni printfn "Deserialized successfully: %A" result BinSerializerWrapper.test()
额外提醒
再次强调:BinaryFormatter在.NET Core及以后的版本中已经被禁用,并且存在不可忽视的安全漏洞,除非有绝对必要,否则强烈建议迁移到现代序列化库。
内容的提问来源于stack exchange,提问作者LOST

