[原创翻译]Protocol Buffer Basics: C#

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:
Protocol Buffer 基础知识:c#    原文地址: https://developers.google.com/protocol-buffers/docs/csharptutorial
 
    这篇讲解c#版的Protocol Buffers的帖子,并不是完整的指南。想了解更多的信息请查看  Protocol Buffer Language Guide,   C# API Reference,   C# Generated Code Guide和  Encoding Reference.
 
    为什么使用protocol buffers?
    我们通过一个非常简单的"地址薄"程序来讲解,这个程序可以把人们的联系方式读取和写入一个文件。每个人的地址包含一个name, ID,email,和一个联系电话phone.
    你要怎么序列化和获取这样的结构化数据?这里有几种解决方案:
    *通过使用.net的二进制序列化System.Runtime.Serialization.Formatters.Binary.BinaryFormatter和相关的类。在一些数据大小要求苛刻的情况下,面对改变,这是非常脆弱的。
     并且对于跨平台共享数据方面做的不是很好。
    *你可以用特别的方式把数据编码成一个字符串-例:把4个int型数据编码成"12:3:-23:67".尽管它需要一次性编码和解析,解析时会造成一点运行时的损耗,但是它是非常简单灵活的做法。
     对于简单的数据编码这是最好的方式。
    *把数据序列化成XML。这种方式是非常吸引人的,因为对于人类XML是通俗易懂的,并且很多语言都有相应的库。但是XML是出了名的空间密集,编码和解码会造成程序上很大的性能损失。
     并且导航一个XML DOM树通常要比导航一个类中的字段复杂的多。
    Protocol Buffe 是灵活的,高效的,自动化的解决方案,完美的解决了上述的问题。你可以写一个 .proto文件用于描述你想存储的数据结构。然后,protocol buffer 编译器创建一个类。
这个类使用高效的二进制格式实现了自动编码和解析protocol buffer 数据。这个生成的类为字段生成了getters和setters,组成一条protocol buffer。并且会像一个单元一样处理详细的读写protocol
buffer.重要的是,potocol buffer格式支持随着时间扩展的概念,使用这样的方式代码始终可以读取旧格式的数据编码。
     
     从哪找到示例代码?
     我们的例子是用管理地址薄数据文件的命令行应用程序,使用protocol buffers 编码。  AddressBook 命令(查看  Program.cs )可以把一个新的实体添加到数据文件或者解析数据文件并输出到控制台。
     你可以在GitHub的  examples directory和  csharp/src/AddressBook directory找到完整的示例代码。
 
     定义你自己的协议格式(protcol format)
     你需要使用 .proto文件来创建地址薄应用程序.在 .proto文件内定义非常简单:为每一个你想序列化的数据结构添加一个 message,在 message中为每个字段指定名称和类型。在我们的示例中,定义message 的 .proto文件在  addressbook.proto
     .proto文件的起始行声明一个包,这样可以避免在不同的项目中命名空间发生冲突。
syntax = "proto3";
package tutorial;

 

     使用c#,如果你没有指定命名空间( csharp_namespace)你生成的类会被放置到和包名称一至的命名空间中。在我们的示例中,  csharp_namespace 选项已经被重新指新值,所以生成的代码使用命名空间 Google.Protobuf.Examples.AddressBook而不是 Tutorial。   
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";

 

     下一步,定义你自己的 message . message 是字段的集合。有许多标准的简单数据类型可以供字段类型使用,包含  boolint32floatdouble, and  string.你可以通过使用其它message 类型做为字段类型,来增加更多的结构。
复制代码
message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}
复制代码

 


     在上边的示例中, Person  message包含  PhoneNumber message, AddressBook message 包含 Person message.甚至你可以在message里定义message类型- 你可以看到, PhoneNumber类型定义在 Person里。如果你想字段拥有预定义好的集合中的一个值,你可以定义枚举( enum)类型 -这里我们指定一个手机号,可以是  MOBILEHOME, 或者  MOBILEHOME, 。
     每一个元素都有 " = 1", " = 2"这样的标记,这些标记是字段在二进制编码内的唯一标识标签,标签数字1-15比更大的数字需要更少的字节编码,做为优化你可以为常用的或repeated元素使用这些标签数字,把16以上的标签留给不经常使用的元素。重复字段的每一个元素都需要重新编码标签数字,所以重复字段特别适合这种优化。
     如果字段值没有被设置,使用默认值(  default value):数值类型使用0,字符串类型使用空字符串,布尔类型为false。对于内嵌的message,如果没有给字段赋值,默认的值一般是message的"默认示例"或"原型"。如果没有显示的给字段设置值,在调用访问器时得到的是它的默认值。
     如果字段是 repeated这个字段可以重复任意次(包含0)。重复的值按序保存到protocol buffer.把重复字段相像成一个动态大小的数组。
     你可以在   Protocol Buffer Language Guide 找到一个完整编写 .proto文件的指南-包含所有可能的字段类型。不要去寻找类似于继承的功能,protocol buffer不这么做。
     编译你自己的 protocol buffers
     现在你已经有了一个 .proto.下面的事你需要做的是生成一个类,这个类用来读写 AddressBook消息(包含 Person和 PhoneNumber)。为了能完成这样的工作,你需要运行protocol buffer的编译器 protoc,编译你的 protoc文件。
     1.如果你没有安装编译器,下载  download the package后根据README的指示进行操作。
     2.现在运行你的编译器,指定源码目录(你的应用程序源码所在的文件夹-如果你不指定,会使用当前的文件夹目录),目标目录(生成代码保存的目录;一般和 $SRC_DIR是一样的),你的 .proto文件的路径,这个例子中你...:     
