Getting started (C#)
Getting Started with Bebop in C#
Bebop is a high-performance serialization framework designed for efficient data transfer. This guide will walk you through setting up Bebop in your C# project, creating schemas, and using the generated code.
Supported Runtimes
- .NET Framework 4.7.2
- .NET Framework 4.8
- .NET Core 3.1
- .NET 5+
Installation
First, let’s install the necessary packages:
Package | NuGet Stable | Downloads |
---|---|---|
bebop | ||
bebop-tools |
Install Bebop Runtime
Install Bebop Compiler Tools
Project Configuration
To configure Bebop in your project, add the following ItemGroup
to your .csproj
file:
This configuration tells Bebop to:
- Include all
.bop
files in your project - Generate C# code
- Output the generated code to
./Models/IpcModels.g.cs
- Use the specified namespace for the generated code
Creating Bebop Schemas
Bebop uses its own schema language to define data structures. Create a new file with a .bop
extension (e.g., schemas.bop
) and define your schemas:
Generating C# Code
After defining your schemas, the C# code will be automatically generated when you build your project. Any issues encountered during compilation will be displayed in the error list.
Using Generated Code
Now you can use the generated code in your C# project. Here’s an example of how to create and encode a Person
object:
Using the BebopSerializer
The BebopSerializer
class provides static methods for encoding and decoding Bebop records. Here are some examples:
Working with Unions
Bebop supports unions, which allow you to define a type that can be one of several possible structures. Here’s an example of how to work with unions in C#.
Defining a Union in Bebop
First, let’s look at how a union is defined in a Bebop schema file (.bop
):
Using the Generated Union in C#
After generating the C# code from this Bebop schema, you can use the union like this:
Key Points About Unions
- The union type (
Person
in this case) has properties likeIsJohn
andIsDoe
to check which variant it currently represents. - Use
AsJohn
andAsDoe
to access the specific fields of each variant. - The
Discriminator
property tells you which variant the union currently represents (1 for John, 2 for Doe in this case). - You can use pattern matching or the
Switch
andMatch
methods for type-safe handling of the union. - Encoding and decoding work seamlessly with unions, just like with regular Bebop structures.
Unions are particularly useful when you need to represent data that can be one of several types, providing a type-safe way to handle different cases in your application.
Advanced Usage: Zero-Allocation Network Writing with EncodeIntoBuffer
For high-performance scenarios, especially when writing to a network stream, you can use the EncodeIntoBuffer
method along with System.Buffers.ArrayPool<T>
to achieve zero-allocation encoding. This approach is particularly useful in situations where you’re sending many objects rapidly and want to avoid any overhead from memory allocations.
Concept: Using EncodeIntoBuffer for Network Writing
Here’s the core concept of how to use EncodeIntoBuffer
with ArrayPool<T>
to write directly to a network stream without allocations:
Key Concepts
-
Use of ArrayPool: We rent a buffer from
ArrayPool<byte>.Shared
instead of allocating a new array each time. -
Sizing with MaxByteCount: We use
person.MaxByteCount
to ensure our buffer is large enough for the current instance of the Person record. This value can vary between instances, especially for records with variable-length fields like strings or arrays. -
Zero-Allocation Encoding:
EncodeIntoBuffer
writes directly into the rented buffer, avoiding additional allocations. -
Direct Stream Writing: We write the exact number of bytes used in encoding directly to the stream.
-
Resource Management: The
finally
block ensures that the buffer is always returned to the pool, even if an exception occurs.
This pattern allows for efficient, zero-allocation serialization and network writing, which can significantly improve performance in high-throughput scenarios. It adapts to the specific size requirements of each record instance, ensuring optimal buffer usage.