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.
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.cs
with:
using System; using System.IO; using Example; class Program { static void Main(string[] args) { // Create a new Person object var person = new Person { Name = "John Doe", Id = 1234, Email = "john.doe@example.com" }; // 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: John Doe ID: 1234 Email: john.doe@example.com Serialized to 62 bytes Deserialized Person: Name: John Doe ID: 1234 Email: john.doe@example.com Phone numbers: 555-1234, 555-5678 Saved to person.bin Read from file: John Doe
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.