![]() |
VOOZH | about |
dotnet add package LightProto --version 1.3.2
NuGet\Install-Package LightProto -Version 1.3.2
<PackageReference Include="LightProto" Version="1.3.2" />
<PackageVersion Include="LightProto" Version="1.3.2" />Directory.Packages.props
<PackageReference Include="LightProto" />Project file
paket add LightProto --version 1.3.2
#r "nuget: LightProto, 1.3.2"
#:package LightProto@1.3.2
#addin nuget:?package=LightProto&version=1.3.2Install as a Cake Addin
#tool nuget:?package=LightProto&version=1.3.2Install as a Cake Tool
👁 .NET
👁 NuGet
👁 downloads
👁 Build
👁 codecov
👁 CodeQL Advanced
👁 Codacy Badge
👁 GitHub Repo stars
👁 Size
|
A high-performance, Native AOT–friendly, production-ready Protocol Buffers implementation for C#/.NET, powered by source generators.
protobuf-net is a popular Protocol Buffers implementation in .NET, but some scenarios (especially Native AOT) can be challenging due to runtime reflection and dynamic generation. LightProto addresses this with compile-time code generation and a protobuf-net–style API.
byte,sbyte, int,uint,long,ulong, bool, char, double, etc.)string, decimal, Half, Int128, UInt128, Guid, Rune, BigIntegerTimeSpan, DateTime, DateTimeOffset, TimeOnly, DateOnly, TimeZoneInfoComplex, Plane, Quaternion, Matrix3x2, Matrix4x4, Vector2, Vector3, Vector4Uri, Version, StringBuilder, BitArray, CultureInfoNullable<>, Lazy<>T[], List<>, LinkedList<>, Queue<>, Stack<>, HashSet<>, SortedSet<>Dictionary<,>, SortedList<,>, SortedDictionary<,>, ReadOnlyDictionary<,>Collection<>, ReadOnlyCollection<>, ObservableCollection<>, ReadOnlyObservableCollection<>IEnumerable<>, ICollection<>, IList<>, IReadOnlyCollection<>, IReadOnlyList<>, ISet<>IDictionary<,>, IReadOnlyDictionary<,>ConcurrentBag<>, ConcurrentQueue<>, ConcurrentStack<>, ConcurrentDictionary<,>, BlockingCollection<>ImmutableList<>, ImmutableArray<>, ImmutableHashSet<>, ImmutableDictionary<,>Install from NuGet:
dotnet add package LightProto
Define your contracts (partial classes) using LightProto attributes:
using LightProto;
[ProtoContract]
public partial class Person
{
[ProtoMember(1)]
public string Name { get; set; } = string.Empty;
[ProtoMember(2)]
public int Age { get; set; }
}
var person = new Person { Name = "Alice", Age = 30 };
// Serialize to a byte[]
byte[] bytes = person.ToByteArray();
// person.ToByteArray(Person.ProtoWriter); // use this overload when targeting .netstandard2.0
// Or serialize to a Stream
using var stream = new MemoryStream();
Serializer.Serialize(stream, person);
// Serializer.Serialize(stream, person, Person.ProtoWriter); // use this overload when targeting .netstandard2.0
byte[] data = stream.ToArray();
// Deserialize from byte[] (ReadOnlySpan<byte> overload will be used)
Person fromBytes = Serializer.Deserialize<Person>(bytes);
// Person fromBytes = Serializer.Deserialize<Person>(bytes, Person.ProtoReader); // use this overload when targeting .netstandard2.0
// Or deserialize from Stream
using var input = new MemoryStream(data);
Person fromStream = Serializer.Deserialize<Person>(input);
// Person fromStream = Serializer.Deserialize<Person>(input, Person.ProtoReader); // use this overload when targeting .netstandard2.0
Most code migrates by swapping the namespace and marking your types partial.
Example:
- using ProtoBuf;
+ using LightProto;
[ProtoContract]
- public class Person
+ public partial class Person
{
[ProtoMember(1)]
public string Name { get; set; } = string.Empty;
[ProtoMember(2)]
public int Age { get; set; }
}
var myObject = new Person { Name = "Alice", Age = 30 };
// Serialization
var stream = new MemoryStream();
Serializer.Serialize(stream, myObject);
byte[] data = stream.ToArray();
// Deserialization
var obj = Serializer.Deserialize<Person>(new ReadOnlySpan<byte>(data));
LightProto ships lightproto-gen, a dotnet tool that generates LightProto [ProtoContract] C# classes directly from .proto files.
dotnet tool install -g LightProto.ProtoGen
Quick start:
# Generate to ./Generated (also supports stdin/stdout pipe mode)
lightproto-gen --proto "**/*.proto" --output ./Generated --namespace MyApp.Models
For full documentation — all options, pipe mode, type-shape and nullability controls — see the tool README.
Serializer.Serialize<T>(...) and Serializer.Deserialize<T>(...) require that T implements IProtoParser<T> (i.e., a generated message type).
Note: These APIs are not supported in .netstandard2.0 due to lack of static virtual members in interfaces. Use IProtoParser-specified APIs instead.
Serializer.Serialize<T>(..., IProtoWriter<T>) and Serializer.Deserialize<T>(..., IProtoReader<T>) where T is a [ProtoContract] marked type with generated IProtoParser<T> implementation.
Person person = new Person { Name = "Alice", Age = 30 };
ArrayBufferWriter<byte> bufferWriter = new ArrayBufferWriter<byte>();
LightProto.Serializer.Serialize<Person>(bufferWriter, person, Person.ProtoWriter); // must pass writer
var bytes = person.ToByteArray(Person.ProtoWriter); // extension method
Person result = LightProto.Serializer.Deserialize<Person>(bytes, Person.ProtoReader); // must pass reader
Serializer.SerializeDynamically<T>(...) and Serializer.DeserializeDynamically<T>(...) resolve IProtoReader/Writer at runtime via T.ProtoReader/Writer or Serializer.RegisterParser or reflection.
Person person = new Person { Name = "Alice", Age = 30 };
ArrayBufferWriter<byte> bufferWriter = new ArrayBufferWriter<byte>();
LightProto.Serializer.SerializeDynamically<Person>(bufferWriter, person); // dynamic API
Person result = LightProto.Serializer.DeserializeDynamically<Person>(bufferWriter.WrittenSpan); // dynamic API
ProtoWriter/Reader resolution order:
Serializer.RegisterParser<T>(reader, writer), use the registered parser.T is a primitive/built-in type, use built-in parser from LightProto.Parser namespace which is registered internally.T implements IProtoParser<T> (usually marked as [ProtoContract]), use T.ProtoWriter/Reader by reflection. This is fine with AOT, as the generic argument T is marked as [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)].T is a generic container shape (e.g., List<>, Dictionary<,>, Nullable<>, etc.), try to resolve element type parser recursively. This may fail at runtime on AOT due to missing type metadata.Serializer.SerializeNonGeneric(..., object instance) and Serializer.DeserializeNonGeneric(Type type, ...) are similar to Dynamic APIs, but the type is specified at runtime.
Person person = new Person { Name = "Alice", Age = 30 };
ArrayBufferWriter<byte> bufferWriter = new ArrayBufferWriter<byte>();
LightProto.Serializer.SerializeNonGeneric(bufferWriter, person); // non-generic API
Person result = (Person)LightProto.Serializer.DeserializeNonGeneric(typeof(Person), bufferWriter.WrittenSpan); // non-generic API
The ProtoWriter/Reader resolution order is the same as Dynamic APIs.
In .NET Standard target frameworks, we can't use static virtual members in interface to find T.ProtoReader/Writer.
So LightProto requires you to specify a ProtoWriter when serializing and a ProtoReader when deserializing.
For [ProtoContract]-marked message types, ProtoReader/Writer is generated by LightProto, so use MessageType.ProtoReader/Writer.
For primitive types, LightProto provides predefined parsers in the LightProto.Parser namespace, such as LightProto.Parser.DateTimeParser.
If you don't need AOT support, you can use the dynamic APIs Serializer.SerializeDynamically<T> and Serializer.DeserializeDynamically<T> without passing ProtoReader/Writer.
LightProto's generated code supports C# 9, making it compatible with Unity projects targeting .NET Standard 2.0. You can use LightProto in Unity by following the same installation and usage instructions as for other .NET projects. IL2CPP builds are supported because LightProto is AOT-friendly.
protobuf-net can register surrogates via RuntimeTypeModel at runtime.
LightProto lets you specify a custom ProtoParserType for MessageType.
For example, if MessageType is Person and the custom ProtoParserType is PersonProtoParser, you can use the following attributes in precedence order:
[ProtoMember(1,ParserType=typeof(PersonProtoParser))][ProtoParserTypeMap(typeof(Person), typeof(PersonProtoParser))][ProtoParserTypeMap(typeof(Person), typeof(PersonProtoParser))] (messageType and parserType should not be in the same assembly; if they are, use the type-level attribute.)[ProtoParserTypeMap(typeof(Person), typeof(PersonProtoParser))]global::LightProto.Parser.PersonProtoParserThe ProtoParserType must implement IProtoParser<MessageType>. The easiest way is to define a SurrogateType with [ProtoContract] and mark it with [ProtoSurrogateFor<MessageType>].
Example for Person (can be any type):
[ProtoParserType(typeof(PersonProtoParser))] // type level ProtoParser
public class Person
{
public string Name {get; set;}
Person(){}
public static Person FromName(string name) => new Person() { Name = name };
}
[ProtoContract]
[ProtoSurrogateFor<Person>] // mark this to tell source generator generate IProtoParser<Person> instead of `IProtoParser<PersonProtoParser>`
public partial struct PersonProtoParser
{
[ProtoMember(1)]
internal string Name { get; set; }
public static implicit operator Person(PersonProtoParser parser) // must define implicit conversions for surrogate type
{
return Person.FromName(parser.Name);
}
public static implicit operator PersonProtoParser(Person value) // must define implicit conversions for surrogate type
{
return new PersonProtoParser() { Name = value.Name };
}
}
[assembly: ProtoParserTypeMap(typeof(Person), typeof(PersonProtoParser))] // assembly level ProtoParser
[ProtoParserTypeMap(typeof(Person), typeof(PersonProtoParser))] // class level ProtoParser
public class MessageContract
{
[ProtoMember(1,ParserType=typeof(PersonProtoParser))] //member level ProtoParser
public Person Person {get; set;}
}
You can also read/write raw binary data, but only WireType.LengthDelimited is supported for now because LightProtoGenerator needs to compute tags at compile time and any unknown type will be treated as LengthDelimited.
[StringIntern] attribute can be applied to individual string members, classes, modules, or assemblies.
LightProto provides a set of APIs in RuntimeProtoWriter, RuntimeProtoReader, and RuntimeProtoParser.
RuntimeProtoParser<T> can be used to get IProtoReader<T> and IProtoWriter<T> at runtime.
You can use them to serialize/deserialize, or use Serializer.RegisterParser<T>(reader, writer) to register globally, then use Serializer.SerializeDynamically/DeserializeDynamically or Serializer.SerializeNonGeneric APIs.
public class TestMessage
{
public int Value { get; set; }
public string StringValue { get; set; } = string.Empty;
public int[] IntArray { get; set; } = [];
}
var runtimeParser = new RuntimeProtoParser<TestMessage>(() => new());
runtimeParser.AddMember(1, message => message.Value, (message, value) => message.Value = value);
runtimeParser.AddMember(typeof(string), 2, message => message.StringValue, (message, value) => message.StringValue = (string)value);
runtimeParser.AddMember<int[]>(
3,
message => message.IntArray,
(message, value) => message.IntArray = value,
// specify array reader/writer for aot support
Int32ProtoParser.ProtoReader.GetArrayReader(),
Int32ProtoParser.ProtoWriter.GetCollectionWriter()
);
// Use the runtime parser to serialize/deserialize.
var writer = runtimeParser.ProtoWriter;
var reader = runtimeParser.ProtoReader;
If you do not need both Serialize and Deserialize, you can use RuntimeProtoWriter<T> or RuntimeProtoReader<T> to create only writer or reader.
public class TestMessage
{
public int Value { get; set; }
public string StringValue { get; set; } = string.Empty;
public int[] IntArray { get; set; } = [];
}
var protoReader = new RuntimeProtoReader<TestMessage>(() => new());
protoReader.AddMember<int>(1, (message, value) => message.Value = value);
protoReader.AddMember(typeof(string), 2, (message, value) => message.StringValue = (string)value);
protoReader.AddMember<int[]>(3, (message, value) => message.IntArray = value, Int32ProtoParser.ProtoReader.GetArrayReader());
var protoWriter = new RuntimeProtoWriter<TestMessage>();
protoWriter.AddMember<int>(1, message => message.Value);
protoWriter.AddMember(typeof(string), 2, message => message.StringValue);
protoWriter.AddMember<int[]>(3, message => message.IntArray, Int32ProtoParser.ProtoWriter.GetCollectionWriter());
IExtensible is defined for compatibility only and has no effect.
The following benchmarks compare serialization performance between LightProto, protobuf-net, and Google.Protobuf.
You can reproduce these by cloning the repo and running tests/Benchmark.
BenchmarkDotNet v0.15.3, Windows 11 (10.0.26100.4652/24H2/2024Update/HudsonValley)
AMD Ryzen 7 5800X 3.80GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 10.0.100-rc.1.25451.107
[Host] : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
.NET 10.0 : .NET 10.0.0 (10.0.0-rc.1.25451.107, 10.0.25.45207), X64 RyuJIT x86-64-v3
.NET 8.0 : .NET 8.0.20 (8.0.20, 8.0.2025.41914), X64 RyuJIT x86-64-v3
.NET 9.0 : .NET 9.0.9 (9.0.9, 9.0.925.41916), X64 RyuJIT x86-64-v3
| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Serialize_ProtoBuf_net | .NET 10.0 | .NET 10.0 | 645.6 μs | 12.70 μs | 11.88 μs | 1.39 | 0.03 | 526.41 KB | 1.03 |
| Serialize_GoogleProtoBuf | .NET 10.0 | .NET 10.0 | 539.9 μs | 10.71 μs | 12.75 μs | 1.16 | 0.03 | 512.95 KB | 1.00 |
| Serialize_LightProto | .NET 10.0 | .NET 10.0 | 465.1 μs | 7.88 μs | 6.99 μs | 1.00 | 0.02 | 512.95 KB | 1.00 |
| Serialize_ProtoBuf_net | .NET 8.0 | .NET 8.0 | 757.0 μs | 12.80 μs | 11.98 μs | 1.42 | 0.04 | 526.41 KB | 1.03 |
| Serialize_GoogleProtoBuf | .NET 8.0 | .NET 8.0 | 553.9 μs | 10.97 μs | 9.72 μs | 1.04 | 0.03 | 512.95 KB | 1.00 |
| Serialize_LightProto | .NET 8.0 | .NET 8.0 | 531.9 μs | 10.52 μs | 14.04 μs | 1.00 | 0.04 | 512.95 KB | 1.00 |
| Serialize_ProtoBuf_net | .NET 9.0 | .NET 9.0 | 712.6 μs | 13.61 μs | 12.73 μs | 1.39 | 0.04 | 526.41 KB | 1.03 |
| Serialize_GoogleProtoBuf | .NET 9.0 | .NET 9.0 | 546.7 μs | 10.70 μs | 16.33 μs | 1.07 | 0.04 | 512.95 KB | 1.00 |
| Serialize_LightProto | .NET 9.0 | .NET 9.0 | 513.6 μs | 10.15 μs | 13.89 μs | 1.00 | 0.04 | 512.95 KB | 1.00 |
| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Deserialize_ProtoBuf_net | .NET 10.0 | .NET 10.0 | 569.2 μs | 10.88 μs | 12.53 μs | 1.38 | 0.04 | 562 KB | 0.84 |
| Deserialize_GoogleProtoBuf | .NET 10.0 | .NET 10.0 | 441.4 μs | 8.67 μs | 10.64 μs | 1.07 | 0.04 | 648.7 KB | 0.97 |
| Deserialize_LightProto | .NET 10.0 | .NET 10.0 | 411.5 μs | 8.08 μs | 9.92 μs | 1.00 | 0.03 | 665.95 KB | 1.00 |
| Deserialize_ProtoBuf_net | .NET 8.0 | .NET 8.0 | 688.0 μs | 13.51 μs | 15.56 μs | 1.55 | 0.05 | 562 KB | 0.84 |
| Deserialize_GoogleProtoBuf | .NET 8.0 | .NET 8.0 | 595.5 μs | 11.51 μs | 16.14 μs | 1.34 | 0.04 | 648.7 KB | 0.97 |
| Deserialize_LightProto | .NET 8.0 | .NET 8.0 | 444.8 μs | 8.88 μs | 9.12 μs | 1.00 | 0.03 | 665.95 KB | 1.00 |
| Deserialize_ProtoBuf_net | .NET 9.0 | .NET 9.0 | 662.3 μs | 12.60 μs | 11.17 μs | 1.53 | 0.04 | 562 KB | 0.84 |
| Deserialize_GoogleProtoBuf | .NET 9.0 | .NET 9.0 | 491.7 μs | 9.64 μs | 13.52 μs | 1.14 | 0.04 | 648.7 KB | 0.97 |
| Deserialize_LightProto | .NET 9.0 | .NET 9.0 | 431.9 μs | 8.33 μs | 9.25 μs | 1.00 | 0.03 | 665.95 KB | 1.00 |
Note: Results vary by hardware, runtime, and data model. Please run the benchmarks on your environment for the most relevant numbers.
Contributions are welcome! Please see for detailed contribution guidelines.
describes the internal design and structure of LightProto, which may be helpful for contributors.
MIT License — see LICENSE for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 net5.0 was computed. net5.0-windows net5.0-windows was computed. net6.0 net6.0 was computed. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 netcoreapp2.0 was computed. netcoreapp2.1 netcoreapp2.1 was computed. netcoreapp2.2 netcoreapp2.2 was computed. netcoreapp3.0 netcoreapp3.0 was computed. netcoreapp3.1 netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 netstandard2.0 is compatible. netstandard2.1 netstandard2.1 was computed. |
| .NET Framework | net461 net461 was computed. net462 net462 was computed. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
| MonoAndroid | monoandroid monoandroid was computed. |
| MonoMac | monomac monomac was computed. |
| MonoTouch | monotouch monotouch was computed. |
| Tizen | tizen40 tizen40 was computed. tizen60 tizen60 was computed. |
| Xamarin.iOS | xamarinios xamarinios was computed. |
| Xamarin.Mac | xamarinmac xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos xamarinwatchos was computed. |
Showing the top 5 NuGet packages that depend on LightProto:
| Package | Downloads |
|---|---|
|
Ahsoka.Core
Package Description |
|
|
JK.Mexc.Net
A high-performance .NET client library for the Mexc REST and WebSocket APIs, providing strongly typed models, automatic WebSocket reconnection, client-side rate limiting, local order book support, and seamless integration with other CryptoExchange.Net based clients. |
|
|
EnovationCloud.Core
Package Description |
|
|
Ahsoka.Extensions.Storyboard
Storyboard Extension for OpenPV 5.0 // 60f365e6af3b712659435adb8e2f8c34e4c7c2f5 - OpenPV_Extension_Info { "description": "Storyboard Extension for OpenPV", "extensionName": "Storyboard Extension", "packageName": "Ahsoka.Extensions.Storyboard", "uxpackageName": "Ahsoka.Extensions.Storyboard.Ux", "serviceConfigurations": [] } |
|
|
Ahsoka.Extensions.AudioManager
AudioManager Extension for OpenPV // 60f365e6af3b712659435adb8e2f8c34e4c7c2f5 - OpenPV_Extension_Info { "extensionName": "Audio Manager Extension", "packageName": "Ahsoka.Extensions.AudioManager", "uxpackageName": "Ahsoka.Extensions.AudioManager.UX", "serviceConfigurations": [ { "socketType": "TcpSocket", "serviceName": "AudioManagerService", "tcpListenAddress": "localhost", "tcpConnectionAddress": "localhost", "dataChannel": 6302, "behaviors": "AutoStart" }, { "socketType": "TcpSocket", "serviceName": "A2BService", "tcpListenAddress": "localhost", "tcpConnectionAddress": "localhost", "dataChannel": 6303, "behaviors": "AutoStart" }, { "socketType": "TcpSocket", "serviceName": "TunerService", "tcpListenAddress": "localhost", "tcpConnectionAddress": "localhost", "dataChannel": 6304, "behaviors": "AutoStart" } ] } |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.3.2 | 7,694 | 5/6/2026 |
| 1.3.1 | 230 | 4/25/2026 |
| 1.3.0 | 159 | 4/13/2026 |
| 1.2.1 | 1,198 | 1/26/2026 |
| 1.2.0 | 145 | 1/21/2026 |
| 1.1.1 | 123 | 1/12/2026 |
| 1.1.0 | 118 | 1/10/2026 |
| 1.0.1 | 130 | 1/6/2026 |
| 1.0.0 | 127 | 1/4/2026 |
| 0.9.5 | 204 | 12/24/2025 |
| 0.9.4 | 296 | 12/18/2025 |
| 0.9.3 | 294 | 12/18/2025 |
| 0.9.2 | 301 | 12/17/2025 |
| 0.9.1 | 299 | 12/16/2025 |
| 0.9.0 | 15,071 | 12/12/2025 |
| 0.8.5 | 411 | 11/17/2025 |
| 0.8.4 | 252 | 10/27/2025 |
| 0.8.3 | 133 | 10/25/2025 |
| 0.8.2 | 142 | 10/25/2025 |
| 0.8.1 | 221 | 10/23/2025 |