从零到一上手 Protocol Buffers用 C# 打造可演进的通讯录

一、为什么是 Protobuf(而不是 XML/自定义字符串/.NET 二进制序列化)

在需要把结构化对象持久化或跨进程/跨语言传输时,常见方案各有痛点:

  • BinaryFormatter 等 .NET 二进制序列化:对类型签名与版本极其脆弱、体积偏大,且跨语言互通性差
  • 自定义分隔字符串:一次性编码/解析成本高,健壮性与可读性差。
  • XML:可读但冗长、编解码开销大。

Protocol Buffers(Protobuf) 的优势在于:
用一个 .proto 文件声明消息结构,protoc 生成高效的 C# 类型,提供自动的二进制编码/解码;并且天然支持演进(老代码可读新消息、反之亦然)。

二、工程与示例骨架

本文示例是一个命令行通讯录工具:

  • 可新增联系人并写入文件;
  • 可读取文件并打印联系人列表。

完整示例(Program.csaddressbook.proto 等)可按本文步骤自行创建;若你使用官方示例仓库,也能在 examplescsharp/src/AddressBook 目录中找到等价实现。

三、定义协议:addressbook.proto

syntax = "proto3";
package tutorial;import "google/protobuf/timestamp.proto";// 覆盖 C# 默认命名空间(否则取 package 名)
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";message Person {string name = 1;int32  id   = 2;   // 唯一 IDstring email = 3;enum PhoneType {PHONE_TYPE_UNSPECIFIED = 0;PHONE_TYPE_MOBILE = 1;PHONE_TYPE_HOME   = 2;PHONE_TYPE_WORK   = 3;}message PhoneNumber {string   number = 1;PhoneType type  = 2;}repeated PhoneNumber phones = 4;google.protobuf.Timestamp last_updated = 5;
}message AddressBook {repeated Person people = 1;
}

要点速记

  1. package 防冲突;csharp_namespace 可定制命名空间。
  2. = 1/2/…标签号(wire tag),1–15 编码更省字节,优先留给常用repeated 字段。
  3. 未赋值字段使用类型默认值(数字 0、布尔 false、字符串空串、枚举 0 值)。
  4. repeated有序动态数组;集合类型在 C# 中为 RepeatedField<T>(只读集合,支持增删元素)。
  5. 嵌套 messageenum 提升结构清晰度;可以引入标准类型(如 Timestamp)。

四、编译生成 C# 代码

安装好 protoc 后执行:

protoc -I=$SRC_DIR --csharp_out=$DST_DIR $SRC_DIR/addressbook.proto

输出 Addressbook.cs,其中包含:

  • 静态 Addressbook(元数据);
  • AddressBookPersonPerson.Types.PhoneNumber 类;
  • Person.Types.PhoneType 枚举。

注意RepeatedField<T> 是只读集合属性,不能整体替换,只能调用 Add/Remove/Clear 等方法修改元素。

五、写与读:序列化 / 反序列化

1)写入

using System.IO;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Google.Protobuf.Examples.AddressBook;
using static Google.Protobuf.Examples.AddressBook.Person.Types;var john = new Person {Id = 1234,Name = "John Doe",Email = "jdoe@example.com",LastUpdated = Timestamp.FromDateTime(DateTime.UtcNow)
};
john.Phones.Add(new PhoneNumber { Number = "555-4321", Type = PhoneType.HOME });var book = new AddressBook();
book.People.Add(john);using var output = File.Create("addressbook.binpb");
book.WriteTo(output); // 二进制高效写入

2)读取

using var input = File.OpenRead("addressbook.binpb");
var parsed = AddressBook.Parser.ParseFrom(input);foreach (var p in parsed.People) {Console.WriteLine($"{p.Id} | {p.Name} | {p.Email}");foreach (var ph in p.Phones) {Console.WriteLine($"  - {ph.Type}: {ph.Number}");}
}

六、生成代码的常见模式

  • 属性访问:普通字段直接 get/set;repeated 用集合操作。
  • using static 简化枚举/嵌套名using static Person.Types; 之后可写 PhoneType.HOME
  • Debug 与 JSON:调试可用 ToString()(等同 JsonFormatter 的简版),生产落盘/传输请使用二进制 WriteTo/ParseFrom不要把调试输出当数据格式使用。

七、向前/向后兼容的演进法则

演进 .proto 需遵守:

  1. 不要修改已有字段的标签号
  2. 可以删除字段;
  3. 可以新增字段,但必须使用从未使用过的新标签号(包括曾被删除的号也不能复用)。

这样:

  • 旧程序读新消息:会忽略看不懂的新字段;
  • 新程序读旧消息:新加字段会呈现其默认值(比如空串/0/false)。

小贴士:把最常用/可能 repeated 的字段优先安排在 1–15,可观节省体积。

八、反射(Reflection)一键遍历任意消息

