Giter VIP home page Giter VIP logo

memorypack's Introduction

MemoryPack

NuGet GitHub Actions Releases

Zero encoding extreme performance binary serializer for C# and Unity.

image

Compared with System.Text.Json, protobuf-net, MessagePack for C#, Orleans.Serialization. Measured by .NET 7 / Ryzen 9 5950X machine. These serializers have IBufferWriter<byte> method, serialized using ArrayBufferWriter<byte> and reused to avoid measure buffer copy.

For standard objects, MemoryPack is x10 faster and x2 ~ x5 faster than other binary serializers. For struct array, MemoryPack is even more powerful, with speeds up to x50 ~ x200 greater than other serializers.

MemoryPack is my 4th serializer, previously I've created well known serializers, ZeroFormatter, Utf8Json, MessagePack for C#. The reason for MemoryPack's speed is due to its C#-specific, C#-optimized binary format and a well tuned implementation based on my past experience. It is also a completely new design utilizing .NET 7 and C# 11 and the Incremental Source Generator (.NET Standard 2.1 (.NET 5, 6) and there is also Unity support).

Other serializers perform many encoding operations such as VarInt encoding, tag, string, etc. MemoryPack format uses a zero-encoding design that copies as much C# memory as possible. Zero-encoding is similar to FlatBuffers, but it doesn't need a special type, MemoryPack's serialization target is POCO.

Other than performance, MemoryPack has these features.

  • Support modern I/O APIs (IBufferWriter<byte>, ReadOnlySpan<byte>, ReadOnlySequence<byte>)
  • Native AOT friendly Source Generator based code generation, no Dynamic CodeGen (IL.Emit)
  • Reflectionless non-generics APIs
  • Deserialize into existing instance
  • Polymorphism (Union) serialization
  • Limited version-tolerant (fast/default) and full version-tolerant support
  • Circular reference serialization
  • PipeWriter/Reader based streaming serialization
  • TypeScript code generation and ASP.NET Core Formatter
  • Unity (2021.3) IL2CPP Support via .NET Source Generator

Installation

This library is distributed via NuGet. For best performance, recommend to use .NET 7. Minimum requirement is .NET Standard 2.1.

PM> Install-Package MemoryPack

And also a code editor requires Roslyn 4.3.1 support, for example Visual Studio 2022 version 17.3, .NET SDK 6.0.401. For details, see the Roslyn Version Support document.

For Unity, the requirements and installation process are completely different. See the Unity section for details.

Quick Start

Define a struct or class to be serialized and annotate it with the [MemoryPackable] attribute and the partial keyword.

using MemoryPack;

[MemoryPackable]
public partial class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
}

Serialization code is generated by the C# source generator feature which implements the IMemoryPackable<T> interface. In Visual Studio you can check a generated code by using a shortcut Ctrl+K, R on the class name and select *.MemoryPackFormatter.g.cs.

Call MemoryPackSerializer.Serialize<T>/Deserialize<T> to serialize/deserialize an object instance.

var v = new Person { Age = 40, Name = "John" };

var bin = MemoryPackSerializer.Serialize(v);
var val = MemoryPackSerializer.Deserialize<Person>(bin);

Serialize method supports a return type of byte[] as well as it can serialize to IBufferWriter<byte> or Stream. Deserialize method supports ReadOnlySpan<byte>, ReadOnlySequence<byte> and Stream. And there are alse non-generics versions.

Built-in supported types