protoc -I=$SRC_DIR --csharp_out=$DST_DIR $SRC_DIR/addressbook.proto
     因为你使用c#类,所以使用 --csharp_out选项--相似的选项用来支持其它的语言。
     在你指定的目录生成 Addressbook.cs类。为了能编译这个代码,你需要有一个引用了  Google.Protobuf程序集的工程。
 
     地址薄类
     生成的 Addressbook.cs提供了5种有用的类型:
     1.一个静态的 AddressBook类,包含protocol buffer message的元数据。
     2.一个有只读 People属性类 AddressBook。
     3.一个拥有的  NameIdEmail和 Phones属性的类 Person。
     4.一个 PhoneNumber类,嵌入在静态的类 Person.Types中。
     5.一个 PhoneType枚举,也嵌入在 Person.Types中。
 
     你可以在  C# Generated Code guide阅读更多更详细的的生成信息。但是大部分你都可以把它们看成普通的c#类型。需要声明的一点重复字段(repeated fields)是只读的。你可以添加或删除项,但是不能使用一个完全独立的集合替换它。重复字段的集合类型是 RepeatedField<T>。这个类型像 List<T>一样,只是多了一些额外的便利方法,就像 Add方法的重载可以接收项的集合,用于集合的初始化。
     下边是一个例子用于说明如何创建一个Person的实例:
复制代码
Person john = new Person
{
    Id = 1234,
    Name = "John Doe",
    Email = "jdoe@example.com",
    Phones = { new Person.Types.PhoneNumber { Number = "555-4321", Type = Person.Types.PhoneType.HOME } }
};
复制代码

 

     注意如果使用c#6,你可以使用 using static删除 Person.Types。
 
// Add this to the other using directives
using static Google.Protobuf.Examples.AddressBook.Person.Types;
...
// The earlier Phones assignment can now be simplified to:
Phones = { new PhoneNumber { Number = "555-4321", Type = PhoneType.HOME } }

 

     解析和序列化
     使用protocol buffers的目的是序列化你的数据,在任何地方都可以解析。所有生成的类都有一个 WriteTo(CodedOutputStream)方法, CodedOutputStream是prtocol buffer运行时库的一个类。然而,一般情况下你可以使用扩展方法之一来写入到一个常规的 System.IO.Stream或者把message转换成二进制数组或者 ByteString.这些扩展信息在 Google.Protobuf.MessageExtensions类,所以当你想序列化时,你需要使用 using引入 Google.Protobuf命名空间,例:
复制代码
using Google.Protobuf;
...
Person john = ...; // Code as before
using (var output = File.Create("john.dat"))
{
    john.WriteTo(output);
}
复制代码

 


     解析也是非常简单的。每一个生成的类都有一个静态的属性 Parser,为这个类型返回 MessageParser<T>。反过来有一个方法用来解析流,二进制数组和 ByteString。用来解析我们刚创建的文件,我们可以这么做:
Person john;
using (var input = File.OpenRead("john.dat"))
{
    john = Person.Parser.ParseFrom(input);
}

 


     使用这些信息维护一个addressbook(添加一个新实体,和现有的清单) 的完整的示例程序在这里  in the Github repository
 
     扩展 Protocol Buffer
    迟早你会发布使用protocol buffer的代码,毫无疑问的你想改善protocol buffer的定义。如果你想要新的buffers向后兼容,并且你的旧buffers向前兼容-不用说你一定想要这个-这里有一些规则你需要遵守。在新版本的protocol buffer:
     1.你不能改变已有字段的数字标签.
     2.你不能删除字段.
     3.添加新字段的时候一定要用没使用过的标签数字(也就是说,你不能使用已经使用过的标签数字,删除字段的标签数字也不要用)。
    如果你遵守这些规则,老的代码会非常高兴的读取新消息,并且忽略你新添加的字段。对于旧代码来说,删除的单独字段也会有它们的默认值,删除的repeated字段会被赋为空。新代码也会透明的读取旧的消息。
 
    反射
    消息描述(文件的内容信息)消息的实例可使用反射api进行检验。对于编写不同文本格式的的代码或智能比较工具是非常有用的。每一个生成的类都有一个静态的 Descriptor属性,可以使用 IMessage.Descriptor属性获取任意实例的描述。下面是一个如何使用上述内容的简单例子,一个用来打印任意message的顶级字段的简单的方法。
