Protobuf OneOf and Options
1. What is OneOf?
Think of a oneof like a radio button group on a form — you can only select one option at a time. If you pick a new one, the previous selection is automatically cleared.
In Protobuf, oneof means: "this message has several possible fields, but only one of them can hold a value at any given time."
Real-world example: A notification can be sent via Email or SMS or Push — but not all three at once.
2. OneOf Syntax
Here is the basic syntax:
Key points:
- The
oneofblock has a name (delivery_channel). - Each field inside gets its own unique field number (3, 4, 5).
- Fields outside the
oneofblock (title,body) work normally.
3. OneOf with Messages
oneof fields can be scalars or message types. Here is an example with geometric shapes:
A Shape always has a color, but its geometry is exactly one of: Circle, Rectangle, or Triangle.
4. OneOf in C#
Protobuf generates a special enum and a Case property for each oneof block. For the delivery_channel oneof, you get:
You use a switch expression to check which field is set:
Setting a value clears the previous one:
5. OneOf Rules and Restrictions
| Rule | Explanation |
|---|---|
| Only one field active | Setting a new field clears the previous one automatically |
Cannot use repeated | oneof fields cannot be repeated or map |
| Default is None | If nothing is set, the Case property returns None |
| Field numbers are shared | Oneof fields share the message's field number space — no duplicates allowed |
| Cannot contain another oneof | You cannot nest a oneof inside another oneof |
6. What is Option?
Options are metadata annotations you attach to various proto elements — files, messages, fields, services, or methods. They do not change the data itself; they change how the code is generated or how tools interpret the proto file.
Think of options like HTML attributes — they provide extra information about an element.
7. Common Built-in Options
| Level | Option | What It Does |
|---|---|---|
| File | option csharp_namespace = "MyApp"; | Sets the C# namespace for all generated types in this file |
| File | option java_package = "com.example"; | Sets the Java package name |
| File | option go_package = "example.com/pb"; | Sets the Go import path |
| File | option java_multiple_files = true; | Generates separate .java files for each message |
| Field | [deprecated = true] | Marks a field as deprecated — compiler warns on usage |
| Field | [json_name = "myField"] | Overrides the JSON field name used during serialization |
| Message | option deprecated = true; | Marks an entire message as deprecated |
The most commonly used option in .NET projects is csharp_namespace.
8. The deprecated Option
The deprecated option marks a field or message as "no longer recommended." The generated C# code will include an [Obsolete] attribute, and the compiler will show a warning when you use it.
In C#, if you try to use vehicle.LicensePlate, you will see a compiler warning:
Warning CS0612: 'Vehicle.LicensePlate' is obsolete.
This is useful when you want to stop using a field but cannot remove it (because existing clients might still send it).
9. Complete Working Example
Here is the full .proto file combining both concepts:
And the C# service implementation that handles the oneof:
10. Common Mistakes
| Mistake | Why It's Wrong | Fix |
|---|---|---|
oneof channel { repeated string emails = 1; } | repeated is not allowed inside oneof | Wrap in a message: message EmailList { repeated string emails = 1; } |
| Not checking the Case property | Accessing a field that is not set returns default values, leading to bugs | Always check DeliveryChannelCase before accessing the field |
Using option keyword for field options | Field options use square brackets, not the option keyword | Use string name = 1 [deprecated = true]; |
Forgetting option is file-level when outside a message | option csharp_namespace applies to the whole file | Place file-level options near the top of the file |
11. Summary
| Feature | Purpose | C# Mapping |
|---|---|---|
oneof | Only one field can be set at a time | Generates an enum + Case property; switch to check which field is active |
option (file-level) | Metadata for the whole file (namespace, package, etc.) | csharp_namespace sets the C# namespace |
[deprecated = true] | Marks a field as obsolete | Generates [Obsolete] attribute — compiler warning on use |
[json_name = "..."] | Overrides JSON field name | Changes the JSON property name during serialization |