These types can be serialized by default:

  • .NET primitives (byte, int, bool, char, double, etc.)
  • Unmanaged types (Any enum, Any user-defined struct which doesn't contain reference types)
  • string, decimal, Half, Int128, UInt128, Guid, Rune, BigInteger
  • TimeSpan, DateTime, DateTimeOffset, TimeOnly, DateOnly, TimeZoneInfo
  • Complex, Plane, Quaternion Matrix3x2, Matrix4x4, Vector2, Vector3, Vector4
  • Uri, Version, StringBuilder, Type, BitArray, CultureInfo
  • T[], T[,], T[,,], T[,,,], Memory<>, ReadOnlyMemory<>, ArraySegment<>, ReadOnlySequence<>
  • Nullable<>, Lazy<>, KeyValuePair<,>, Tuple<,...>, ValueTuple<,...>
  • List<>, LinkedList<>, Queue<>, Stack<>, HashSet<>, SortedSet<>, PriorityQueue<,>
  • Dictionary<,>, SortedList<,>, SortedDictionary<,>, ReadOnlyDictionary<,>
  • Collection<>, ReadOnlyCollection<>, ObservableCollection<>, ReadOnlyObservableCollection<>
  • IEnumerable<>, ICollection<>, IList<>, IReadOnlyCollection<>, IReadOnlyList<>, ISet<>
  • IDictionary<,>, IReadOnlyDictionary<,>, ILookup<,>, IGrouping<,>,
  • ConcurrentBag<>, ConcurrentQueue<>, ConcurrentStack<>, ConcurrentDictionary<,>, BlockingCollection<>
  • Immutable collections (ImmutableList<>, etc.) and interfaces (IImmutableList<>, etc.)

Define [MemoryPackable] class / struct / record / record struct

[MemoryPackable] can annotate to any class, struct, record, record struct and interface. If a type is struct or record struct which contains no reference types (C# Unmanaged types) any additional annotation (ignore, include, constructor, callbacks) is not used, that serialize/deserialize directly from the memory.

Otherwise, by default, [MemoryPackable] serializes public instance properties or fields. You can use [MemoryPackIgnore] to remove serialization target, [MemoryPackInclude] promotes a private member to serialization target.

[MemoryPackable]
public partial class Sample
{
    // these types are serialized by default
    public int PublicField;
    public readonly int PublicReadOnlyField;
    public int PublicProperty { get; set; }
    public int PrivateSetPublicProperty { get; private set; }
    public int ReadOnlyPublicProperty { get; }
    public int InitProperty { get; init; }
    public required int RequiredInitProperty { get; init; }

    // these types are not serialized by default
    int privateProperty { get; set; }
    int privateField;
    readonly int privateReadOnlyField;

    // use [MemoryPackIgnore] to remove target of a public member
    [MemoryPackIgnore]
    public int PublicProperty2 => PublicProperty + PublicField;

    // use [MemoryPackInclude] to promote a private member to serialization target
    [MemoryPackInclude]
    int privateField2;
    [MemoryPackInclude]
    int privateProperty2 { get; set; }
}

MemoryPack's code generator adds information about what members are serialized to the <remarks /> section. This can be viewed by hovering over the type with Intellisense.

image

All members must be memorypack-serializable, if not the code generator will emit an error.

image

MemoryPack has 35 diagnostics rules (MEMPACK001 to MEMPACK035) to be defined comfortably.

If target type is defined MemoryPack serialization externally and registered, use [MemoryPackAllowSerialize] to silent diagnostics.

[MemoryPackable]
public partial class Sample2
{
    [MemoryPackAllowSerialize]
    public NotSerializableType? NotSerializableProperty { get; set; }
}

Member order is important, MemoryPack does not serialize the member-name or other information, instead serializing fields in the order they are declared. If a type is inherited, serialization is performed in the order of parent → child. The order of members can not change for the deserialization. For the schema evolution, see the Version tolerant section.

The default order is sequential, but you can choose the explicit layout with [MemoryPackable(SerializeLayout.Explicit)] and [MemoryPackOrder()].

// serialize Prop0 -> Prop1
[MemoryPackable(SerializeLayout.Explicit)]
public partial class SampleExplicitOrder
{
    [MemoryPackOrder(1)]
    public int Prop1 { get; set; }
    [MemoryPackOrder(0)]
    public int Prop0 { get; set; }
}

Constructor selection

MemoryPack supports both parameterized and parameterless constructors. The selection of the constructor follows these rules. (Applies to classes and structs).

  • If there is [MemoryPackConstructor], use it.
  • If there is no explicit constructor (including private), use a parameterless one.
  • If there is one parameterless/parameterized constructor (including private), use it.
  • If there are multiple constructors, then the [MemoryPackConstructor] attribute must be applied to the desired constructor (the generator will not automatically choose one), otherwise the generator will emit an error.
  • If using a parameterized constructor, all parameter names must match corresponding member names (case-insensitive).
[MemoryPackable]
public partial class Person
{
    public readonly int Age;
    public readonly string Name;

    // You can use a parameterized constructor - parameter names must match corresponding members name (case-insensitive)
    public Person(int age, string name)
    {
        this.Age = age;
        this.Name = name;
    }
}

// also supports record primary constructor
[MemoryPackable]
public partial record Person2(int Age, string Name);

public partial class Person3
{
    public int Age { get; set; }
    public string Name { get; set; }

    public Person3()
    {
    }

    // If there are multiple constructors, then [MemoryPackConstructor] should be used
    [MemoryPackConstructor]
    public Person3(int age, string name)
    {
        this.Age = age;
        this.Name = name;
    }
}

Serialization callbacks

When serializing/deserializing, MemoryPack can invoke a before/after event using the [MemoryPackOnSerializing], [MemoryPackOnSerialized], [MemoryPackOnDeserializing], [MemoryPackOnDeserialized] attributes. It can annotate both static and instance (non-static) methods, and public and private methods.

[MemoryPackable]
public partial class MethodCallSample
{
    // method call order is static -> instance
    [MemoryPackOnSerializing]
    public static void OnSerializing1()
    {
        Console.WriteLine(nameof(OnSerializing1));
    }

    // also allows private method
    [MemoryPackOnSerializing]
    void OnSerializing2()
    {
        Console.WriteLine(nameof(OnSerializing2));
    }

    // serializing -> /* serialize */ -> serialized
    [MemoryPackOnSerialized]
    static void OnSerialized1()
    {
        Console.WriteLine(nameof(OnSerialized1));
    }

    [MemoryPackOnSerialized]
    public void OnSerialized2()
    {
        Console.WriteLine(nameof(OnSerialized2));
    }

    [MemoryPackOnDeserializing]
    public static void OnDeserializing1()
    {
        Console.WriteLine(nameof(OnDeserializing1));
    }

    // Note: instance method with MemoryPackOnDeserializing, that not called if instance is not passed by `ref`
    [MemoryPackOnDeserializing]
    public void OnDeserializing2()
    {
        Console.WriteLine(nameof(OnDeserializing2));
    }

    [MemoryPackOnDeserialized]
    public static void OnDeserialized1()
    {
        Console.WriteLine(nameof(OnDeserialized1));
    }

    [MemoryPackOnDeserialized]
    public void OnDeserialized2()
    {
        Console.WriteLine(nameof(OnDeserialized2));
    }
}

Callbacks allows parameterless method and ref reader/writer, ref T value method. For example, ref callbacks can write/read custom header before serialization process.

[MemoryPackable]
public partial class EmitIdData
{
    public int MyProperty { get; set; }

    [MemoryPackOnSerializing]
    static void WriteId<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, ref EmitIdData? value)
        where TBufferWriter : IBufferWriter<byte> // .NET Standard 2.1, use where TBufferWriter : class, IBufferWriter<byte>
    {
        writer.WriteUnmanaged(Guid.NewGuid()); // emit GUID in header.
    }

    [MemoryPackOnDeserializing]
    static void ReadId(ref MemoryPackReader reader, ref EmitIdData? value)
    {
        // read custom header before deserialize
        var guid = reader.ReadUnmanaged<Guid>();
        Console.WriteLine(guid);
    }
}

If set a value to ref value, you can change the value used for serialization/deserialization. For example, instantiate from ServiceProvider.

// before using this formatter, set ServiceProvider
// var options = MemoryPackSerializerOptions.Default with { ServiceProvider = provider };
// MemoryPackSerializer.Deserialize(value, options);

[MemoryPackable]
public partial class InstantiateFromServiceProvider
{
    static IServiceProvider serviceProvider = default!;

    public int MyProperty { get; private set; }

    [MemoryPackOnDeserializing]
    static void OnDeserializing(ref MemoryPackReader reader, ref InstantiateFromServiceProvider value)
    {
        if (value != null) return;
        value = reader.Options.ServiceProvider!.GetRequiredService<InstantiateFromServiceProvider>();
    }
}

Define custom collection

By default, annotated [MemoryPackObject] type try to serialize its members. However, if a type is a collection (ICollection<>, ISet<>, IDictionary<,>), use GenerateType.Collection to serialize it correctly.

[MemoryPackable(GenerateType.Collection)]
public partial class MyList<T> : List<T>
{
}

[MemoryPackable(GenerateType.Collection)]
public partial class MyStringDictionary<TValue> : Dictionary<string, TValue>
{

}

Static constructor

MemoryPackable class can not define static constructor because generated partial class uses it. Instead, you can define a static partial void StaticConstructor() to do the same thing.

[MemoryPackable]
public partial class CctorSample
{
    static partial void StaticConstructor()
    {
    }
}

Polymorphism (Union)

MemoryPack supports serializing interface and abstract class objects for polymorphism serialization. In MemoryPack this feature is called Union. Only interfaces and abstracts classes are allowed to be annotated with [MemoryPackUnion] attributes. Unique union tags are required.

// Annotate [MemoryPackable] and inheritance types with [MemoryPackUnion]
// Union also supports abstract class
[MemoryPackable]
[MemoryPackUnion(0, typeof(FooClass))]
[MemoryPackUnion(1, typeof(BarClass))]
public partial interface IUnionSample
{
}

[MemoryPackable]
public partial class FooClass : IUnionSample
{
    public int XYZ { get; set; }
}

[MemoryPackable]
public partial class BarClass : IUnionSample
{
    public string? OPQ { get; set; }
}
// ---

IUnionSample data = new FooClass() { XYZ = 999 };

// Serialize as interface type.
var bin = MemoryPackSerializer.Serialize(data);

// Deserialize as interface type.
var reData = MemoryPackSerializer.Deserialize<IUnionSample>(bin);

switch (reData)
{
    case FooClass x:
        Console.WriteLine(x.XYZ);
        break;
    case BarClass x:
        Console.WriteLine(x.OPQ);
        break;
    default:
        break;
}

tag allows 0 ~ 65535, it is especially efficient for less than 250.

If an interface and derived types are in different assemblies, you can use MemoryPackUnionFormatterAttribute instead. Formatters are generated the way that they are automatically registered via ModuleInitializer in C# 9.0 and above.

Note that ModuleInitializer is not supported in Unity, so the formatter must be manually registered. To register your union formatter invoke {name of your union formatter}Initializer.RegisterFormatter() manually in Startup. For example UnionSampleFormatterInitializer.RegisterFormatter().

// AssemblyA
[MemoryPackable(GenerateType.NoGenerate)]
public partial interface IUnionSample
{
}

// AssemblyB define definition outside of target type
[MemoryPackUnionFormatter(typeof(IUnionSample))]
[MemoryPackUnion(0, typeof(FooClass))]
[MemoryPackUnion(1, typeof(BarClass))]
public partial class UnionSampleFormatter
{
}

Union can be assembled in code via DynamicUnionFormatter<T>.

var formatter = new DynamicUnionFormatter<IFooBarBaz>(new[]
{
    (0, typeof(Foo)),
    (1, typeof(Bar)),
    (2, typeof(Baz))
});

MemoryPackFormatterProvider.Register(formatter);

Serialize API

Serialize has three overloads.

// Non generic API also available, these version is first argument is Type and value is object?
byte[] Serialize<T>(in T? value, MemoryPackSerializerOptions? options = default)
void Serialize<T, TBufferWriter>(in TBufferWriter bufferWriter, in T? value, MemoryPackSerializerOptions? options = default)
async ValueTask SerializeAsync<T>(Stream stream, T? value, MemoryPackSerializerOptions? options = default, CancellationToken cancellationToken = default)

For performance, the recommended API uses BufferWriter. This serializes directly into the buffer. It can be applied to PipeWriter in System.IO.Pipelines, BodyWriter in ASP .NET Core, etc.

If a byte[] is required (e.g. RedisValue in StackExchange.Redis), the return byte[] API is simple and almost as fast.

Note that SerializeAsync for Stream is asynchronous only for Flush; it serializes everything once into MemoryPack's internal pool buffer and then writes using WriteAsync. Therefore, the BufferWriter overload, which separates and controls buffer and flush, is better.

If you want to do a complete streaming write, see the Streaming Serialization section.

MemoryPackSerializerOptions

MemoryPackSerializerOptions configures whether strings are serialized as UTF16 or UTF8. This can be configured by passing MemoryPackSerializerOptions.Utf8 for UTF8 encoding, MemoryPackSerializerOptions.Utf16 for UTF16 encoding or MemoryPackSerializerOptions.Default which defaults to UTF8. Passing null or using the default parameter results in UTF8 encoding.

Since C#'s internal string representation is UTF16, UTF16 performs better. However, the payload tends to be larger; in UTF8, an ASCII string is one byte, while in UTF16 it is two bytes. Because the difference in size of this payload is so large, UTF8 is set by default.

If the data is non-ASCII (e.g. Japanese, which can be more than 3 bytes, and UTF8 is larger), or if you have to compress it separately, UTF16 may give better results.

While UTF8 or UTF16 can be selected during serialization, it is not necessary to specify it during deserialization. It will be automatically detected and deserialized normally.

Additionaly you can get/set IServiceProvider? ServiceProvider { get; init; } from options. It is useful to get DI object(such as ILogger<T>) from serialization process(MemoryPackReader/MemoryPackWriter has .Options property).

Deserialize API

Deserialize has ReadOnlySpan<byte> and ReadOnlySequence<byte>, Stream overload and ref support.

T? Deserialize<T>(ReadOnlySpan<byte> buffer)
int Deserialize<T>(ReadOnlySpan<byte> buffer, ref T? value)
T? Deserialize<T>(in ReadOnlySequence<byte> buffer)
int Deserialize<T>(in ReadOnlySequence<byte> buffer, ref T? value)
async ValueTask<T?> DeserializeAsync<T>(Stream stream)

ref overload overwrites an existing instance, for details see the Overwrite section.

DeserializeAsync(Stream) is not a complete streaming read operation, first it reads into MemoryPack's internal pool up to the end-of-stream, then it deserializes.

If you want to do a complete streaming read operation, see the Streaming Serialization section.

Overwrite

To reduce allocations, MemoryPack supports deserializing to an existing instance, overwriting it. This can be used with the Deserialize(ref T? value) overload.

var person = new Person();
var bin = MemoryPackSerializer.Serialize(person);

// overwrite data to existing instance.
MemoryPackSerializer.Deserialize(bin, ref person);

MemoryPack will attempt to overwrite as much as possible, but if the following conditions do not match, it will create a new instance (as in normal deserialization).

  • ref value (includes members in object graph) is null, set new instance
  • only allows parameterless constructor, if parameterized constructor is used, create new instance
  • if value is T[], reuse only if the length is the same, otherwise create new instance
  • if value is collection that has .Clear() method(List<>, Stack<>, Queue<>, LinkedList<>, HashSet<>, PriorityQueue<,>, ObservableCollection, Collection, ConcurrentQueue<>, ConcurrentStack<>, ConcurrentBag<>, Dictionary<,>, SortedDictionary<,>, SortedList<,>, ConcurrentDictionary<,>) call Clear() and reuse it, otherwise create new instance

Version tolerant

In default(GenerateType.Object), MemoryPack supports limited schema evolution.

  • unmanaged struct can't be changed anymore
  • members can be added, but can not be deleted
  • can change member name
  • can't change member order
  • can't change member type
[MemoryPackable]
public partial class VersionCheck
{
    public int Prop1 { get; set; }
    public long Prop2 { get; set; }
}

// Add is OK.
[MemoryPackable]
public partial class VersionCheck
{
    public int Prop1 { get; set; }
    public long Prop2 { get; set; }
    public int? AddedProp { get; set; }
}

// Remove is NG.
[MemoryPackable]
public partial class VersionCheck
{
    // public int Prop1 { get; set; }
    public long Prop2 { get; set; }
}

// Change order is NG.
[MemoryPackable]
public partial class VersionCheck
{
    public long Prop2 { get; set; }
    public int Prop1 { get; set; }
}

In use-case, store old data (to file, to redis, etc...) and read to new schema is always ok. In the RPC scenario, schema exists both on the client and the server side, the client must be updated before the server. An updated client has no problem connecting to the old server but an old client can not connect to a new server.

By default, when the old data read to new schema, any members not on the data side are initialized with the default literal. If you want to avoid this and use initial values of field/properties, you can use [SuppressDefaultInitialization].

[MemoryPackable]
public partial class DefaultValue
{
    public string Prop1 { get; set; }

    [SuppressDefaultInitialization]
    public int Prop2 { get; set; } = 111; // < if old data is missing, set `111`.
    
    public int Prop3 { get; set; } = 222; // < if old data is missing, set `default`.
}

[SuppressDefaultInitialization] has following limitation:

  • Cannot be used with readonly, init-only, and required modifier.

The next Serialization info section shows how to check for schema changes, e.g., by CI, to prevent accidents.

When using GenerateType.VersionTolerant, it supports full version-tolerant.

  • unmanaged struct can't change any more
  • all members must add [MemoryPackOrder] explicitly(except annotate SerializeLayout.Sequential)
  • members can add, can delete but not reuse order (can use missing order)
  • can change member name
  • can't change member order
  • can't change member type
// Ok to serialize/deserialize both 
// VersionTolerantObject1 -> VersionTolerantObject2 and 
// VersionTolerantObject2 -> VersionTolerantObject1

[MemoryPackable(GenerateType.VersionTolerant)]
public partial class VersionTolerantObject1
{
    [MemoryPackOrder(0)]
    public int MyProperty0 { get; set; } = default;

    [MemoryPackOrder(1)]
    public long MyProperty1 { get; set; } = default;

    [MemoryPackOrder(2)]
    public short MyProperty2 { get; set; } = default;
}

[MemoryPackable(GenerateType.VersionTolerant)]
public partial class VersionTolerantObject2
{
    [MemoryPackOrder(0)]
    public int MyProperty0 { get; set; } = default;

    // deleted
    //[MemoryPackOrder(1)]
    //public long MyProperty1 { get; set; } = default;

    [MemoryPackOrder(2)]
    public short MyProperty2 { get; set; } = default;

    // added
    [MemoryPackOrder(3)]
    public short MyProperty3 { get; set; } = default;
}
// If set SerializeLayout.Sequential explicitly, allows automatically order.
// But it can not remove any member for versoin-tolerant.
[MemoryPackable(GenerateType.VersionTolerant, SerializeLayout.Sequential)]
public partial class VersionTolerantObject3
{
    public int MyProperty0 { get; set; } = default;
    public long MyProperty1 { get; set; } = default;
    public short MyProperty2 { get; set; } = default;
}

GenerateType.VersionTolerant is slower than GenerateType.Object in serializing. Also, the payload size will be slightly larger.

Serialization info

You can check IntelliSense in type what members are serialized. There is an option to write that information to a file at compile time. Set MemoryPackGenerator_SerializationInfoOutputDirectory as follows.

<!-- output memorypack serialization info to directory -->
<ItemGroup>
    <CompilerVisibleProperty Include="MemoryPackGenerator_SerializationInfoOutputDirectory" />
</ItemGroup>
<PropertyGroup>
    <MemoryPackGenerator_SerializationInfoOutputDirectory>$(MSBuildProjectDirectory)\MemoryPackLogs</MemoryPackGenerator_SerializationInfoOutputDirectory>
</PropertyGroup>

The following info is written to the file.

image

If the type is unmanaged, showed unmanaged before type name.

unmanaged FooStruct
---
int x
int y

By checking the differences in this file, dangerous schema changes can be prevented. For example, you may want to use CI to detect the following rules

  • modify unmanaged type
  • member order change
  • member deletion

Circular Reference

MemoryPack also supports circular reference. This allows the tree objects to be serialized as is.

// to enable circular-reference, use GenerateType.CircularReference
[MemoryPackable(GenerateType.CircularReference)]
public partial class Node
{
    [MemoryPackOrder(0)]
    public Node? Parent { get; set; }
    [MemoryPackOrder(1)]
    public Node[]? Children { get; set; }
}

For example, System.Text.Json preserve-references code will become like here.

// https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/preserve-references?pivots=dotnet-7-0
Employee tyler = new()
{
    Name = "Tyler Stein"
};

Employee adrian = new()
{
    Name = "Adrian King"
};

tyler.DirectReports = new List<Employee> { adrian };
adrian.Manager = tyler;

var bin = MemoryPackSerializer.Serialize(tyler);
Employee? tylerDeserialized = MemoryPackSerializer.Deserialize<Employee>(bin);

Console.WriteLine(tylerDeserialized?.DirectReports?[0].Manager == tylerDeserialized); // true

[MemoryPackable(GenerateType.CircularReference)]
public partial class Employee
{
    [MemoryPackOrder(0)]
    public string? Name { get; set; }
    [MemoryPackOrder(1)]
    public Employee? Manager { get; set; }
    [MemoryPackOrder(2)]
    public List<Employee>? DirectReports { get; set; }
}

GenerateType.CircularReference has the same characteristics as version-tolerant. However, as an additional constraint, only parameterless constructors are allowed. Also, object reference tracking is only done for objects marked with GenerateType.CircularReference. If you want to track any other object, wrap it.

CustomFormatter

If implements MemoryPackCustomFormatterAttribute<T> or MemoryPackCustomFormatterAttribute<TFormatter, T>(more performant, but complex), you can configure to use custom formatter to MemoryPackObject's member.

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public abstract class MemoryPackCustomFormatterAttribute<T> : Attribute
{
    public abstract IMemoryPackFormatter<T> GetFormatter();
}

MemoryPack provides the following formatting attributes: Utf8StringFormatterAttribute, Utf16StringFormatterAttribute, InternStringFormatterAttribute, OrdinalIgnoreCaseStringDictionaryFormatterAttribute<TValue>, BitPackFormatterAttribute, BrotliFormatter, BrotliStringFormatter, BrotliFormatter<T>, MemoryPoolFormatter<T>, ReadOnlyMemoryPoolFormatter<T>.

[MemoryPackable]
public partial class Sample
{
    // serialize this member as UTF16 String, it is performant than UTF8 but in ASCII, size is larger(but non ASCII, sometimes smaller).
    [Utf16StringFormatter]
    public string? Text { get; set; }

    // In deserialize, Dictionary is initialized with StringComparer.OrdinalIgnoreCase.
    [OrdinalIgnoreCaseStringDictionaryFormatter<int>]
    public Dictionary<string, int>? Ids { get; set; }
    
    // In deserialize time, all string is interned(see: String.Intern). If similar values come repeatedly, it saves memory.
    [InternStringFormatter]
    public string? Flag { get; set; }
}

In order to configure a set/dictionary's equality comparer, all built-in formatters have a comparer constructor overload. You can easily create custom equality-comparer formatters.

public sealed class OrdinalIgnoreCaseStringDictionaryFormatter<TValue> : MemoryPackCustomFormatterAttribute<Dictionary<string, TValue?>>
{
    static readonly DictionaryFormatter<string, TValue?> formatter = new DictionaryFormatter<string, TValue?>(StringComparer.OrdinalIgnoreCase);

    public override IMemoryPackFormatter<Dictionary<string, TValue?>> GetFormatter()
    {
        return formatter;
    }
}

BitPackFormatter compresses bool[] types only. bool[] is normally serialized as 1 byte per boolean value, however BitPackFormatter serializes bool[] like a BitArray storing each bool as 1 bit. Using BitPackFormatter, 8 bools become 1 byte where they would normally be 8 bytes, resulting in a 8x smaller size.

[MemoryPackable]
public partial class Sample
{
    public int Id { get; set; }

    [BitPackFormatter]
    public bool[]? Data { get; set; }
}

BrotliFormatter is for byte[], for example you can compress large payload by Brotli.

[MemoryPackable]
public partial class Sample
{
    public int Id { get; set; }

    [BrotliFormatter]
    public byte[]? Payload { get; set; }
}

BrotliStringFormatter is for string, serialize compressed string (UTF16) by Brotli.

[MemoryPackable]
public partial class Sample
{
    public int Id { get; set; }

    [BrotliStringFormatter]
    public string? LargeText { get; set; }
}

BrotliFormatter<T> is for any type, serialized data compressed by Brotli. If a type is byte[] or string, you should use BrotliFormatter or BrotliStringFormatter for performance.

[MemoryPackable]
public partial class Sample
{
    public int Id { get; set; }

    [BrotliFormatter<ChildType>]
    public ChildType? Child { get; set; }
}

Deserialize array pooling

In order to deserialize a large array (any T), MemoryPack offers multiple efficient pooling methods. The most effective way is to use the #Overwrite function. In particular List<T> is always reused.

[MemoryPackable]
public partial class ListBytesSample
{
    public int Id { get; set; }
    public List<byte> Payload { get; set; }
}

// ----

// List<byte> is reused, no allocation in deserialize.
MemoryPackSerializer.Deserialize<ListBytesSample>(bin, ref reuseObject);

// for efficient operation, you can get Span<T> by CollectionsMarshal
var span = CollectionsMarshal.AsSpan(value.Payload);

A convenient way is to deserialize to an ArrayPool at deserialization time. MemoryPack provides MemoryPoolFormatter<T> and ReadOnlyMemoryPoolFormatter<T>.

[MemoryPackable]
public partial class PoolModelSample : IDisposable
{
    public int Id { get; }

    [MemoryPoolFormatter<byte>]
    public Memory<byte> Payload { get; private set; }

    public PoolModelSample(int id, Memory<byte> payload)
    {
        Id = id;
        Payload = payload;
    }

    // You must write the return code yourself, here is snippet.

    bool usePool;

    [MemoryPackOnDeserialized]
    void OnDeserialized()
    {
        usePool = true;
    }

    public void Dispose()
    {
        if (!usePool) return;

        Return(Payload); Payload = default;
    }

    static void Return<T>(Memory<T> memory) => Return((ReadOnlyMemory<T>)memory);

    static void Return<T>(ReadOnlyMemory<T> memory)
    {
        if (MemoryMarshal.TryGetArray(memory, out var segment) && segment.Array is { Length: > 0 })
        {
            ArrayPool<T>.Shared.Return(segment.Array, clearArray: RuntimeHelpers.IsReferenceOrContainsReferences<T>());
        }
    }
}

// ---

using(var value = MemoryPackSerializer.Deserialize<PoolModelSample>(bin))
{
    // do anything...
}   // return to ArrayPool

Performance

See the my blog post How to make the fastest .NET Serializer with .NET 7 / C# 11, case of MemoryPack

Payload size and compression

Payload size depends on the target value; unlike JSON, there are no keys and it is a binary format, so the payload size is likely to be smaller than JSON.

For those with varint encoding, such as MessagePack and Protobuf, MemoryPack tends to be larger if ints are used a lot (in MemoryPack, ints are always 4 bytes due to fixed size encoding, while MessagePack is 1~5 bytes).

float and double are 4 bytes and 8 bytes in MemoryPack, but 5 bytes and 9 bytes in MessagePack. So MemoryPack is smaller, for example, for Vector3 (float, float, float) arrays.

String is UTF8 by default, which is similar to other serializers, but if the UTF16 option is chosen, it will be of a different nature.

In any case, if the payload size is large, compression should be considered. LZ4, ZStandard and Brotli are recommended.

Compression

MemoryPack provides an efficient helper for Brotli compression via BrotliEncoder and BrotliDecoder. MemoryPack's BrotliCompressor and BrotliDecompressor provide compression/decompression optimized for MemoryPack's internal behavior.

using MemoryPack.Compression;

// Compression(require using)
using var compressor = new BrotliCompressor();
MemoryPackSerializer.Serialize(compressor, value);

// Get compressed byte[]
var compressedBytes = compressor.ToArray();

// Or write to other IBufferWriter<byte>(for example PipeWriter)
compressor.CopyTo(response.BodyWriter);
using MemoryPack.Compression;

// Decompression(require using)
using var decompressor = new BrotliDecompressor();

// Get decompressed ReadOnlySequence<byte> from ReadOnlySpan<byte> or ReadOnlySequence<byte>
var decompressedBuffer = decompressor.Decompress(buffer);

var value = MemoryPackSerializer.Deserialize<T>(decompressedBuffer);

Both BrotliCompressor and BrotliDecompressor are struct, it does not allocate memory on heap. Both store compressed or decompressed data in an internal memory pool for Serialize/Deserialize. Therefore, it is necessary to release the memory pooling, don't forget to use using.

Compression level is very important. The default is set to quality-1 (CompressionLevel.Fastest), which is different from the .NET default (CompressionLevel.Optimal, quality-4).

Fastest (quality-1) will be close to the speed of LZ4, but 4 is much slower. This was determined to be critical in the serializer use scenario. Be careful when using the standard BrotliStream (quality-4 is the default). In any case, compression/decompression speeds and sizes will result in very different results for different data. Please prepare the data to be handled by your application and test it yourself.

Note that there is a several-fold speed penalty between MemoryPack's uncompressed and Brotli's added compression.

Brotli is also suppored in a custom formatter. BrotliFormatter can compress a specific member.

[MemoryPackable]
public partial class Sample
{
    public int Id { get; set; }

    [BrotliFormatter]
    public byte[]? Payload { get; set; }
}

Serialize external types

If you want to serialize external types, you can make a custom formatter and register it to provider, see Formatter/Provider API for details. However, creating a custom formatter is difficult. Therefore, we recommend making a wrapper type. For example, if you want to serialize an external type called AnimationCurve.

// Keyframe: (float time, float inTangent, float outTangent, int tangentMode, int weightedMode, float inWeight, float outWeight)
[MemoryPackable]
public readonly partial struct SerializableAnimationCurve
{
    [MemoryPackIgnore]
    public readonly AnimationCurve AnimationCurve;

    [MemoryPackInclude]
    WrapMode preWrapMode => AnimationCurve.preWrapMode;
    [MemoryPackInclude]
    WrapMode postWrapMode => AnimationCurve.postWrapMode;
    [MemoryPackInclude]
    Keyframe[] keys => AnimationCurve.keys;

    [MemoryPackConstructor]
    SerializableAnimationCurve(WrapMode preWrapMode, WrapMode postWrapMode, Keyframe[] keys)
    {
        var curve = new AnimationCurve(keys);
        curve.preWrapMode = preWrapMode;
        curve.postWrapMode = postWrapMode;
        this.AnimationCurve = curve;
    }

    public SerializableAnimationCurve(AnimationCurve animationCurve)
    {
        this.AnimationCurve = animationCurve;
    }
}

The type to wrap is public, but excluded from serialization (MemoryPackIgnore). The properties you want to serialize are private, but included (MemoryPackInclude). Two patterns of constructors should also be prepared. The constructor used by the serializer should be private.

As it is, it must be wrapped every time, which is inconvenient. And also strcut wrapper can not represents null. So let's create a custom formatter.

public class AnimationCurveFormatter : MemoryPackFormatter<AnimationCurve>
{
    // Unity does not support scoped and TBufferWriter so change signature to `Serialize(ref MemoryPackWriter writer, ref AnimationCurve value)`
    public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref AnimationCurve? value)
    {
        if (value == null)
        {
            writer.WriteNullObjectHeader();
            return;
        }

        writer.WritePackable(new SerializableAnimationCurve(value));
    }

    public override void Deserialize(ref MemoryPackReader reader, scoped ref AnimationCurve? value)
    {
        if (reader.PeekIsNull())
        {
            reader.Advance(1); // skip null block
            value = null;
            return;
        }
        
        var wrapped = reader.ReadPackable<SerializableAnimationCurve>();
        value = wrapped.AnimationCurve;
    }
}