复制代码
public void PrintMessage(IMessage message)
{
    var descriptor = message.Descriptor;
    foreach (var field in descriptor.Fields.InDeclarationOrder())
    {
        Console.WriteLine(
            "Field {0} ({1}): {2}",
            field.FieldNumber,
            field.Name,
            field.Accessor.GetValue(message);
    }
}
复制代码

 本文转自lpxxn博客园博客,原文链接:http://www.cnblogs.com/li-peng/p/5174575.html,如需转载请自行联系原作者

相关文章
|
文字识别 监控 算法
Baumer工业相机堡盟相机使用BGAPISDK联合Halcon直接进行彩色图像显示(Color)(C#)(将图像数据Buffer转为HObject)
Baumer工业相机堡盟相机使用BGAPISDK联合Halcon直接进行彩色图像显示(Color)(C#)(将图像数据Buffer转为HObject)
155 0
|
文字识别 监控 算法
Baumer工业相机堡盟相机使用BGAPISDK联合Halcon直接进行图像显示(Mono)(C#)(将图像数据Buffer转为HObject)
Baumer工业相机堡盟相机使用BGAPISDK联合Halcon直接进行图像显示(Mono)(C#)(将图像数据Buffer转为HObject)
130 0
|
文字识别 监控 算法
Baumer工业相机堡盟相机BGAPI SDK如何联合Halcon进行图像转换(C#)(将图像数据Buffer转为HObject)
Baumer工业相机堡盟相机BGAPI SDK如何联合Halcon进行图像转换(C#)(将图像数据Buffer转为HObject)
164 0
|
6月前
|
开发框架 前端开发 .NET
C#编程与Web开发
【4月更文挑战第21天】本文探讨了C#在Web开发中的应用,包括使用ASP.NET框架、MVC模式、Web API和Entity Framework。C#作为.NET框架的主要语言,结合这些工具,能创建动态、高效的Web应用。实际案例涉及企业级应用、电子商务和社交媒体平台。尽管面临竞争和挑战,但C#在Web开发领域的前景将持续拓展。
194 3
|
6月前
|
SQL 开发框架 安全
C#编程与多线程处理
【4月更文挑战第21天】探索C#多线程处理,提升程序性能与响应性。了解C#中的Thread、Task类及Async/Await关键字,掌握线程同步与安全,实践并发计算、网络服务及UI优化。跟随未来发展趋势,利用C#打造高效应用。
199 3
|
16天前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
29 3
|
1月前
|
安全 C# 数据安全/隐私保护
实现C#编程文件夹加锁保护
【10月更文挑战第16天】本文介绍了两种用 C# 实现文件夹保护的方法:一是通过设置文件系统权限,阻止普通用户访问;二是使用加密技术,对文件夹中的文件进行加密,防止未授权访问。提供了示例代码和使用方法,适用于不同安全需求的场景。
105 0
|
2月前
|
API C#
C# 一分钟浅谈:文件系统编程
在软件开发中,文件系统操作至关重要。本文将带你快速掌握C#中文件系统编程的基础知识,涵盖基本概念、常见问题及解决方法。文章详细介绍了`System.IO`命名空间下的关键类库,并通过示例代码展示了路径处理、异常处理、并发访问等技巧,还提供了异步API和流压缩等高级技巧,帮助你写出更健壮的代码。
42 2
|
2月前
|
安全 程序员 编译器
C#一分钟浅谈:泛型编程基础
在现代软件开发中,泛型编程是一项关键技能,它使开发者能够编写类型安全且可重用的代码。C# 自 2.0 版本起支持泛型编程,本文将从基础概念入手,逐步深入探讨 C# 中的泛型,并通过具体实例帮助理解常见问题及其解决方法。泛型通过类型参数替代具体类型,提高了代码复用性和类型安全性,减少了运行时性能开销。文章详细介绍了如何定义泛型类和方法,并讨论了常见的易错点及解决方案,帮助读者更好地掌握这一技术。
74 11