Skip to main content

# Basic types

- Basic types
  - Field numbers
  - Scalar data types
  - Enums
  - Other .NET primitives
    - Decimals
    - Nullable types
    - Date & times
    - Guids
    - Any

### Field numbers​

Are used to identify position inside the string byte. `int32 id = 1;` is not an asignment

### Scalar data types​

Protobuf | C# | Notes
--- | --- | ---
double | double |
float | float |
int32 |  | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.
int64 | long |
int64 |  |
uint32 |  |
uint64 |  |
sint32 |  |
sint64 |  |
fixed32 |  |
fixed64 |  |
sfixed32 | int |
sfixed64 | int |
bool | bool |
string | string |
bytes | ByteString |

1. The standard encoding for int32 and int64 is inefficient when you’re working with signed values. If your field is likely to contain negative numbers, use sint32 or sint64 instead. These types map to the C# int and long types, respectively.
2. The fixed fields always use the same number of bytes no matter what the value is. This behavior makes serialization and deserialization faster for larger values.
3. Protobuf strings are UTF-8 (or 7-bit ASCII) encoded. The encoded length can’t be greater than 232.
4. The Protobuf runtime provides a ByteString type that maps easily to and from C# byte[] arrays

### Enums​

An enum must contain a constant that maps to zero as its first element. This is because:

- There must be a zero value, so that we can use 0 as a numeric default value.
- The zero value needs to be the first element, for compatibility with the proto2 semantics where the first enum value is always the default.

Enum values must be in the 32 bit integer range. If you need negative values, better use messages.

    enum Corpus {
      CORPUS_UNSPECIFIED = 0;
      CORPUS_UNIVERSAL = 1;
      CORPUS_WEB = 2;
      CORPUS_IMAGES = 3;
      CORPUS_LOCAL = 4;
      CORPUS_NEWS = 5;
      CORPUS_PRODUCTS = 6;
      CORPUS_VIDEO = 7;
    }

When using it inside the definition:

     message SearchRequest {
            string query = 1;
            int32 page_number = 2;
            int32 result_per_page = 3;
            Corpus corpus = 4;
    }

Note that the .proto language allows multiple enum symbols to have the same numeric value. Symbols with the same numeric value are synonyms. These are represented in C# in exactly the same way, with multiple names corresponding to the same numeric value.

## Other .NET primitives​

Some of the C# types are not supported by default, but some of them are covered by the Google’s Well Known Types.

> Well Known types - are types in the google.protobuf package API, provide code generation and runtime support for complex field types across the supported platforms

### Decimals​

Protobuf doesn't natively support the .NET `decimal` type, just `double` and `float`. There's an ongoing discussion in the Protobuf project about the possibility of adding a standard `Decimal` type to the well-known types, with platform support for languages and frameworks that support it. Nothing has been implemented yet.

It's possible to create a message definition to represent the `decimal` type that would work for safe serialization between .NET clients and servers. But developers on other platforms would have to understand the format being used and implement their own handling for it.

### Creating a custom decimal type for Protobuf​

A simple implementation might be similar to the nonstandard `Money` type that some Google APIs use, without the `currency` field.

    package CustomTypes;

    // Example: 12345.6789 -> { units = 12345, nanos = 678900000 }
    message DecimalValue {

        // Whole units part of the amount
        int64 units = 1;

        // Nano units of the amount (10^-9)
        // Must be same sign as units
        sfixed32 nanos = 2;
    }

The `nanos` field represents values from `0.999_999_999` to `-0.999_999_999`. For example, the `decimal` value `1.5m` would be represented as `{ units = 1, nanos = 500_000_000 }`. This is why the `nanos` field in this example uses the `sfixed32` type, which encodes more efficiently than `int32` for larger values. If the `units` field is negative, the `nanos` field should also be negative.

> [!NOTE] There are multiple other algorithms for encoding `decimal` values as byte strings, but this message is easier to understand than any of them. The values are not affected by endianness on different platforms.

