Protocol Buffers (protobuf) works seamlessly with C# and .NET applications. This guide walks you through everything you need to know to start using protobuf in your C# projects, from installation to practical implementation.
We'll build a simple example that demonstrates how to define messages, generate C# code, and serialize/deserialize data. By the end of this guide, you'll be able to integrate protobuf into your own applications. Learn about protobuf best practices for production use.
Prerequisites
Before we start, make sure you have:
- •.NET SDK: .NET 6.0 or later (download from dotnet.microsoft.com)
- •Visual Studio or VS Code: Any C# editor you're comfortable with
- •Basic C# knowledge: Understanding of classes and objects
Step 1: Create a New C# Project
First, create a new console application:
dotnet new console -n ProtobufExample cd ProtobufExample
This creates a basic C# console application where we'll add Protocol Buffers support.
Step 2: Install Google.Protobuf NuGet Package
Add the Google.Protobuf package to your project:
dotnet add package Google.Protobuf dotnet add package Grpc.Tools
Note: Google.Protobuf provides runtime support for protobuf in C#, while Grpc.Tools includes the protoc compiler for generating C# code from .proto files.
Step 3: Define Your .proto Schema
Create a new folder called Protos in your project root, then create a file named person.proto:
syntax = "proto3";
package Example;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
repeated string phone_numbers = 4;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 5;
}
message AddressBook {
repeated Person people = 1;
}This schema defines two message types:
- •Person: Contains basic contact information including name, ID, email, and phone numbers
- •AddressBook: A collection of Person objects
Step 4: Configure Your Project File
Edit your ProtobufExample.csproj file to include the .proto file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.25.0" />
<PackageReference Include="Grpc.Tools" Version="2.59.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\person.proto" GrpcServices="None" />
</ItemGroup>
</Project>The <Protobuf Include> element tells the build system to automatically generate C# code from your .proto file.
Step 5: Build the Project
Build your project to generate the C# classes:
dotnet build
This generates C# classes in the obj folder. You can now use the Person andAddressBook classes in your code.
Step 6: Use Protocol Buffers in Your Code
Now let's use the generated classes. Replace the contents of Program.cswith:
using System;
using System.IO;
using Example;
class Program
{
static void Main(string[] args)
{
// Create a new Person object
var person = new Person
{
Name = "Maria Garcia",
Id = 1234,
Email = "[email protected]"
};
// Add phone numbers
person.PhoneNumbers.Add("555-1234");
person.PhoneNumbers.Add("555-5678");
// Add a structured phone number
person.Phones.Add(new Person.Types.PhoneNumber
{
Number = "555-9999",
Type = Person.Types.PhoneType.Mobile
});
Console.WriteLine("Created Person:");
Console.WriteLine($"Name: {person.Name}");
Console.WriteLine($"ID: {person.Id}");
Console.WriteLine($"Email: {person.Email}");
// Serialize to binary
byte[] binaryData;
using (var stream = new MemoryStream())
{
person.WriteTo(stream);
binaryData = stream.ToArray();
}
Console.WriteLine($"\nSerialized to {binaryData.Length} bytes");
// Deserialize from binary
Person deserializedPerson;
using (var stream = new MemoryStream(binaryData))
{
deserializedPerson = Person.Parser.ParseFrom(stream);
}
Console.WriteLine("\nDeserialized Person:");
Console.WriteLine($"Name: {deserializedPerson.Name}");
Console.WriteLine($"ID: {deserializedPerson.Id}");
Console.WriteLine($"Email: {deserializedPerson.Email}");
Console.WriteLine($"Phone numbers: {string.Join(", ", deserializedPerson.PhoneNumbers)}");
// Save to file
using (var output = File.Create("person.bin"))
{
person.WriteTo(output);
}
Console.WriteLine("\nSaved to person.bin");
// Read from file
using (var input = File.OpenRead("person.bin"))
{
var personFromFile = Person.Parser.ParseFrom(input);
Console.WriteLine($"\nRead from file: {personFromFile.Name}");
}
}
}Step 7: Run Your Application
Run the application:
dotnet run
You should see output like:
Created Person: Name: Maria Garcia ID: 1234 Email: [email protected] Serialized to 62 bytes Deserialized Person: Name: Maria Garcia ID: 1234 Email: [email protected] Phone numbers: 555-1234, 555-5678 Saved to person.bin Read from file: Maria Garcia
Common Operations
Serialize to Byte Array
byte[] data = person.ToByteArray();
Deserialize from Byte Array
Person person = Person.Parser.ParseFrom(data);
Convert to JSON
using Google.Protobuf; string json = JsonFormatter.Default.Format(person); Console.WriteLine(json);
Parse from JSON
string json = "{\"name\": \"Jane\", \"id\": 5678}";
Person person = JsonParser.Default.Parse<Person>(json);Clone a Message
Person copy = person.Clone();
Merge Two Messages
person1.MergeFrom(person2); // Merges person2 into person1
Best Practices for C# Protobuf
Use Proper Naming Conventions
In .proto files, use snake_case (e.g., phone_number). The C# generator automatically converts these to PascalCase (e.g., PhoneNumber).
Handle Repeated Fields as Collections
Repeated fields in protobuf become RepeatedField<T> in C#. You can use them like regular lists with Add(), Clear(), and foreach.
Use Binary for Network/Storage, JSON for Debugging
Serialize to binary format for production (smaller, faster). Use JSON format for debugging and logging since it's human-readable.
Dispose Streams Properly
Always use using statements when working with streams to ensure proper resource cleanup.
Common Issues and Solutions
Issue: Generated classes not found
Solution: Run dotnet build to generate the classes. Make sure the .proto file is included in your .csproj file.
Issue: Package namespace not found
Solution: Check the package declaration in your .proto file. Use that namespace in your C# code (e.g., using Example;).
Issue: Serialization fails silently
Solution: Check that all required fields are set. In proto3, all fields are optional, but you may have validation logic that requires certain fields.
Related Tools
Conclusion
Protocol Buffers integrates smoothly with C# and .NET applications. The Google.Protobuf library provides a clean API that feels natural to C# developers, with strong typing and good performance.
Start with simple examples like the one in this guide, then gradually adopt protobuf for performance-critical parts of your application. The initial setup takes a bit of work, but the benefits in terms of speed, size, and type safety make it worthwhile for production systems.