当你需要写通用逻辑(如通用打印、对比、转其它文本格式)时,反射很有用:

using Google.Protobuf;
using Google.Protobuf.Reflection;static void DumpTopLevel(IMessage msg) {var desc = msg.Descriptor;foreach (var f in desc.Fields.InDeclarationOrder()) {var val = f.Accessor.GetValue(msg);Console.WriteLine($"#{f.FieldNumber} {f.Name} = {val}");}
}

九、最佳实践与易踩坑

  • 不要把生成的 Protobuf 类当领域模型的“上帝类”:它们是数据载体。复杂业务建议用封装类包一层,便于隐藏实现细节与组合额外行为。
  • 文件扩展名约定:二进制 .binpb、文本 .txtpb、JSON .json,保持项目内一致性。
  • 调试输出 ≠ 数据格式:日志用调试格式即可;要传输/落盘请用二进制或显式 TextFormat/JSON
  • 版本演进前先留余量:给可能增长的 repeated 预留 1–15;对未来“也许有用”的字段先不上线,避免随后改 tag。
  • 单元测试:比较对象请用值相等Equals/字段比对)而非字节流相等;序列化非确定性,不同运行时/版本字节序可能不同但语义相同

十、把示例跑起来(最短路径)

  1. 安装 Google.Protobuf NuGet 与 protoc
  2. 写好 addressbook.proto
  3. 执行 protoc --csharp_out 生成 Addressbook.cs
  4. 在项目中引用生成文件与 Google.Protobuf
  5. 按第 节代码写入/读取,验证通讯录功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/99213.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/99213.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络(三)网络层

三、网络层网络层是五层模型中的第三层&#xff0c;位于数据链路层和传输层之间。它的核心任务是实现数据包在不同网络之间&#xff08;跨网络&#xff09;的逻辑传输。网络层的数据传输单位是数据报&#xff08;Datagram&#xff09;或数据包&#xff08;Packet&#xff09;。…

互联网大厂Java面试实录:从基础到微服务全栈技术答疑

互联网大厂Java面试实录&#xff1a;从基础到微服务全栈技术答疑 本文以电商场景为背景&#xff0c;展现一场互联网大厂Java开发职位的面试过程。严肃的面试官与搞笑的水货程序员谢飞机展开三轮技术问答&#xff0c;涵盖Java SE、Spring Boot、数据库、微服务、安全以及CI/CD等…

StringBuilder 深度解析:数据结构与扩容机制的底层细节

文章目录 前言 一、数据结构&#xff1a;不止是简单的字符数组 1. 核心成员变量&#xff08;定义在 AbstractStringBuilder 中&#xff09; 2. 构造器与初始容量 二、扩容机制&#xff1a;从 "不够用" 到 "换大容器" 的全过程 步骤 1&#xff1a;计算…

Elasticsearch面试精讲 Day 17:查询性能调优实践

【Elasticsearch面试精讲 Day 17】查询性能调优实践 在“Elasticsearch面试精讲”系列的第17天&#xff0c;我们聚焦于查询性能调优实践。作为全文检索与数据分析的核心引擎&#xff0c;Elasticsearch的查询性能直接影响用户体验和系统吞吐能力。在高并发、大数据量场景下&…

WPF 数据绑定模式详解(TwoWay、OneWay、OneTime、OneWayToSource、Default)

在WPF中&#xff0c;数据绑定模式&#xff08;Binding Mode&#xff09;用于指定数据流的方向。常见的模式有TwoWay、OneWay、OneTime、OneWayToSource和Default。TwoWay&#xff08;双向绑定&#xff09;&#xff1a;数据从源&#xff08;通常是ViewModel或数据上下文&#xf…

使用 NVIDIA Dynamo 部署 PD 分离推理服务

1 Dynamo 介绍 NVIDIA Dynamo 是一个开源的模块化推理框架&#xff0c;用于在分布式环境上实现生成式 AI 模型的服务化部署。Dynamo 通过动态资源调度、智能路由、内存优化与高速数据传输&#xff0c;无缝扩展大型 GPU 集群之间的推理工作负载。 Dynamo 采用推理引擎无关的设…

答题卡识别改分项目

目录 核心思路 分步实现与代码解析 1. 环境准备与工具函数定义 2. 图片预处理 3. 轮廓提取与筛选 3. 轮廓提取与筛选 4. 透视变换&#xff08;矫正倾斜答题卡&#xff09; 5. 阈值处理&#xff08;突出填涂区域&#xff09; 6. 提取选项圆圈轮廓 7. 选项轮廓排序&…

Python爬虫实战:研究Pandas,构建新浪网股票数据采集和分析系统

1. 系统概述 股票数据分析系统旨在通过自动化手段获取市场数据,进行深度分析,辅助投资决策。本系统主要包含以下核心模块: 数据爬取模块:从新浪财经获取股票列表、基本信息及历史交易数据 数据处理模块:清洗原始数据,处理缺失值与异常值,计算技术指标 分析可视化模块:…

