使用ProtoBuf.NET进行序列化而不标记成员

时间:2011-09-30 11:32:34

标签: c# .net mono protobuf-net

我在某处读过ProtoBuf.NET的作者的评论:

  

有自动推断数字的选项,但这很脆弱,不推荐。只有当你知道你永远不需要添加更多成员时才使用它(它按字母顺序排序,因此添加新的AardvarkCount会破坏所有内容)。

这正是我感兴趣的那种情况:)

我有一些类似于map-reduce场景的东西,我希望使用协议缓冲区(例如map-reduce的“map”侧)序列化在远程机器上生成的结果,然后读取它们并将这些结果组合起来以进一步处理(例如“减少”)。

我不想在我可能在此过程中序列化的每个可能的类上启动属性修饰马拉松,并且我发现协议缓冲区非常诱人,因为我可以使用Mono创建结果并毫不费力地使用它们在MS.NET上,反之亦然......

没有预先标记成员的明显缺点并不打扰我,因为完全相同的软件修订版生成/消费,所以我不需要担心新成员弹出代码并弄乱我的整个方案...

简而言之,我的问题是:

  • 我该怎么做(使用ProtoBuf.NET序列化而不自行标记/构建Meta类)?
  • 我的计划中是否有任何漏洞让我明显错过了?

2 个答案:

答案 0 :(得分:9)

如果您可以使用单个属性,那么诀窍是:

    [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
    public class WithImplicitFields
    {
        public int X { get; set; }
        public string Y { get; set; }
    }

这里有2个选项; AllPublic的作用类似XmlSerializer - 公共属性和字段是序列化的(使用字母顺序选择标记号); AllFields有点像BinaryFormatter - 字段被序列化(再次,字母)。

我不记得v2 API上是否还提供了这个功能;我知道这是我确保工作的清单!但如果你想在没有任何属性的v2中使用它,我确信我可以添加Add(ImplicitFields)重载。

只要2个结尾永远不会失步,这很好。如果您存储数据,或者没有“在步骤中”对两端进行版本化,则可能存在问题。另请参阅关于枚举的intellisense注释(它几乎重复了您已经知道的警告)。

答案 1 :(得分:1)

我遇到了同样的问题,我是如何使用TypeModel解决它的。它基于按名称排序的属性(但它不检查属性setter / getter存在或给定类型的序列化能力):

[TestFixture]
public class InferredProtoPoc
{
    [Test]
    public void UsageTest()
    {
        var model = TypeModel.Create();
        // Dynamically create the model for MyPoco
        AddProperties(model, typeof(MyPoco));
        // Display the Generated Schema of MyPoco
        Console.WriteLine(model.GetSchema(typeof(MyPoco)));

        var instance = new MyPoco
        {
            IntegerProperty = 42,
            StringProperty = "Foobar",
            Containers = new List<EmbeddedPoco>
            {
                new EmbeddedPoco { Id = 12, Name = "MyFirstOne" },
                new EmbeddedPoco { Id = 13, Name = "EmbeddedAgain" }
            }
        };

        var ms = new MemoryStream();
        model.Serialize(ms, instance);
        ms.Seek(0, SeekOrigin.Begin);
        var res = (MyPoco) model.Deserialize(ms, null, typeof(MyPoco));
        Assert.IsNotNull(res);
        Assert.AreEqual(42, res.IntegerProperty);
        Assert.AreEqual("Foobar", res.StringProperty);
        var list = res.Containers;
        Assert.IsNotNull(list);
        Assert.AreEqual(2, list.Count);
        Assert.IsTrue(list.Any(x => x.Id == 12));
        Assert.IsTrue(list.Where(x => x.Id == 12).Any(x => x.Name == "MyFirstOne"));
        Assert.IsTrue(list.Any(x => x.Id == 13));
        Assert.IsTrue(list.Where(x => x.Id == 13).Any(x => x.Name == "EmbeddedAgain"));
    }

    private static void AddProperties(RuntimeTypeModel model, Type type)
    {
        var metaType = model.Add(type, true);
        foreach (var property in type.GetProperties().OrderBy(x => x.Name))
        {
            metaType.Add(property.Name);
            var propertyType = property.PropertyType;
            if (!IsBuiltinType(propertyType) &&
                !model.IsDefined(propertyType) && 
                propertyType.GetProperties().Length > 0)
            {

                AddProperties(model, propertyType);
            }
        }
    }

    private static bool IsBuiltinType(Type type)
    {
        return type.IsValueType || type == typeof (string);
    }
}

public class MyPoco
{
    public int IntegerProperty { get; set; }
    public string StringProperty { get; set; }
    public List<EmbeddedPoco> Containers { get; set; }
}

public class EmbeddedPoco
{
    public int Id { get; set; }
    public String Name { get; set; }
}

这就是你从跑步中得到的。

    message EmbeddedPoco {
       optional int32 Id = 1;
       optional string Name = 2;
    }
    message MyPoco {
       repeated EmbeddedPoco Containers = 1;
       optional int32 IntegerProperty = 2;
       optional string StringProperty = 3;
    }

为了提高性能,您可以选择编译TypeModel,和/或存储生成的proto以供将来使用。但要注意,如果poco(普通旧容器对象)发展,那么从长远来看,对Protocol Buffer的隐藏依赖可能是危险的。