Conversion between this type and the BCL `decimal` type might be implemented in C# like this:

    namespace CustomTypes;
    public partial class DecimalValue
    {
        private const decimal NanoFactor = 1_000_000_000;
        public DecimalValue(long units, int nanos)
        {
            Units = units;
            Nanos = nanos;
        }

        public static implicit operator decimal(CustomTypes.DecimalValue grpcDecimal)
        {
            return grpcDecimal.Units + grpcDecimal.Nanos / NanoFactor;
        }

        public static implicit operator CustomTypes.DecimalValue(decimal value)
        {
            var units = decimal.ToInt64(value);
            var nanos = decimal.ToInt32((value - units) * NanoFactor);
            return new CustomTypes.DecimalValue(units, nanos);
        }
    }

> [!IMPORTANT] Whenever you use custom message types like this, you must document them with comments in `.proto`. Other developers can then implement conversion to and from the equivalent type in their own language or framework.

### Nullable types​

The Protobuf code generation for C# uses the native types, such as int for int32. So the values are always included and can’t be null.

    syntax = "proto3"
    import "google/protobuf/wrappers.proto"
    message Person {
       google.protobuf.Int32Value age = 5;
    }

Protobuf will use the simple T? (for example, int?) for the generated message property. The following table shows the complete list of wrapper types with their equivalent C# type:

C# type | Well Known Type wrapper
--- | ---
`double?` | `google.protobuf.DoubleValue`
`float?` | `google.protobuf.FloatValue`
`int?` | `google.protobuf.Int32Value`
`long?` | `google.protobuf.Int64Value`
`uint?` | `google.protobuf.UInt32Value`
`ulong?` | `google.protobuf.UInt64Value`

### Date & times​

    syntax = "proto3"
    import "google/protobuf/duration.proto";
    import "google/protobuf/timestamp.proto";

    message Meeting {
      string subject = 1;
      google.protobuf.Timestamp time = 2;
      google.protobuf.Duration duration = 3;
     }

    // Create Timestamp and Duration from .NET DateTimeOffset and TimeSpan
    var meeting = new Meeting
    {
        Time = Timestamp.FromDateTimeOffset(meetingTime),
        Duration = Duration.FromTimeSpan(meetingLength)
    };
      // Convert Timestamp and Duration to .NET DateTimeOffset and TimeSpan
    DateTimeOffset time = meeting.Time.ToDateTimeOffset();
    TimeSpan? duration = meeting.Duration?.ToTimeSpan()

Offset

The Timestamp type works with UTC times. DateTimeOffset values always have an offset of zero, and the DateTime.Kind property is always DateTimeKind.Utc.

The well-known types `Timestamp` and `Duration` are represented in .NET as classes.

C# type | Protobuf well-known type
--- | ---
`DateTimeOffset` | `google.protobuf.Timestamp`
`DateTime` | `google.protobuf.Timestamp`
`TimeSpan` | `google.protobuf.Duration`

### Guids​

The Guid type, also known as UUID on other platforms, is not directly supported by Protobuf. As a result, there is no recognized type for it. The recommended method is to treat Guid values as a string field and use the standard 8-4-4-4-12 hexadecimal format, such as 45a9fda3-bd01-47a9-8460-c1cd7484b0b3, which can be parsed by all languages and platforms. It is not advisable to use a bytes field for Guid values as it can cause unexpected behavior when Protobuf interacts with other platforms like Java due to endianness issues.

### Any​

Any message type lets you use messages as embedded types without having their .proto definition. An Any contains an arbitrary serialized message as bytes, along with a URL that acts as a globally unique identifier for and resolves to that message’s type. To use the Any type, you need to import google/protobuf/any.proto.

Title

    import "google/protobuf/any.proto";

    message ErrorStatus {
      string message = 1;
      repeated google.protobuf.Any details = 2;
    }