使用protobuf序列化F#区分的联合 [英] Serializing F# discriminated unions with protobuf

查看:78
本文介绍了使用protobuf序列化F#区分的联合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有某种方法可以使probubuf序列化/反序列化F#的已区分联盟?

我正在尝试使用protobuf序列化消息.消息是F#记录和有区别的联合.

序列化似乎可以很好地用于记录,但是我无法使其与受歧视的工会一起使用.

在下面的代码中,测试testMessageA和testMessageB为绿色.测试testMessageDU为红色.

module ProtoBufSerialization

open FsUnit
open NUnit.Framework

open ProtoBuf

type MessageA = {
  X: string;
  Y: int;
}

type MessageB = {
  A: string;
  B: string;
}

type Message =
| MessageA of MessageA
| MessageB of MessageB

let serialize msg =
  use ms = new System.IO.MemoryStream()
  Serializer.SerializeWithLengthPrefix(ms, msg, PrefixStyle.Fixed32)
  ms.ToArray()

let deserialize<'TMessage> bytes =
  use ms = new System.IO.MemoryStream(buffer=bytes)
  Serializer.DeserializeWithLengthPrefix<'TMessage>(ms, PrefixStyle.Fixed32)

[<Test>]
let testMessageA() =
  let msg = {X="foo"; Y=32}
  msg |> serialize |> deserialize<MessageA> |> should equal msg

[<Test>]
let testMessageB() =
  let msg = {A="bar"; B="baz"}
  msg |> serialize |> deserialize<MessageB> |> should equal msg

[<Test>]
let testMessageDU() =
  let msg = MessageA {X="foo"; Y=32}
  msg |> serialize |> deserialize<Message> |> should equal msg

我尝试在Message类型上添加ProtoInclude和KnownType等不同属性,在MessageA和MessageB类型上添加CLIMutable,但是似乎没有任何帮助.

我希望不必将我的DU映射到类以使序列化工作...

解决方案

我已经处理了非常有用的生成的输出,并且看起来基本上一切正常-除了 Message.MessageA子类型.这些非常接近的工作方式-它们基本上与自动元组"代码(匹配所有成员的构造函数)相同,除了自动元组当前不适用于子类型. /p>

认为应该可以通过扩展自动元组代码使其在这种情况下正常工作来调整代码以使其自动工作(我正在尝试考虑任何可能的不良影响)其中,但我看不到).我没有特定的时间范围,因为我需要在多个项目和一个全职的日间工作,一个家庭,志愿者工作等等之间平衡时间.

从短期来看,以下C#足以使其正常工作,但我不认为这将是一个有吸引力的选择:

RuntimeTypeModel.Default[typeof(Message).GetNestedType("MessageA")]
                .Add("item").UseConstructor = false;
RuntimeTypeModel.Default[typeof(Message).GetNestedType("MessageB")]
                .Add("item").UseConstructor = false;

顺便说一句,这里的属性没有帮助,应避免使用:

| [<ProtoMember(1)>] MessageA of MessageA
| [<ProtoMember(2)>] MessageB of MessageB

如果他们做了任何事情,他们将复制<ProtoInclude(n)>的意图.如果在此处指定更方便,则可能会很有趣.但是令我感到非常有趣的是,F#编译器完全忽略了AttributeUsageAttribute,对于[ProtoMember]而言,它是:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,
    AllowMultiple = false, Inherited = true)]
public class ProtoMemberAttribute {...}

是的,F#编译器显然(非法)将其粘贴在方法上:

[ProtoMember(1)]
[CompilationMapping(SourceConstructFlags.UnionCase, 0)]
public static ProtoBufTests.Message NewMessageA(ProtoBufTests.MessageA item)

顽皮的F#编译器!

Is there some way to get protobuf to serialize/deserialize F#'s discriminated unions?

I'm trying to serialize messages with protobuf. Messages are F# records and discriminated unions.

Serialization seems to work fine for records but I cannot get it to work with discriminated unions.

In the following piece of code the tests testMessageA and testMessageB are green. The test testMessageDU is red.

module ProtoBufSerialization

open FsUnit
open NUnit.Framework

open ProtoBuf

type MessageA = {
  X: string;
  Y: int;
}

type MessageB = {
  A: string;
  B: string;
}

type Message =
| MessageA of MessageA
| MessageB of MessageB

let serialize msg =
  use ms = new System.IO.MemoryStream()
  Serializer.SerializeWithLengthPrefix(ms, msg, PrefixStyle.Fixed32)
  ms.ToArray()

let deserialize<'TMessage> bytes =
  use ms = new System.IO.MemoryStream(buffer=bytes)
  Serializer.DeserializeWithLengthPrefix<'TMessage>(ms, PrefixStyle.Fixed32)

[<Test>]
let testMessageA() =
  let msg = {X="foo"; Y=32}
  msg |> serialize |> deserialize<MessageA> |> should equal msg

[<Test>]
let testMessageB() =
  let msg = {A="bar"; B="baz"}
  msg |> serialize |> deserialize<MessageB> |> should equal msg

[<Test>]
let testMessageDU() =
  let msg = MessageA {X="foo"; Y=32}
  msg |> serialize |> deserialize<Message> |> should equal msg

I tried adding different attributes like ProtoInclude and KnownType on type Message, CLIMutable on types MessageA and MessageB,... but nothing seems to help.

I'd prefer not having to map my DUs to classes to get serialization to work...

解决方案

I've played with your very helpful generated output, and it looks like basically everything works - except the Message.MessageA sub-types. These very nearly work - they are essentially the same as the "auto-tuple" code (a constructor that matches all members), except that auto-tuples doesn't currently apply to sub-types.

I think it should be possible to tweak the code to work automatically, by extending the auto-tuple code to work in this scenario (I'm trying to think of any possible bad side-effects of that, but I'm not seeing any). I don't have a specific time-frame, as I need to balance time between multiple projects and a full-time day-job, and a family, and volunteer work, and (etc etc).

In the short term, the following C# is sufficient to make it work, but I don't expect this will be an attractive option:

RuntimeTypeModel.Default[typeof(Message).GetNestedType("MessageA")]
                .Add("item").UseConstructor = false;
RuntimeTypeModel.Default[typeof(Message).GetNestedType("MessageB")]
                .Add("item").UseConstructor = false;

As an aside, the attributes here are unhelpful and should be avoided:

| [<ProtoMember(1)>] MessageA of MessageA
| [<ProtoMember(2)>] MessageB of MessageB

If they did anything, they would be duplicating the intent of <ProtoInclude(n)>. If it is more convenient to specify them there, that might be interesting, though. But what I find really interesting about that is that the F# compiler completely ignores the AttributeUsageAttribute, which for [ProtoMember] is:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,
    AllowMultiple = false, Inherited = true)]
public class ProtoMemberAttribute {...}

Yes the F# compiler clearly stuck that (illegally) on a method:

[ProtoMember(1)]
[CompilationMapping(SourceConstructFlags.UnionCase, 0)]
public static ProtoBufTests.Message NewMessageA(ProtoBufTests.MessageA item)

naughty F# compiler!

这篇关于使用protobuf序列化F#区分的联合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