Finally, register the formatter in startup.

MemoryPackFormatterProvider.Register<AnimationCurve>(new AnimationCurveFormatter());

Note: Unity's AnimationCurve can serializable by default so does not needs this custom formatter for AnimationCurve

Packages

MemoryPack has these packages.

  • MemoryPack
  • MemoryPack.Core
  • MemoryPack.Generator
  • MemoryPack.Streaming
  • MemoryPack.AspNetCoreMvcFormatter
  • MemoryPack.UnityShims

MemoryPack is the main library, it provides full support for high performance serialization and deserialization of binary objects. It depends on MemoryPack.Core for the core base libraries and MemoryPack.Generator for code generation. MemoryPack.Streaming adds additional extensions for Streaming Serialization. MemoryPack.AspNetCoreMvcFormatter adds input/output formatters for ASP.NET Core. MemoryPack.UnityShims adds Unity shim types and formatters for share type between .NET and Unity.

TypeScript and ASP.NET Core Formatter

MemoryPack supports TypeScript code generation. It generates class and serialization code from C#, In other words, you can share types with the Browser without using OpenAPI, proto, etc.

Code generation is integrated with Source Generator, the following options(MemoryPackGenerator_TypeScriptOutputDirectory) set the output directory for TypeScript code. Runtime code is output at the same time, so no additional dependencies are required.

