【问题标题】:Reading a protobuf3 custom option from C#从 C# 读取 protobuf3 自定义选项
【发布时间】:2016-08-24 07:12:30
【问题描述】:

TL;DR

根据文档,如果我使用 C++,我可以使用 string value = MyMessage::descriptor()->options().GetExtension(my_option); 读取自定义选项的值。 Java 和 Python 也有类似的示例。但我正在做 C#,我可以找到一个等价物。我可以做吗?如果可以,怎么做?

更多详情

我正在处理使用protobuf3 生成的类。这些模式声明了custom option。它看起来像这样:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {
  string my_option = 51234;
}

message MyMessage {
  option (my_option) = "Hello world!";
}

我的代码被提供了一个从MyMessage 生成的对象,我想读取这个选项的值(这里是Hello world!


更新:我没有使用 protobuf-net。现在 protobuf 原生支持 C#,我正在使用 Google 的 protobuf3 C# 库。

【问题讨论】:

  • @Sinatr 嗯,你为什么这么认为?在我看来,这似乎是一个写得很清楚且不同的问题。自定义选项是一种高级 protobuf 功能,不经常使用,但非常有用。此外,这似乎与 Google 的 protobuf3 C# 库有关,而不是 protobuf-net。

标签: c# protocol-buffers


【解决方案1】:

更新 Protobuf 3.11.4 的答案,因为这是处理该问题的唯一线程。使用与 DoomGoober 类似的原型:

// Foo.proto
Package foo
import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
  string objectReferenceType = 1000; //Custom options are 1000 and up.
}
// Bar.proto
import "Foo.proto"
message Item
{
  string name = 1;
  int32 id = 2;
  string email = 3;
  ObjectReference prefab = 4 [(foo.objectReferenceType) = "UnityEngine.GameObject"];
}

您可以使用新生成的类从 Item Proto 对象中读取自定义选项。在这种情况下,它称为 FooExtensions(参见 Foo.protos):

public void LogFieldOptions(Item item)
{
  // Get the list of fields in the message (name, id, etc...)
  var fieldDescriptors = item.Descriptor.Fields.InFieldNumberOrder();

  foreach (var fieldDescriptor in fieldDescriptors)
  {
    // Fetch value of this item instance for current field
    var fieldValue = fieldDescriptor.Accessor.GetValue(item);

    // Fetch name of field
    var fieldName = fieldDescriptor.Name;

    // if we are not in the correct field: Skip    
    if(!fieldName.Equals("prefab")) continue;

    // Fetch the option set in this field in the proto
    // (note that this is not related to the instance 
    // of item but to the general item message descriptor)
    var optionObjectReferenceType = fieldDescriptor.GetOption(FooExtensions.objectReferenceType); 
    Console.Log(optionObjectReferenceType ); //logs: "UnityEngine.GameObject";
  }
}

您能够以相同的方式获取所有类型的选项(MessageOptions、FileOptions)。只需确保您使用的是正确的描述符(对于 MessageOptions 使用 MessageDescriptors 等等......)

【讨论】:

  • GetOption() 已过时,使用 GetOptions().GetExtension(FooExtensions.objectReferenceType)
【解决方案2】:

您现在可以在 C# 中访问自定义选项。首先,在 .proto 中定义自定义选项:

import "google/protobuf/descriptor.proto";
extend google.protobuf.FieldOptions {
  string objectReferenceType = 1000; //Custom options are 1000 and up.
}

接下来,将自定义选项应用于某项。在这里,我将它附加到一个字段:

message Item
{
  string name = 1;
  int32 id = 2;
  string email = 3;
  ObjectReference prefab = 4 [(objectReferenceType) = "UnityEngine.GameObject"];
}

然后您需要查找自定义选项字段编号。没有很好的方法可以做到这一点,因此只需从您定义自定义选项扩展名的文件的 FileDescriptor 中查找扩展名。您将拥有一个名为 protoFileNameReflection 的 C# 生成类。从那里,您可以找到扩展名,然后是字段编号。这是一个示例,假设 proto 名为“Item.proto”,因此生成的类称为 ItemReflection:

foreach (FieldDescriptor extensionFieldDescriptor in ItemReflection.Descriptor.Extensions.UnorderedExtensions)
    {   
        if (extensionFieldDescriptor.ExtendeeType.FullName == "google.protobuf.FieldOptions")
        {
            objectReferenceTypeFieldNumber = extensionFieldDescriptor.FieldNumber;
            break;
        }
    }

然后使用 protobuf 反射访问代码中的自定义选项:

FieldDescriptor fieldDescriptor = prefabFieldDescriptor;
CustomOptions customOptions = fieldDescriptor.CustomOptions;
if (customOptions.TryGetString(objectReferenceTypeFieldNumber, out string objectReferenceTypeText))
{
   Console.Log(objectReferenceTypeText); //logs: "UnityEngine.GameObject"
}

【讨论】:

  • 抱歉,C# 编译器不会在 cs 文件中生成任何选项信息。会不会,这个功能还没有在 C# 的 protobuf 编译器中实现?
  • @ErikStroeken 我让它工作了一个月,然后在 10 月它坏了,然后 Protobuf C# 团队说它应该在 3.11 中再次工作(我没有尝试 3.11)。另一个技巧是自定义选项位于定义扩展名的 FileDescriptor 中。我无法从 MessageDescriptor 中得到它。
  • @ErikStroeken 我确认它在 3.11 中再次正常工作。我应该注意到 FieldDescriptor.CustomOptions 被标记为已弃用,但首选的 GetOption 替换方法对我来说没有任何意义,因为它需要一个我似乎在任何地方都找不到的扩展引用。
  • @AliZeinali 主要的,绝对最重要的问题是扩展的 FieldDescriptor 必须来自包含扩展的反射文件。所以如果你在文件 Monkey.proto 中定义 FieldOptions 扩展,你必须从 MonkeyReflection 中获取字段描述符。此外,您应该能够调试并查看它在哪里失败(并告诉我,以便我可以帮助您。只是说它不起作用并不能真正帮助我。)此外,您可以只使用硬编码的 FieldNumber (在我的示例中为 1000)。
  • 要使用GetOption 方法,您需要传入从定义了原始选项的文件描述符中获得的强类型Extension 对象,然后将其转换为@987654327 @.
【解决方案3】:

该功能似乎尚未实现:https://github.com/google/protobuf/issues/1603

看起来这只是时间问题,他们愿意接受拉取请求。因此,根据您需要它的时间,您可能是执行实施的人:)

【讨论】:

    【解决方案4】:

    阅读自定义消息选项(扩展)

    • 假设文件名为foobar.proto
    
    message.Descriptor.GetOptions().GetExtension(FoobarExtensions.MyOption);
    

    【讨论】:

      【解决方案5】:

      访问描述符数据的简单版本:

      public void LogFieldOptions(Item item)
      {
         var fieldDescriptors = item.Descriptor.Fields.InDeclarationOrder(); // or .InFieldNumberOrder()
         var fieldDescriptor = fieldDescriptors.FirstOrDefault(fd => fd.Name == "prefab")
      
         if (fieldDescriptor != null)
         {
             var objectReferenceType = fieldDescriptor.GetOptions().GetExtension(FooExtensions.objectReferenceType); 
             // use result
         }
      }
      

      【讨论】:

        猜你喜欢
        • 2022-06-30
        • 2020-05-05
        • 2016-08-17
        • 1970-01-01
        • 1970-01-01
        • 2022-12-20
        • 1970-01-01
        • 1970-01-01
        • 2014-09-14
        相关资源
        最近更新 更多