【C++STL】list的详细用法和底层实现

&#x1f31f;个人主页&#xff1a;第七序章 &#x1f308;专栏系列&#xff1a;C&#xff0b;&#xff0b; 目录 ❄️前言&#xff1a; &#x1f308;一&#xff1a;介绍 &#x1f308;二&#xff1a;list的创建 ☀️基本框架 &#x1f319;节点类 &#x1f319;构造函…

AI大模型开发(多模态+提示词)

接着之前的例子&#xff0c;继续测试模型对话&#xff0c;今天主要测试多模态加上系统提示词。 一.多模态 多模态方法&#xff0c;主要添加了对图片的测试。 public String chatWithMessage(UserMessage userMessage){ChatResponse chatResponse qwenChatModel.chat(userMess…

Qt程序单独运行报错问题

Qt程序单独运行报错问题介绍问题原因分析解决方案&#xff08;从最佳实践到临时方法&#xff09;方法一&#xff1a;使用 windeployqt 工具&#xff08;最推荐、最规范&#xff09;方法二&#xff1a;临时修改系统 PATH&#xff08;适合开发调试&#xff09;方法三&#xff1a;…

Flask学习笔记(二)--路由和变量

一、路由Flask支持两种路由1、使用route()装饰器将URL绑定到函数app.route(/hello)def hello_world():return hello world2、使用应用程序对象的add_url_rule()函数def hello_world():return hello worldapp.add_url_rule(/, hello, hello_world)二、变量规则Flask开发中&#…

Skywalking告警配置+简易邮件告警应用配置(保姆级)

Skywalking告警配置简易邮件告警应用配置前言&#xff1a; 前文&#xff1a;SkyWalking Elasticsearch8 容器化部署指南&#xff1a;国内镜像加速与生产级调优_skywalkinges-CSDN博客 ​ SKywalking Agent配置Oracle监控插件安装指南-CSDN博客 Skywalking版本&#xff1a;V10.…

无人机如何实现图传:从原理到实战的全景解读

无人机图传的工作不是简单地把镜头的数据直接“丢”到一个屏幕上&#xff0c;而是一个由编码、传输、解码三段组成的系统。首先是视频编码&#xff1a;摄像头采集的原始画面通常需要经过编解码器压缩&#xff0c;常见标准包括H.264、H.265和VP9等。压缩的目的是减少数据量&…

AS32S601在轨重构(OTA)方案的优化与分析

摘要在轨重构&#xff08;OTA&#xff09;技术因其在航天、工业控制、物联网等领域的高可靠性和持续服务需求而备受关注。本文以国科安芯推出的AS32S601芯片为研究对象&#xff0c;深入分析其OTA方案的设计原理、技术细节及优化策略&#xff0c;并结合芯片的硬件特性&#xff0…

修复Android studio的adb无法连接手机问题

复制下面的内容到一个文本txt里面然后把里面的Android studio路径和sdk路径改成你自己的&#xff0c;然后改成把.txt改成bat 右键管理员运行 echo off REM Deep Fix for "Couldnt terminate the existing process" error REM This script will completely reset ADB …

css优化都有哪些优化方案

CSS 优化其实可以分成几个层面&#xff1a;性能优化、可维护性优化、兼容性优化以及用户体验优化。这里我帮你梳理一份比较系统的 CSS 优化方案清单&#xff0c;方便你参考&#xff1a;&#x1f539; 一、加载性能优化减少 CSS 文件体积压缩 CSS&#xff08;去掉空格、换行、注…

vue,uniapp 实现卷帘对比效果

需求&#xff1a;两张图重叠放在一起&#xff0c;拖动分割线实现卷帘对比效果&#xff0c;如图一、vue2代码 <template><div class"main"><div class"img-comparison" mousedown"startSlide"><img class"before"…

【笔记】空气弹簧概述、刚度调节原理

参考链接&#xff1a;汽车底盘空气悬架关键零部件之空气弹簧 1.概述 汽车空气弹簧&#xff08;Air Spring&#xff09;是一种以“压缩空气”作为弹性介质的悬架元件&#xff0c;用来取代传统钢制螺旋弹簧或钢板弹簧。它在乘用车、客车、重卡及轨道交通上越来越普及&#xff0…

UDP Socket 进阶:从 Echo 到字典服务器,学会 “解耦” 网络与业务

开篇&#xff1a;从 “回显” 到 “字典”&#xff0c;核心变在哪&#xff1f;上一篇我们实现了 Echo 服务器 —— 网络层和业务层是 “绑死” 的&#xff1a;网络层收到数据后&#xff0c;直接把原数据发回去。但实际开发中&#xff0c;业务逻辑会复杂得多&#xff08;比如查字…