<!-- output memorypack TypeScript code to directory -->
<ItemGroup>
    <CompilerVisibleProperty Include="MemoryPackGenerator_TypeScriptOutputDirectory" />
</ItemGroup>
<PropertyGroup>
    <MemoryPackGenerator_TypeScriptOutputDirectory>$(MSBuildProjectDirectory)\wwwroot\js\memorypack</MemoryPackGenerator_TypeScriptOutputDirectory>
</PropertyGroup>

A C# MemoryPackable type must be annotated with [GenerateTypeScript].

[MemoryPackable]
[GenerateTypeScript]
public partial class Person
{
    public required Guid Id { get; init; }
    public required int Age { get; init; }
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public required DateTime DateOfBirth { get; init; }
    public required Gender Gender { get; init; }
    public required string[] Emails { get; init; }
}

public enum Gender
{
    Male, Female, Other
}

Runtime code and TypeScript type will be generated in the target directory.

image

The generated code is as follows, with simple fields and static methods for serialize/serializeArray and deserialize/deserializeArray.

import { MemoryPackWriter } from "./MemoryPackWriter.js";
import { MemoryPackReader } from "./MemoryPackReader.js";
import { Gender } from "./Gender.js"; 

export class Person {
    id: string;
    age: number;
    firstName: string | null;
    lastName: string | null;
    dateOfBirth: Date;
    gender: Gender;
    emails: (string | null)[] | null;

    constructor() {
        // snip...
    }

    static serialize(value: Person | null): Uint8Array {
        // snip...
    }

    static serializeCore(writer: MemoryPackWriter, value: Person | null): void {
        // snip...
    }

    static serializeArray(value: (Person | null)[] | null): Uint8Array {
        // snip...
    }

    static serializeArrayCore(writer: MemoryPackWriter, value: (Person | null)[] | null): void {
        // snip...
    }
    static deserialize(buffer: ArrayBuffer): Person | null {
        // snip...
    }

    static deserializeCore(reader: MemoryPackReader): Person | null {
        // snip...
    }

    static deserializeArray(buffer: ArrayBuffer): (Person | null)[] | null {
        // snip...
    }

    static deserializeArrayCore(reader: MemoryPackReader): (Person | null)[] | null {
        // snip...
    }
}

You can use this type like following.

let person = new Person();
person.id = crypto.randomUUID();
person.age = 30;
person.firstName = "foo";
person.lastName = "bar";
person.dateOfBirth = new Date(1999, 12, 31, 0, 0, 0);
person.gender = Gender.Other;
person.emails = ["[email protected]", "[email protected]"];

// serialize to Uint8Array
let bin = Person.serialize(person);

let blob = new Blob([bin.buffer], { type: "application/x-memorypack" })

let response = await fetch("http://localhost:5260/api",
    { method: "POST", body: blob, headers: { "Content-Type": "application/x-memorypack" } });

let buffer = await response.arrayBuffer();

// deserialize from ArrayBuffer 
let person2 = Person.deserialize(buffer);

The MemoryPack.AspNetCoreMvcFormatter package adds MemoryPack input and output formatters for ASP.NET Core MVC. You can add MemoryPackInputFormatter, MemoryPackOutputFormatter to ASP.NET Core MVC with the following code.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddControllers(options =>
{
    options.InputFormatters.Insert(0, new MemoryPackInputFormatter());
    // If checkContentType: true then can output multiple format(JSON/MemoryPack, etc...). default is false.
    options.OutputFormatters.Insert(0, new MemoryPackOutputFormatter(checkContentType: false));
});

If you call from HttpClient, you can set application/x-memorypack to content-header.

var content = new ByteArrayContent(bin)
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-memorypack");

TypeScript Type Mapping

There are a few restrictions on the types that can be generated. Among the primitives, char and decimal are not supported. Also, OpenGenerics type cannot be used.

C# TypeScript Description
bool boolean
byte number
sbyte number
int number
uint number
short number
ushort number
long bigint
ulong bigint
float number
double number
string string | null
Guid string In TypeScript, represents as string but serialize/deserialize as 16byte binary
DateTime Date DateTimeKind will be ignored
enum const enum long and ulong underlying type is not supported
T? T | null
T[] T[] | null
byte[] Uint8Array | null
: ICollection<T> T[] | null Supports all ICollection<T> implemented type like List<T>
: ISet<T> Set<T> | null Supports all ISet<T> implemented type like HashSet<T>
: IDictionary<K,V> Map<K, V> | null Supports all IDictionary<K,V> implemented type like Dictionary<K,V>.
[MemoryPackable] class Supports class only
[MemoryPackUnion] abstract class

[GenerateTypeScript] can only be applied to classes and is currently not supported by struct.

Configure import file extension and member name casing

In default, MemoryPack generates file extension as .js like import { MemoryPackWriter } from "./MemoryPackWriter.js";. If you want to change other extension or empty, use MemoryPackGenerator_TypeScriptImportExtension to configure it. Also the member name is automatically converted to camelCase. If you want to use original name, use MemoryPackGenerator_TypeScriptConvertPropertyName to false.

<ItemGroup>
    <CompilerVisibleProperty Include="MemoryPackGenerator_TypeScriptOutputDirectory" />
    <CompilerVisibleProperty Include="MemoryPackGenerator_TypeScriptImportExtension" />
    <CompilerVisibleProperty Include="MemoryPackGenerator_TypeScriptConvertPropertyName" />
    <CompilerVisibleProperty Include="MemoryPackGenerator_TypeScriptEnableNullableTypes" />
</ItemGroup>
<PropertyGroup>
    <MemoryPackGenerator_TypeScriptOutputDirectory>$(MSBuildProjectDirectory)\wwwroot\js\memorypack</MemoryPackGenerator_TypeScriptOutputDirectory>
    <!-- allows empty -->
    <MemoryPackGenerator_TypeScriptImportExtension></MemoryPackGenerator_TypeScriptImportExtension>
    <!-- default is true -->
    <MemoryPackGenerator_TypeScriptConvertPropertyName>false</MemoryPackGenerator_TypeScriptConvertPropertyName>
    <!-- default is false -->
    <MemoryPackGenerator_TypeScriptEnableNullableTypes>true</MemoryPackGenerator_TypeScriptEnableNullableTypes>
</PropertyGroup>

MemoryPackGenerator_TypeScriptEnableNullableTypes allows C# nullable annotations to be reflected in TypeScript code. The default is false, making everything nullable.

Streaming Serialization

MemoryPack.Streaming provides MemoryPackStreamingSerializer, which adds additional support for serializing and deserializing collections with streams.

public static class MemoryPackStreamingSerializer
{
    public static async ValueTask SerializeAsync<T>(PipeWriter pipeWriter, int count, IEnumerable<T> source, int flushRate = 4096, CancellationToken cancellationToken = default)
    public static async ValueTask SerializeAsync<T>(Stream stream, int count, IEnumerable<T> source, int flushRate = 4096, CancellationToken cancellationToken = default)
    public static async IAsyncEnumerable<T?> DeserializeAsync<T>(PipeReader pipeReader, int bufferAtLeast = 4096, int readMinimumSize = 8192, [EnumeratorCancellation] CancellationToken cancellationToken = default)
    public static IAsyncEnumerable<T?> DeserializeAsync<T>(Stream stream, int bufferAtLeast = 4096, int readMinimumSize = 8192, CancellationToken cancellationToken = default)
}

Formatter/Provider API

If you want to implement formatter manually, inherit MemoryPackFormatter<T> and override the Serialize and Deserialize methods.

public class SkeltonFormatter : MemoryPackFormatter<Skelton>
{
    public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref Skelton? value)
    {
        if (value == null)
        {
            writer.WriteNullObjectHeader();
            return;
        }

        // use writer method.
    }

    public override void Deserialize(ref MemoryPackReader reader, scoped ref Skelton? value)
    {
        if (!reader.TryReadObjectHeader(out var count))
        {
            value = null;
            return;
        }

        // use reader method.
    }
}

The created formatter is registered with MemoryPackFormatterProvider.

MemoryPackFormatterProvider.Register(new SkeltonFormatter());

Note: unmanged struct(doesn't contain reference types) can not use custom formatter, it always serializes native memory layout.

MemoryPackWriter/ReaderOptionalState

Initializing MemoryPackWriter/MemoryPackReader requires OptionalState. It is wrapper of MemoryPackSerializerOptions, it can create form MemoryPackWriterOptionalStatePool.

// when disposed, OptionalState will return to pool.
using(var state = MemoryPackWriterOptionalStatePool.Rent(MemoryPackSerializerOptions.Default))
{
    var writer = new MemoryPackWriter<T>(ref t, state);
}

// for Reader
using (var state = MemoryPackReaderOptionalStatePool.Rent(MemoryPackSerializerOptions.Default))
{
    var reader = new MemoryPackReader(buffer, state);
}

Target framework dependency

MemoryPack provides netstandard2.1 and net7.0 but both are not compatible. For example, MemoryPackable types under netstandard2.1 project and use it from net7.0 project, throws runtime exception like this

Unhandled exception. System.TypeLoadException: Virtual static method '' is not implemented on type '' from assembly '*'.

Since net7.0 uses static abstract members (Virtual static method), that does not support netstandard2.1, this behavior is a specification.

.NET 7 project shouldn't use the netstandard 2.1 dll. In other words, if the Application is a .NET 7 Project, all the dependencies that use MemoryPack must support .NET 7. So if a library developer has a dependency on MemoryPack, you need to configure dual target framework.

<TargetFrameworks>netstandard2.1;net7.0</TargetFrameworks>

RPC

Cysharp/MagicOnion is a code-first grpc-dotnet framework using MessagePack instead of protobuf. MagicOnion now supports MemoryPack as a serialization layer via MagicOnion.Serialization.MemoryPack package(preview). See details: MagicOnion#MemoryPack support

Unity

Minimum supported Unity version is 2022.3.12f1.

The MemoryPack core package is provided by nuget. It is also available in Unity. If you want to get Unity built-in type support, we additionally provide MemoryPack.Unity extension.

  1. Install MemoryPack from NuGet using NuGetForUnity
  • Open Window from NuGet -> Manage NuGet Packages, Search "MemoryPack" and Press Install. screenshot

  • If you encount version conflicts error, please disable version validation in Player Settings(Edit -> Project Settings -> Player -> Scroll down and expand "Other Settings" than uncheck "Assembly Version Validation" under the "Configuration" section).

  1. Install the MemoryPack.Unity package by referencing the git URL
  • https://github.com/Cysharp/MemoryPack.git?path=src/MemoryPack.Unity/Assets/MemoryPack.Unity screenshot screenshot

MemoryPack uses the ..* release tag, so you can specify a version like #1.0.0. For example: https://github.com/Cysharp/MemoryPack.git?path=src/MemoryPack.Unity/Assets/MemoryPack.Unity#1.0.0

As with the .NET version, the code is generated by a code generator (MemoryPack.Generator.dll). Reflection-free implementation also provides the best performance in IL2CPP.

For more information on Unity and Source Generator, please refer to the Unity documentation.

Source Generator is also used officially by Unity by com.unity.properties and com.unity.entities. In other words, it is the standard for code generation in the next generation of Unity.

You can serialize all unmanaged types (such as Vector3, Rect, etc...) and some classes(AnimationCurve, Gradient, RectOffset). If you want to serialize other Unity-specific types, see Serialize external types section.

In Unity performance, MemoryPack is x3~x10 faster than JsonUtility.

image

If shared code has Unity's type(Vector2, etc...), MemoryPack provides MemoryPack.UnityShims package in NuGet.

The MemoryPack.UnityShims package provides shims for Unity's standard structs (Vector2, Vector3, Vector4, Quaternion, Color, Bounds, Rect, Keyframe, WrapMode, Matrix4x4, GradientColorKey, GradientAlphaKey, GradientMode, Color32, LayerMask, Vector2Int, Vector3Int, RangeInt, RectInt, BoundsInt) and some classes(AnimationCurve, Gradient, RectOffset).

Warning

Currently, the following limitations exist for use in Unity

  1. Unity version does not support CustomFormatter.
  2. If you are using .NET7 or later, MemoryPack binary format is not fully compatible with Unity.
    • This problem occurs with value types that [StructLayout(LayoutKind.Auto)] is explicitly specified. (The default for struct is LayoutKind.Sequencil.) For such types, binaries serialized in .NET cannot be deserialized in Untiy. Similarly, a binary serialized in Unity cannot be serialized in .NET side.
    • The affected types typically include the following types.
      • DateTimeOffset
      • ValueTuple
    • Currently, the simple solution is to not use these types.

Native AOT

Unfortunately, .NET 7 Native AOT causes crash (Generic virtual method pointer lookup failure) when use MemoryPack due to a runtime bug. It is going to be fixed in .NET 8. Using ``Microsoft.DotNet.ILCompiler` preview version, will fix it in .NET 7. Please see issue's comment how setup it.

Binary wire format specification

The type of T defined in Serialize<T> and Deserialize<T> is called C# schema. MemoryPack format is not self-described format. Deserialize requires the corresponding C# schema. These types exist as internal representations of binaries, but types cannot be determined without a C# schema.

Endian must be Little Endian. However, reference C# implementation does not care about endianness so can not use on big-endian machine. However, modern computers are usually little-endian.

There are eight types of format.

  • Unmanaged struct
  • Object
  • Version Tolerant Object
  • Circular Reference Object
  • Tuple
  • Collection
  • String
  • Union

Unmanaged struct

Unmanaged struct is C# struct that doesn't contain reference types, similar constraint of C# Unmanaged types. Serializing struct layout as it is, includes padding.

Object

(byte memberCount, [values...])

Object has 1byte unsigned byte as member count in header. Member count allows 0 to 249, 255 represents object is null. Values store memorypack value for the number of member count.

Version Tolerant Object

(byte memberCount, [varint byte-length-of-values...], [values...])

Version Tolerant Object is similar as Object but has byte length of values in header. varint follows these spec, first sbyte is value or typeCode and next X byte is value. 0 to 127 = unsigned byte value, -1 to -120 = signed byte value, -121 = byte, -122 = sbyte, -123 = ushort, -124 = short, -125 = uint, -126 = int, -127 = ulong, -128 = long.

Circular Reference Object

(byte memberCount, [varint byte-length-of-values...], varint referenceId, [values...])
(250, varint referenceId)

Circular Reference Object is similar as Version Tolerant Object but if memberCount is 250, next varint(unsigned-int32) is referenceId. If not, after byte-length-of-values, varint referenceId is written.

Tuple

(values...)

Tuple is fixed-size, non-nullable value collection. In .NET, KeyValuePair<TKey, TValue> and ValueTuple<T,...> are serialized as Tuple.

Collection

(int length, [values...])

Collection has 4 byte signed integer as data count in header, -1 represents null. Values store memorypack value for the number of length.

String

(int utf16-length, utf16-value)
(int ~utf8-byte-count, int utf16-length, utf8-bytes)

String has two-forms, UTF16 and UTF8. If first 4byte signed integer is -1, represents null. 0, represents empty. UTF16 is same as collection(serialize as ReadOnlySpan<char>, utf16-value's byte count is utf16-length * 2). If first signed integer <= -2, value is encoded by UTF8. utf8-byte-count is encoded in complement, ~utf8-byte-count to retrieve count of bytes. Next signed integer is utf16-length, it allows -1 that represents unknown length. utf8-bytes store bytes for the number of utf8-byte-count.

Union

(byte tag, value)
(250, ushort tag, value)

First unsigned byte is tag that for discriminated value type or flag, 0 to 249 represents tag, 250 represents next unsigned short is tag, 255 represents union is null.

License

This library is licensed under the MIT License.

memorypack's People

Contributors

aarnott avatar dependabot[bot] avatar dustedham avatar filzrev avatar github-actions[bot] avatar guitarrapc avatar hadashia avatar hankovich avatar jasonxudeveloper avatar jodydonetti avatar joseftw avatar mihailskuzmins avatar neuecc avatar omurefeguclu avatar pcysl5edgo avatar rameel avatar rauhs avatar seunghulee avatar thorgeirk11 avatar timothymakkison avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

memorypack's Issues

the "new" keyword causing member to be initialized twice in generated code.

if u have a member in a MemoryPackable type like this,
public new string Description { get; set; }

u'll get this in generated code:

   [global::MemoryPack.Internal.Preserve]
    public static void Deserialize(ref MemoryPackReader reader, scoped ref PluginInfo? value)
    {

        if (!reader.TryReadObjectHeader(out var count))
        {
            value = default!;
            goto END;
        }


        string __Description;
    ...
        string __Description;
     ...
}

.NET 7 Native AOT error: Generic virtual method pointer lookup failure

First, thanks for updating MemoryPack to work with .NET 7 and AOT.
Unfortunately my native AOT test program (latest .NET 7) is crashing with the error below:
image

(it works OK in debug and release mode in Visual Studio, but crashes only after publishing and running the AOT executable)

Here is the full test code:

using MemoryPack;

namespace Tests
{
    [MemoryPackable]
    public partial class MemPackObject
    {
        public string TestString { get; set; }
    }

    class MemPackTest
    {
        static void Main(string[] args)
        {
            MemPackObject test_object = new MemPackObject();
            test_object.TestString = "ABC";
            Console.WriteLine("Initial value before serializing: " + test_object.TestString);

            byte[] serialized_object = MemoryPackSerializer.Serialize(test_object);
            MemPackObject? deserialized_object = MemoryPackSerializer.Deserialize<MemPackObject>(serialized_object);

            if (deserialized_object != null)
            {
                Console.WriteLine("Final value after deserializing: " + deserialized_object.TestString);
            }

            Console.WriteLine("Test completed. Press any key to exit");
            Console.ReadKey();
        }

    }
}

Unsafe.dll not included in the unitypackage?

The documentation says "The dependency managed DLL System.Runtime.CompilerServices.Unsafe/6.0.0 is included with unitypackage", but v1.4.2 Unsafe.dll was not included with unitypackage.

I attached the import screen of MemoryPack v1.4.2 unitypackage for your reference.

image

MemoryPackable Class cannot be deserialized to MemoryPackable Subclass?

ClassA
{
}

ClassB : ClassA
{
// ClassB may have some extra properties in addtion to ClassA
}


ClassA objectA = new();


ClassB objectB =  MemoryPackSerializer.Deserialize<ClassB>(MemoryPackSerializer.Serialize(objectA));

this throws exception:

MemoryPack.MemoryPackSerializationException:“Length header size is larger than buffer size, length: 2048.

Why?

Support Order field on Members attributes

Like most serializers, it would be nice to have an attribute field specifying the order of that field instead of relying purely on C# member ordering.

[Member(Order=1)]

A plus would be interop with [DataContract], [DataMember] attributes.

Integration with MagicOnion

Will there be integration with MagicOnion?
I would like to see an example in ASP Net with zero allocation for objects of a value type, for example structures.

FluentAPI support?

In the readme you explain that we need to add [MemoryPackable] attribute. I want to avoid this and have clean classes and add such thing via FluentAPI like EF does. Is this already supported or will this be supported?

Feature proposal: Formatter Merging, to support assembly dependency architecture and assemblies loaded during runtime(such as plugin)

AssemblyA:

[MemoryPackUnionFormatter(typeof(IPlugin))]
 [MemoryPackUnion(0, typeof(Plugin1))]
 [MemoryPackUnion(1, typeof(Plugin2))]
 public partial class PluginFormatter
 { }

AssemblyB:

[MemoryPackUnionFormatter(typeof(IPlugin))]
[MemoryPackUnion(2, typeof(Plugin3))]
[MemoryPackUnion(3, typeof(Plugin4))]
public partial class PluginFormatter
{ }

Question: Is it possible to merge the MemoryPackUnionFormatters declared in different assemblies (like above) referencing the same interface with/without same class names?
The underlying solution might be allowing formatters for each interface be registered more than once and merge the types into one type dictionary (together with their tags) or be search through a hierachy of dicitonaries.

This solves following issue:

  1. MemoryPackUnionFormatter must be centralized in the final consumer libs, and cannot be distributed in different assemblies, if these libs r used by different programs, each program must write their own formatter (a lot of redundency work), why can not the type registration be done by each assembly on module basis and they merge to one when compiled, while programmers can negotiate tag segments for each assembly to avoid conflicts, like 1-10 for assembly one, 20-30 for another?

  2. Unreferenced types(with known interface) declared in plugin assembly r not applicable for MemoryPack, cos they r not pre-registered in the host program, but what if their types come as compiled with MemoryPack with code already generated?

GeneratedRegex or RegexGenerator?

Hi. I just cloned the repo and tried to build with .net 7.0.100-preview.7.22377.5 and got this error:

error CS0246: The type or namespace name 'GeneratedRegexAttribute' could not be found (are you missing a using directive or an assembly reference?)

[GeneratedRegex(@", Version=\d+.\d+.\d+.\d+, Culture=[\w-]+, PublicKeyToken=(?:null|[a-f0-9]{16})")]

But when I replace GeneratedRegex with RegexGenerator It builds successfully.

MEMPACK019 when compiling a subclass inherited from Dictionary or ConcurrentDictionary.

MEMPACK019 The MemoryPackable object 'ConfigDictionary' member 'Comparer' type 'global::System.Collections.Generic.IEqualityComparer' is not MemoryPackable. Annotate [MemoryPackable] to 'global::System.Collections.Generic.IEqualityComparer' or if external type that can serialize, annotate [MemoryPackAllowSerialize] to member ....

"Comparer" seems a protected member of Dictionary or ConcurrentDictionary.

[MemoryPackable]
    public partial class ConfigDictionary<T> : Dictionary<string, T>
 {
}

Endianness

I think we should force little endian

netstandard2.1 compiling error: CS8987 The ‘scoped’ modifier of parameter ‘value’ doesn’t match overridden or implemented member

when work with netstandard2.1, the compiler reports on the generated code:
CS8987 The ‘scoped’ modifier of parameter ‘value’ doesn’t match overridden or implemented member.

how to solve this and get MemoryPack work with netstandard2.1?
since i've already dual-targeted both netstandard2.1 and net7.0, and this compiler error prevents my netstandard2.1 library to be compiled.

partial class Stat
{
    [global::MemoryPack.Internal.Preserve]
    sealed class StatFormatter : MemoryPackFormatter<Stat>
    {
        [global::MemoryPack.Internal.Preserve]
        public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer,  ref Stat value)            <-- here
        {
            Stat.Serialize(ref writer, ref value);
        }

        [global::MemoryPack.Internal.Preserve]
        public override void Deserialize(ref MemoryPackReader reader, ref Stat value)           <-- and here
        {
            Stat.Deserialize(ref reader, ref value);
        }
    }
}

Support for missing / deleted properties

Hi, the Version tolerant section mentions that

MemoryPackable objects, members can be added, but not deleted

I'm trying to use MemoryPack for RPC between two sides that should support different versions.

If I add a property to one side, serialize it, and send it to the other side which doesn't have the added property yet, deserialization crashes in MemoryPackSerializationException.ThrowInvalidPropertyCount.

It's a bit confusing, because technically I added a property, but for the deserializer it's as if I deleted it, which is not supported. Would it be possible to ignore deleted properties?

[MemoryPackUnionFormatter] (net7.0) yields compiling error: CS8987 The ‘scoped’ modifier of parameter ‘value’ doesn’t match overridden or implemented member

I have various Types defined in 3 libs, namely Model, Man, Config, and these types are finally used in the lib: Server
i.e. Server depends on Model, Man, Config, otherwise Man and Config depends on Model.
And, All these Types inherits a single interface IMyMemoryPackable defined in Model.

I wrote a MyMemoryPackableFormtter.cs in lib Server like this to register all these types:

[MemoryPackUnionFormatter(typeof(IMyMemoryPackable))]
    [MemoryPackUnion(0, typeof(Type1))]
    [MemoryPackUnion(1, typeof(Type2))]
    [MemoryPackUnion(2, typeof(Type3))]
    public partial class MyMemoryPackableFormatter
    {
    }

but compiler yields:
Server (net7.0) :
compiling error: CS8987 The ‘scoped’ modifier of parameter ‘value’ doesn’t match overridden or implemented member

However,
if i split this MyMemoryPackableFormatter into three separate Formatters(note all these formatters register for the same interface, namely IMyMemoryPackable) into three libs (no matter their names r the same or not), the compiler compiles OK and the program can launch, but the generated code seems only to use one Formatter to do serialize Job (other Types cannot be found ).

Breaking change of binary format, add tuple type

Currently tuple is serialized:

  • If all T is unmanaged, serialize as unamanged struct
  • If T has included reference type, serialize as object format(add object header)

Shouldn't we have a type that is serialized as a continuous value with no object header?

Add a new constructor for MemoryPackUnionAttribute by using FullName,Assembly info of a type in addition to typeof(Type)

Suggested new constructor: [MemoryPackUnion(0, "MyNamespace.MyType, MyAssemblyName")]

this is useful when the implementing type is not in the same assembly with the Interface, which is usually isolated from its implementation and MemoryPackUnionAttribute may not possibly make a reference to it by typeof() operator.

this is a similar measure in:
[Editor("MyNamespace.ByteArrayEditor, MyAssemblyName", "System.Drawing.Design.UITypeEditor, System.Windows.Forms")]

Support for serializing to Spans

I propose new overloads for Serialize and Deserialize which operate on a Span<Byte> and ReadOnlySpan<T>, respectively.

Additionally, a new method would be required which returns the calculated size of the serialized object. Something like GetSerializedByteLength.

Together, it would enable zero-allocation serialization on the stack like this:

const int maxStackAllocSize = 4096;
var length = MemoryPackSerializer.GetSerializedByteLength(v);
Span<byte> buf = length <= maxStackAllocSize ? stackalloc Byte[length] : new Byte[length];
MemoryPackSerializer.Serialize(v, buf);
var val = MemoryPackSerializer.Deserialize<Person>(bin, buf);

Serialization of `List<T>` and `Dictionary<K,V>` fails on Unity + IL2CPP

I executed the following code on an IL2CPP built Android app and got an exception.

I built the app with Unity 2021.3.6f1, and executed it on Pixel 4a(Android) & iPhoneX(iOS).
Managed stripping code level set to minimal.

// List<T> case
var list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);

var bin = MemoryPackSerializer.Serialize(list); // Caught an exception here.
var val = MemoryPackSerializer.Deserialize<List<int>>(bin);
Autoconnected Player "Autoconnected Player" ExecutionEngineException: Attempting to call method 'MemoryPack.Formatters.UnmanagedFormatter`1<System.Int32>::Serialize<MemoryPack.Internal.ReusableLinkedArrayBufferWriter>' for which no ahead of time (AOT) code was generated.  Consider increasing the --generic-virtual-method-iterations=1 argument
  at MemoryPack.Formatters.ListFormatter`1[T].Serialize[TBufferWriter] (MemoryPack.MemoryPackWriter`1[TBufferWriter]& writer, System.Collections.Generic.List`1[T]& value) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPack.MemoryPackSerializer.Serialize[T,TBufferWriter] (MemoryPack.MemoryPackWriter`1[TBufferWriter]& writer, T& value) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPack.MemoryPackSerializer.Serialize[T] (T& value, MemoryPack.MemoryPackSerializeOptions options) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPackCheck.Start () [0x00000] in <00000000000000000000000000000000>:0 
MemoryPackCheck:Start()
// Dictionary<K,V> case
var dict = new Dictionary<int, string>();
dict[1] = "one";
dict[2] = "two";
dict[3] = "three";

var bin = MemoryPackSerializer.Serialize(dict);  // Caught an exception here.
var val = MemoryPackSerializer.Deserialize<Dictionary<int, string>>(bin);
Autoconnected Player "Autoconnected Player" ExecutionEngineException: Attempting to call method 'MemoryPack.Formatters.KeyValuePairFormatter`2<System.Int32, System.String>::Serialize<MemoryPack.Internal.ReusableLinkedArrayBufferWriter>' for which no ahead of time (AOT) code was generated.  Consider increasing the --generic-virtual-method-iterations=1 argument
  at MemoryPack.Formatters.DictionaryFormatter`2[TKey,TValue].Serialize[TBufferWriter] (MemoryPack.MemoryPackWriter`1[TBufferWriter]& writer, System.Collections.Generic.Dictionary`2[TKey,TValue]& value) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPack.MemoryPackSerializer.Serialize[T,TBufferWriter] (MemoryPack.MemoryPackWriter`1[TBufferWriter]& writer, T& value) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPack.MemoryPackSerializer.Serialize[T] (T& value, MemoryPack.MemoryPackSerializeOptions options) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPackCheck.Start () [0x00000] in <00000000000000000000000000000000>:0 
MemoryPackCheck:Start()
// List<T> property case
[MemoryPackable]
public partial class MyContainer2
{
    public List<int> List = new List<int>();
}

var container = new MyContainer2();

container.List.Add(1);
container.List.Add(2);
container.List.Add(3);

var bin = MemoryPackSerializer.Serialize(container);  // Caught an exception here.
var val = MemoryPackSerializer.Deserialize<MyContainer2>(bin);
Autoconnected Player "Autoconnected Player" ExecutionEngineException: Attempting to call method 'MemoryPack.Formatters.UnmanagedFormatter`1<System.Int32>::Serialize<MemoryPack.Internal.ReusableLinkedArrayBufferWriter>' for which no ahead of time (AOT) code was generated.  Consider increasing the --generic-virtual-method-iterations=1 argument
  at MemoryPack.Formatters.ListFormatter`1[T].Serialize[TBufferWriter] (MemoryPack.MemoryPackWriter`1[TBufferWriter]& writer, System.Collections.Generic.List`1[T]& value) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPack.MemoryPackSerializer.Serialize[T,TBufferWriter] (MemoryPack.MemoryPackWriter`1[TBufferWriter]& writer, T& value) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPack.MemoryPackSerializer.Serialize[T] (T& value, MemoryPack.MemoryPackSerializeOptions options) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPackCheck.Start () [0x00000] in <00000000000000000000000000000000>:0 
MemoryPackCheck:Start()
// Dictionary<K,V> property case
[MemoryPackable]
public partial class MyContainer
{
    public Dictionary<int, string> Dict = new Dictionary<int, string>();
}

var container = new MyContainer();

container.Dict[1] = "One";
container.Dict[2] = "Two";
container.Dict[3] = "Three";

var bin = MemoryPackSerializer.Serialize(container);  // Caught an exception here.
var val = MemoryPackSerializer.Deserialize<MyContainer>(bin);
Autoconnected Player "Autoconnected Player" ExecutionEngineException: Attempting to call method 'MemoryPack.Formatters.KeyValuePairFormatter`2<System.Int32, System.String>::Serialize<MemoryPack.Internal.ReusableLinkedArrayBufferWriter>' for which no ahead of time (AOT) code was generated.  Consider increasing the --generic-virtual-method-iterations=1 argument
  at MemoryPack.Formatters.DictionaryFormatter`2[TKey,TValue].Serialize[TBufferWriter] (MemoryPack.MemoryPackWriter`1[TBufferWriter]& writer, System.Collections.Generic.Dictionary`2[TKey,TValue]& value) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPack.MemoryPackSerializer.Serialize[T,TBufferWriter] (MemoryPack.MemoryPackWriter`1[TBufferWriter]& writer, T& value) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPack.MemoryPackSerializer.Serialize[T] (T& value, MemoryPack.MemoryPackSerializeOptions options) [0x00000] in <00000000000000000000000000000000>:0 
  at MemoryPackCheck.Start () [0x00000] in <00000000000000000000000000000000>:0 
MemoryPackCheck:Start()

Support external union metadata

It is impossible to serialize classes that are defined in external libraries, so it would be nice if you could provide the same information as MemoryPackUnion, but via a builder system instead.

From your own example. Instead of doing this:

[MemoryPackable]
[MemoryPackUnion(0, typeof(FooClass))]
[MemoryPackUnion(1, typeof(BarClass))]
public partial interface IUnionSample { }

[MemoryPackable]
public partial class FooClass : IUnionSample { }

[MemoryPackable]
public partial class BarClass : IUnionSample { }

It would be nice to do this:

These 3 classes are provided by an external library that can't take a dependency on MemoryPack. so they look like this (in the library):

public interface IUnionSample { }
public class FooClass : IUnionSample { }
public class BarClass : IUnionSample { }

Since they are not partial, we can't use source generator etc. - instead we create a custom formatter. But in order to support union/polymorphism, we need to provide the inheritance relationships somehow. It could be done like this:

UnionBuilder ub = new UnionBuilder(typeof(IUnionSample));
ub.AddUnion(0, typeof(FooClass));
ub.AddUnion(1, typeof(BarClass));

//Then we provide 'ub' to the serializer in some way.

Note that I just come up with an example on the spot. I have no preference for how the above is achieved.

Why allocating if value is unmanaged?

Hi, about this code:

public static byte[] Serialize<T>(in T? value, MemoryPackSerializerOptions? options = default)
    {
        if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>())
        {
            var array = AllocateUninitializedArray<byte>(Unsafe.SizeOf<T>());
            Unsafe.WriteUnaligned(ref GetArrayDataReference(array), value);
            return array;
        }

I am not sure why ReusableLinkedArrayBufferWriter is not used for this case too. wouldn't this mean a certain allocation?

Can an extra interface representing the abstract for all MemoryPackable types be added ?

CASE: I need to serialize any MemoryPackable objects using an array or colleciton but seems not possible, cos the most abstract interface IMemoryPackFormatterRegister has a static member, which can not be used as a type parameter.

I think a more abstract interface without static member could solve this problem, couldn't it?

SUGGEST:

namespace MemoryPack
{
    public interface IMemoryPackFormatterRegister: IMemoryPackObject
    {
        static void RegisterFormatter();
    }


    public interface IMemoryPackObject {} 

}



lexically, i prefer the name IMemoryPackable, but it was already used.
A more reasonable hierachy might be IMemoryPackObject: IMemoryPackFormatterRegister : IMemoryPackable

Unable to compile when [MemoryPackable] class has multiple constructors

I'm testing MemoryPack (latest .NET 7) with a class that has multiple constructors, and I marked one of the constructors as '[MemoryPackConstructor]'.
However, the project doesn't compile and shows an error during compilation:
"Error CS0234 The type or namespace name 'Formatters' does not exist in the namespace 'Tests.MemoryPack' (are you missing an assembly reference?)"

And it points to line 44 in generated code:
image

Here is my full test code:

using MemoryPack;

namespace Tests.MemoryPack
{
    [MemoryPackable]
    public partial class TestObject
    {
        public string testString { get; set; } = "";


        [MemoryPackConstructor]
        public TestObject() { }

        public TestObject(string ignore) { }
    }

    class MemPackTest
    {
        public static void Main()
        {
            TestObject test_object = new();
            test_object.testString = "ABC";
            Console.WriteLine("Initial value before serializing: " + test_object.testString);

            byte[] serialized_object = MemoryPackSerializer.Serialize(test_object);
            TestObject? deserialized_object = MemoryPackSerializer.Deserialize<TestObject>(serialized_object);

            if (deserialized_object != null)
            {
                Console.WriteLine("Final value after deserializing: " + deserialized_object.testString);
            }

            Console.WriteLine("Test completed. Press any key to exit");
            Console.ReadKey();
        }

    }
}

wide Union tag

Currently only support byte.
add ushort,or uint tag support.

Unity support

I’m curious if supporting a lower version is possible? If we target C# 9/.NET Standard 2.1/Roslyn 3.9.0 we can support Unity. @neuecc I know you already know all of this, I’m just curious what features from C# 11/.NET 7 are required. Thanks.

__typeToTag.TryGetValue(value.GetType(), out var tag) cannot identify the same Type created in a plugin.

if u have a type used in a plugin (i.e. dynamically loaded assembly) while registered with [MemoryPackUnionFormatter] in the host program, the following line in in the generated code seems unable to get the Type, even The type names are same.

if (__typeToTag.TryGetValue(value.GetType(), out var tag))

this is possibly because the Type registered in __typeToTag is not the same object with value.GetType(), even though their FullNames and GUIDs r the same, for the former was instantiated at host launch, but the latter was instantiated when load the plugin.

is it better to use type GUID as Key?

TypeScript generator does not generate serializers and enums

I was impressed by how amazing this library is!!
And, even more, after seeing that TypeScript would be supported.

Bug description

I don't know yet if there is a problem with the latest versions, but it looks like the generator is not generating the runtime and dependent files.

image

Expected behavior

  • Runtime should be emitted;
  • Dependent types should also be generated.

Working version

Version 1.8.6 is working as expected.

Another possible bug

Thank you for the fast fix on my last issue. But when that was solved is stumbled on another edge case with int. I think the problem occurs when deserializing to a primitive type without generics so my best guess is that it is some sort of boxing issue but I failed to narrow it down further. Fortunately the issue can be recreated with the following code

        var buff = MemoryPackSerializer.Serialize<int>(0);
        var thisWorks = MemoryPackSerializer.Deserialize<int>(buff);
        var thisThrows = MemoryPackSerializer.Deserialize( typeof(int), buff);

If this isn't valid code just close the issue, thanks in advance/ Mattias

TODO

HttpClient Example for SandboxWebApp

Hello,

I am trying to write a c# console app client for the SandboxWebApp using HttpClient, but so far i have no been sucessful.

How can i use the HttpClient for calling this endpoint?

This is the code for my console app that I am testing:

using MemoryPack;

TestModel value = new() { Id = Guid.NewGuid(), Name = "Test" };


var bin = MemoryPackSerializer.Serialize(value);

HttpClient httpClient = new();

HttpRequestMessage request = new() {
    Content = new ByteArrayContent(bin),
    RequestUri= new Uri("http://localhost:5173"),
    Method= HttpMethod.Post
};

request.Headers.TryAddWithoutValidation("Content-Type", "application/x-memorypack");

var response = await httpClient.SendAsync(request);



[MemoryPackable]
public partial class TestModel
{
    public Guid Id { get; set; }

    public string? Name { get; set; }

}

Bug: failed System.TypeLoadException

The HostOptions.BackgroundServiceExceptionBehavior is configured to StopHost. A BackgroundService has thrown an unhandled exception, and the IHost instance is stopping. To avoid this behavior, configure this to Ignore; however the BackgroundService will not be restarted. System.TypeLoadException: Virtual static method 'Serialize' is not implemented on type 'SystemModule.Packets.ServerPackets.ServerDataMessage' from assembly 'SystemModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

Possible bug

Hi and thanks for your work to provide super-fast serializers for c#.
When testing a migration from MessagePack to MemoryPack I think I stumbled upon a possible bug when serializing a int into IBufferWriter.

I think the trouble is on row 86 in MemoryPackSerializer.Serialize.cs where Advance(span.Length) is called but the span.Length can be much larger than the serialized data since its legal for IBufferWriter.GetSpan() on row 84 to return a larger span than requested.

Hope I'm describing the issue I am seeing well enough /Mattias

How to mark up external objects (ie Unity Vector3, etc)

Hi,

This looks great and congrats on the new library. It's clear you've been using this library in Unity projects so I hope there is a simple answer here: If one uses Newtonsoft .NET JSON serializer in a Unity project you typically have to define some custom contracts to properly serialize various Unity structs and classes that are common to add to serialized data. For example an AnimationCurve.
Does MemoryPack have a similar ability to mark up or write contracts for external C# structures that we cannot access?

Typescript casing

I have testet out the library, it's fantastic :)
Is there any way to control the casing of generated typescript models?

Net 6 Support

Hello, this is a pretty good project for dotneters. However, since Net6 is the newest LTS, is there any possibility of adding support for Net6 with the help of other packages? Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.