简体   繁体   中英

Reading a protobuf3 custom option from C#

TL;DR

According to the doc, if I were doing C++, I could read the value of a custom option using string value = MyMessage::descriptor()->options().GetExtension(my_option); . There are similar examples for Java and Python. But I'm doing C# and I could find an equivalent. Can I do it, and if yes, how?

More details

I'm manipulating classes generated with protobuf3 . The schemas are declaring a custom option . It looks like this:

import "google/protobuf/descriptor.proto";

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

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

My code is being provided an object generated from MyMessage , and I'd like to read the value of this option (here Hello world! )


Update: I'm not using protobuf.net. Now that C# is natively supported by protobuf, I'm using Google's protobuf3 C# library.

You can now access custom options in C#. First, define the custom option in your .proto:

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

Next, apply the custom option to something. Here I attached it to a field:

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

Then you need to lookup the custom option field number. There's no nice way to do this, so just look up the extension from FileDescriptor of the file where you defined the custom option extension. You will have a C# generated class called protoFileNameReflection. From that, you can find the extension then the field number. Here's an example assuming the proto is called "Item.proto" so the generated class is called ItemReflection:

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

Then access the custom option in code using protobuf reflection:

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

Looks like the feature hasn't been implemented yet: https://github.com/google/protobuf/issues/1603

It also looks like it's only a matter of time and they're open to pull requests. So depending on how soon you need it, you could be the one doing the implementation :)

Updating the answer for Protobuf 3.11.4 as this is the only thread dealing with the problem. Using similar protos as 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"];
}

You can read the custom options from an Item Proto object using a new generated class. In this case its called FooExtensions (see 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";
  }
}

You are able to fetch all types of options in the same way (MessageOptions, FileOptions). Simply ensure you are using the correct descriptor (For MessageOptions use MessageDescriptors and so on...)

Reading Custom Message Options (Extensions)

  • Assuming the file is called foobar.proto

message.Descriptor.GetOptions().GetExtension(FoobarExtensions.MyOption);

A simple version to access the descriptor data:

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
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM