本教程提供了一个基本的C#程序员的介绍与PB的工作,使用proto3的PB语言的版本。通过创建一个简单的示例应用程序走,就说明你如何
- 定义一个消息格式
.proto
文件。 - 使用的PB编译器。
- 使用C#协议缓存API来编写和阅读邮件。
这不是一个全面的指南,在C#中使用Protocol Buffers。欲了解更多详细的参考信息,请参阅PB语言指南,在C#API参考,在C#生成的代码指南,和编码参考。
为什么要使用protobuf(PB)?
我们将使用的示例是一个非常简单的“通讯录”应用程序,它可以读取和写入人们的联系方式,并从一个文件。每个人在地址簿中有一个名字,一个ID,一个电子邮件地址和联系电话。
你怎么序列化和获取这样的结构化数据?有解决这个问题的一些方法:
- 使用使用.NET二进制序列化
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
以及相关的类。这最终被在变化,在某些情况下,数据大小方面是昂贵的面很脆弱。同时,如果你需要与其他平台编写的应用程序共享数据并不能很好地工作。 - 你可以发明一个特设的方式来编码数据项组合成一个字符串 - 诸如编码4整数为“12:3:-23:67”。这是一个简单而灵活的方法,但它确实需要书面形式一次性编码和解析代码,并解析强加一个小的运行时间成本。这最适合编码非常简单的数据。
- 数据序列化到XML。这种方法可以是人类可读的非常有吸引力的,因为XML是(那种),有获得大量语言绑定库。这可以是一个很好的选择,如果你想分享与其他应用程序/项目的数据。然而,XML是出了名的空间密集,编码/解码它可以在应用程序施加了巨大的性能损失。此外,导航一个XML DOM树显然比导航中的一类简单的领域,通常会比较复杂。
PB是灵活,高效的,自动化的解决方案来解决恰好这个问题。随着PB,你写一个.proto
要存储的数据结构的描述。由此看出,PB编译器创建实现自动编码和协议缓冲区中的数据分析与高效的二进制格式的类。生成的类提供了getter和setter方法,使一个协议缓冲区领域和需要的读取和写入协议缓存为单位的细节关怀。重要的是,PB格式支持以这样的方式,该代码仍然可以读取与旧格式的编码数据上延伸时的格式的想法。
在哪里可以找到示例代码
我们的例子是用于管理地址簿数据文件,使用PB编码的命令行应用程序。命令通讯簿
(参见: Program.cs中)可以添加新条目数据文件或解析数据文件和数据打印到控制台。
你可以找到完整的示例 示例目录 和 CSHARP / src目录/地址簿目录 GitHub的信息库。
定义你的协议格式
要创建您的地址簿应用程序,你需要开始一个 .proto
文件。在定义.proto
文件很简单:你添加一个消息,你想序列每个数据结构,然后指定一个名称和类型对消息中每个字段。在我们的例子中,.proto
定义邮件文件 addressbook.proto
。
该.proto
文件开始与包声明,这有助于防止不同项目之间的命名冲突。
在C#中,您生成的类将被放置在相匹配的命名空间 包
,如果名称csharp_namespace
没有被指定。在我们的例子中,csharp_namespace
选项已被指定覆盖默认的,所以生成的代码使用的命名空间 Google.Protobuf.Examples.AddressBook
而不是 教程
。
接下来,你有你的消息定义。有消息刚刚包含一组类型字段的集合。许多标准的简单数据类型都可以作为字段类型,包括布尔
,INT32
, 浮法
,双
和字符串
。您还可以通过使用其他的消息类型字段类型增加更多的结构,你的消息。
在上述的例子中,人
消息包含 ******中国
的消息,而所述地址簿
消息包含人
的消息。你甚至可以定义嵌套在其他消息内部消息类型-正如你所看到的, ******中国
类型中定义的人
。您还可以定义枚举
要指定一个电话号码可以是一个在这里-如果你希望你的领域之一有预定义的值列表中的一个类型MOBILE
,HOME
,或 工作
。
每个元素的“= 1”,“= 2”的标记标识唯一“标记”该领域使用在二进制编码。标签号1-15需要至少一个字节高于数字编码,从而优化您可以决定使用这些标签常用的或重复的元素,使标签16和更高的不太常用的可选元素。在重复字段中的每个元件都需要重新编码的标签号码,如此反复字段对于这种优化特别好人选。
如果未设置字段值, 默认值的方法有:零数字类型,字符串空字符串,虚假的布尔变量。对于嵌入式的消息,默认值始终是“默认实例”或邮件,其中有没有其字段设置的“原型”。调用访问得到而没有被明确设置始终返回该字段的默认值的字段的值。
如果字段重复
,所述字段可以重复任意次数(包括0)。重复值的顺序将在协议缓存被保留。想重复领域的动态大小的数组中。
你会发现一个完整的指南编写.proto
文件-包括所有可能的字段类型-中PB语言指南。不要去寻找类似的类继承的设施,虽然-PB不这样做。
编译PROTOBUF
现在,你有一个.proto
,你需要做的下一件事就是生成你需要读写类通讯簿
(因此人
与******中国
)消息。要做到这一点,你需要运行该PB编译器protoc
您.proto
:
- 如果你还没有安装编译器,下载包并按照自述文件的说明。
- 现在运行编译器,指定源目录(其中应用程序的源代码存在-在当前目录中使用,如果你不提供一个值),目标目录(您要生成的代码去;经常同
$ SRC_DIR
),和的路径.proto
。在这种情况下,你...:protoc -I = $ SRC_DIR --csharp_out = $ DST_DIR $ SRC_DIR / addressbook.proto
因为你想要的C#类,使用--csharp_out
选项-提供了其他支持的语言类似的选项。
这会产生Addressbook.cs
在你指定的目标目录。编译这段代码,你需要给一个参考项目Google.Protobuf
组装。
地址簿类
生成Addressbook.cs
给你五有用的类型:
- 静态
地址簿
类,它包含有关PB中的消息的元数据。 - 一个
通讯簿
类只读人民
财产。 - 一个
人
类的属性名称
,标识
,电子邮件
和电话
。 - 一
******中国
一流的,嵌套在一个静态Person.Types
类。 - 一个
PHONETYPE
枚举,也嵌套在Person.Types
。
您可以了解更多关于什么是在生成的细节生成的代码指南C# ,但在大多数情况下,你可以把它们作为非常普通的C#类型。有一点需要强调的是,对应于重复字段的任何属性是只读的。您可以将项目添加到集合,或者从中删除项目,但你不能用一个完全独立的集合替换它。重复字段集合类型始终是RepeatedField <T>
。这种类型是一样 的List <T>
,但有一些额外的方便的方法,如添加
超负荷接受项目的集合,用于初始化colleciton使用。
这里是你如何可以创建Person实例的例子:
人约翰=新的Person { ID = 1234, NAME =“李四”, 电子邮件=“jdoe@example.com” 手机= {新Person.Types.PhoneNumber {总数=“555-4321”,类型= Person.Types.PhoneType.HOME}} };
需要注意的是用C#6,您可以使用使用静态
去除Person.Types
丑陋:
//这个添加到其他using指令 使用静态Google.Protobuf.Examples.AddressBook.Person.Types; ... //越早电话分配现在可以简化为: 手机= {新******中国{总数=“555-4321”,类型= PhoneType.HOME}}
解析和序列化
使用PB的全部目的是序列数据,以便它可以在别处解析。每个生成的类都有的writeTo(CodedOutputStream)
方法,其中CodedOutputStream
是PB运行时库的类。但是,通常你会使用的扩展方法之一写入到正规的System.IO.Stream
或消息转换为字节数组或字节串
。这些扩展的消息都在Google.Protobuf.MessageExtensions
类,所以当你想序列,你会通常希望一个使用
指令的Google.Protobuf
命名空间。例如:
使用Google.Protobuf; ... 人约翰= ...; //守则之前 使用(VAR输出= File.Create(“john.dat”)) { john.WriteTo(输出); }
解析也很简单。每个生成的类都有一个静态分析器
,它返回一个属性MessageParser <T>
为该类型。这反过来有一些方法来分析流,字节数组和字节字符串
秒。因此,要分析我们刚刚创建的文件,我们可以使用:
人约翰; 使用(VAR输入= File.OpenRead(“john.dat”)) { 约翰= Person.Parser.ParseFrom(输入); }
一个完整的示例程序,以保持使用这些消息的地址簿(添加新条目和列出现有的),可在Github上库。
扩展一个PB
迟早你释放你的使用协议缓存代码后,你无疑要“改进”的PB的定义。如果你希望你的新的缓冲区是向后兼容的,和你的旧缓冲区以向前兼容 - 你几乎肯定不希望这样 - 然后有一些需要遵循的规则。在PB的新版本:
- 你不能改变任何现有的字段的标签号。
- 您可以删除字段。
- 您可以添加新的字段,但必须使用新的标签号(即从来没有在这个PB,即使通过删除字段中使用标签号)。
(有 一些例外这些规则,但它们很少使用。)
如果你遵循这些规则,旧的代码会高兴地阅读新邮件和根本不理会任何新的领域。老代码,这被删除只会有其默认值奇异的领域,并删除重复的领域将是空的。新的代码也将透明地读取旧消息。
但是,请记住,新的领域会不会出现在旧信息,所以你需要做一些合理的默认值。特定类型 的默认值 时:对于字符串,默认值是空字符串。对于布尔值,默认值为false。对于数字类型,默认值是零。
反射
消息描述符(在信息.proto
文件)和消息的情况下,可以以编程方式使用反射API进行检查。编写通用代码,如不同的文本格式或智能比较工具时,这可能是有用的。每个生成的类有一个静态的描述符
属性,任何实例的描述符可以通过检索IMessage.Descriptor
财产。至于如何将这些可以用一个简单的例子,这里是打印的任何消息的顶级域很短的方法。
公共无效PrintMessage(即时聊天消息) { VAR描述符= message.Descriptor; 的foreach(在descriptor.Fields.InDeclarationOrder()VAR字段) { Console.WriteLine( “现场{0}({1}):{2}”, field.FieldNumber, field.Name, field.Accessor.GetValue(消息); } }