protobuf C 如何启用实验性 proto2 支持并使用 HasXYZ/ClearXYZ 与 extension API

protobuf C 如何启用实验性 proto2 支持并使用 HasXYZ/ClearXYZ 与 extension API protobuf C# 如何启用实验性 proto2 支持并使用 HasXYZ/ClearXYZ 与 extension API【免费下载链接】protobufProtocol Buffers - Googles data interchange format项目地址: https://gitcode.com/GitHub_Trending/pr/protobuf如果你的 C# 项目需要与遗留系统交换 proto2 格式的数据required/optional字段、字段 presence、扩展字段就需要在 Google.Protobuf 中启用 proto2 支持。自 Google.Protobuf 3.10 版本起proto2 支持以实验性experimental方式发布它不破坏现有 proto3 用法但 proto2 相关生成代码和公共 API 可能随反馈新增、删除或调整源码中IExtendableMessageT接口也明确标注 experimental and is subject to change见 IExtendableMessage.cs。本文按 docs/csharp/proto2.md 与 csharp/README.md 说明如何生成并使用这部分 API。准备条件安装 NuGet 包与 protoc按照 csharp/README.md 的说明C# 运行时最简使用方式是通过 NuGet 包Google.Protobuf运行时库。其目标框架为 .NET 4.5net45、.NET Standard 1.1 和 2.0netstandard1.1/netstandard2.0、.NET 5net50不支持 .NET 3.5。Google.Protobuf.Tools包含预编译的protoc.exe以及包内tools目录下的 well known.proto文件副本。生成 C# 代码时对.proto文件调用protoc并加上--csharp_out选项。另外用旧编译器C# 7.2 之前编译生成代码时需要在项目中定义GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE符号使生成类不实现使用ref struct类型的IBufferMessage接口。启用 proto2在 .proto 文件中声明 syntaxproto2 特性只在带syntax proto2;声明的 proto2 文件中生效与其他语言的用法一致。文档同时强调 proto3 仍是推荐版本proto2 支持面向遗留系统互操作legacy system interop和高级用途。对这样的文件运行protoc --csharp_out后生成代码会额外包含字段 presence 和扩展相关的成员。使用 HasXYZ/ClearXYZ 处理 optional/required 字段proto2 中的 message 与 proto3 类似提供普通的属性读写同时为字段 presence 增加了属性和方法对optional/required字段XYZ生成代码包含用于检查 presence 的HasXYZ属性和用于清除值的ClearXYZ方法来源docs/csharp/proto2.md。文档中的 proto 示例message Foo { optional Bar bar 1; required Baz baz 2; }对应的文档示例代码示例结果展示 presence 行为var foo new Foo(); Assert.IsNull(foo.Bar); Assert.False(foo.HasBar); foo.Bar new Bar(); Assert.True(foo.HasBar); foo.ClearBar();即未赋值时HasBar为 false赋值后为 trueClearBar()清除后可回到未设置状态。使用 extension APIIExtendableMessage 与生成的扩展容器定义了 extension range 的 message 会实现IExtendableMessageT接口提供以下方法完整定义见 IExtendableMessage.csGetExtensionTValue(ExtensionT, TValue)读取单值扩展扩展不存在时返回默认值。GetExtensionTValue(RepeatedExtensionT, TValue)读取 repeated 扩展未设置时返回null以避免不必要的分配。GetOrInitializeExtensionTValue(RepeatedExtensionT, TValue)读取 repeated 扩展并初始化不会返回null。SetExtensionTValue(ExtensionT, TValue, TValue)设置扩展值。HasExtensionTValue(ExtensionT, TValue)判断扩展是否已设置。ClearExtension单值与 repeated 两个重载从消息中移除扩展。扩展会生成为静态容器方便取用。文档示例文件foo.proto中定义在文件作用域的扩展会生成FooExtensions类嵌套在 message 内的extend则会生成嵌套的Extensions类。可以用using static把所有扩展带入作用域option csharp_namespace FooBar; extend Foo { optional Baz foo_ext 124; } message Baz { extend Foo { repeated Baz repeated_foo_ext 125; } }生成的 C# 结构/* initialization */为文档中省略的初始化代码public static partial class FooExtensions { public static readonly ExtensionFoo, Baz FooExt /* initialization */; } public partial class Baz { public partial static class Extensions { public static readonly RepeatedExtensionFoo, Baz RepeatedFooExt /* initialization */; } }使用示例using static FooBar.FooExtensions; using static FooBar.Baz.Extensions; var foo new Foo(); foo.SetExtension(FooExt, new Baz()); foo.GetOrInitializeExtension(RepeatedFooExt).Add(new Baz());解析带扩展的消息ExtensionRegistry从输入流解析消息时扩展不会自动生效需要构造ExtensionRegistry并交给解析器。文档示例其中HasExtension的断言用于验证注册与否的差异var registry new ExtensionRegistry() { Baz.Extensions.FooExt }; var foo Foo.Factory.WithExtensionRegistry(registry).ParseFrom(input); Assert.True(foo.HasExtension(Baz.Extensions.FooExt)); var fooNoRegistry Foo.Factory.ParseFrom(input); Assert.False(fooNoRegistry.HasExtension(Baz.Extensions.FooExt));WithExtensionRegistry返回一个新的、配置了该注册表的解析器见 MessageParser.cs 中的MessageParser.WithExtensionRegistry与MessageParserT.WithExtensionRegistry。上面的示例同时给出了验证方式注册了 registry 的解析结果HasExtension为 true未注册的为 false。如果不想手工罗列扩展可以用文档中扩展后的反射 API 动态构建注册表var extensions Baz.Descriptor.Extensions.GetExtensionsInDeclarationOrder(Foo.Descriptor); var registry new ExtensionRegistry(); registry.AddRange(extensions.Select(f f.Extension)); var baz Foo.Descriptor.Parser.WithExtensionRegistry(registry).ParseFrom(input); foreach (var field in extensions) { if (field.Accessor.HasValue(baz)) { Console.WriteLine(${field.Name}: {field.Accessor.GetValue(baz)}); } }配套的新增反射成员包括FieldDescriptor.Extension取扩展字段背后的扩展标识符可加入ExtensionRegistry、FieldDescriptor.IsExtension、FieldDescriptor.ExtendeeType、IFieldAccessor.HasValue判断字段值是否已设置对 proto3 字段会抛InvalidOperationException、FileDescriptor.Syntax、FileDescriptor.Extensions和MessageDescriptor.Extensions。required 字段与 IsInitialized 检查proto2 消息中存在 required 字段时初始化指 required 字段包括子消息中的是否全部已设置。这个实现中解析器和输入流不会自行检查消息的初始化状态或抛错——处理缺失 required 字段的方式由你自行决定。文档给出的检查方法是MessageExtensions中的IsInitialized扩展方法见 MessageExtensions.cs在解析后手动调用即可判断消息是否完整。迁移限制与注意事项proto2 生成代码与公共 API 是实验性的后续版本可能新增、删除或调整迁移时不要假定当前 API 形状长期稳定。原CustomOptionsAPI 已被弃用。文档建议改用新生成的扩展标识符通过GetOptionAPI 安全地访问自定义选项repeated 字段和 message 等可克隆值会深拷贝。反射访问扩展时注意IFieldAccessor.HasValue仅适用于 proto2 字段对 proto3 字段会抛InvalidOperationException。完成上述步骤后可验证的结果是生成的 proto2 消息类具备HasXYZ/ClearXYZ成员using static引入的扩展可通过SetExtension/GetExtension读写且只有经过WithExtensionRegistry配置的解析器才能把线上扩展解析进消息以HasExtension的返回值为判断依据。【免费下载链接】protobufProtocol Buffers - Googles data interchange format项目地址: https://gitcode.com/GitHub_Trending/pr/protobuf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考