使用 FieldDescriptor 迭代未知的重复 protobuf 字段

Iterate over unknown repeated protobuf field with FieldDescriptor

我正在尝试编写一个通用方法来遍历 protobuf 消息并基本上创建一个对象树。

到目前为止我有

IMessage message = new Message() as IMessage;
foreach (FieldDescriptor field in message.Descriptor.Fields.InFieldNumberOrder())
            {
                if (field.FieldType == Google.Protobuf.Reflection.FieldType.Message)
                {
                    if (field.IsRepeated)
                    {
                        // How to do this?
                    }
                    else
                    {
                        Children.Add(new MessageNode(field, (IMessage)field.Accessor.GetValue(message)));
                    }
                }
                else
                {
                    Children.Add(new FieldNode(field, message));
                }
           }

工作正常,除了重复的字段(地图字段可能会出现同样的问题)。

如果我对重复字段执行 field.Accessor.GetValue(message),我会得到一个 object[RepeatedField<T>],其中 T 是一些嵌套消息。但是,我无法迭代 object 类型的内容。 我尝试将对象投射到 RepeatedField<IMessage> 但这只是 returns 0.

有没有什么方法可以在不知道里面的类型的情况下迭代一个重复的字段?我只需要一个指向内部对象的 IMessage 指针,从那里我可以使用 IMessage.

中的 Descriptor

我不知道我在过去两天试图解决这个问题时有多盲目......

AccessorGetValue 的文档中它说

For repeated values, this will be an IList implementation. For map values, this will be an IDictionary implementation.

是的,IList d = field.Accessor.GetValue(message) as IList; 可以解决问题并提供重复字段的可迭代集合。

如您在 , as per the protobuf documentation 中所述,field.Accessor.GetValue(protobufMessage) 将 return 为重复字段提供 IList,为映射字段提供 IDictionary

这是我编写的一些代码,这些代码使用字段描述符通过 protobuf 消息递归迭代。它可能是执行其他操作而不是打印的有用起点。

    public static void PrintProtobufMessage(IMessage protobufMessage)
    {
        if (protobufMessage == null)
        {
            return;
        }
        foreach (var field in protobufMessage.Descriptor.Fields.InFieldNumberOrder())
        {
            if (field.IsMap)
            {
                // for map fields, the field type is always a MapField, inside of which are contained a keyField and valueField
                //var keyField = field.MessageType.Fields.InFieldNumberOrder()[0];
                var valueField = field.MessageType.Fields.InFieldNumberOrder()[1];
                IDictionary mapFields = field.Accessor.GetValue(protobufMessage) as IDictionary;
                if (mapFields != null)
                {
                    foreach (DictionaryEntry mapField in mapFields)
                    {
                        Console.WriteLine("Iterating Map, Field {0} ({1}): Key {2}:", field.FieldNumber, field.Name, mapField.Key);
                        if (valueField.FieldType == Google.Protobuf.Reflection.FieldType.Message)
                        {
                            PrintProtobufMessage(mapField.Value as IMessage);
                        }
                        else
                        {
                            PrintProtobufField(valueField, mapField.Value);
                        }
                    }
                }
            }
            else if (field.IsRepeated)
            {
                IList repeatedFieldValues = field.Accessor.GetValue(protobufMessage) as IList;
                if (repeatedFieldValues != null)
                {
                    foreach (var repeatedFieldValue in repeatedFieldValues)
                    {
                        // for repeated fields, the field type represents each element in the list, handle them accordingly
                        if (field.FieldType == Google.Protobuf.Reflection.FieldType.Message)
                        {
                            PrintProtobufMessage(repeatedFieldValue as IMessage);
                        }
                        else
                        {
                            PrintProtobufField(field, repeatedFieldValue);
                        }
                    }
                }
            }
            else
            {
                if (field.FieldType == Google.Protobuf.Reflection.FieldType.Message)
                {
                    var fieldValue = field.Accessor.GetValue(protobufMessage);
                    PrintProtobufMessage(fieldValue as IMessage);
                }
                else
                {
                    PrintProtobufField(field, protobufMessage);
                }
            }
        }
    }

    public static void PrintProtobufField(FieldDescriptor field, IMessage protobufMessage)
    {                   
        var fieldValue = field.Accessor.GetValue(protobufMessage);
        PrintProtobufField(field, fieldValue);
    }

    public static void PrintProtobufField(FieldDescriptor field, object fieldValue)
    {
        if (fieldValue != null)
        {
            Console.WriteLine(
                "Field {0} ({1}): {2}",
                field.FieldNumber,
                field.Name,
                fieldValue
            );
        }
    }