Cysharp / MemoryPack

Zero encoding extreme performance binary serializer for C# and Unity.
MIT License
3.29k stars 193 forks source link

Idea. Setting classes packable via inheritance/implementation. #124

Closed dest-all closed 1 year ago

dest-all commented 1 year ago

Additional to marking class for MemoryPack source generation, it may be handy to do the same with interface implementation or other class inheritance. We mark a class/interface with a, for example, "PackCascade" attribute and then all its heir/implementing types will be treated as though they are marked with the "MemoryPack" attribute. The reasoning for usefulness of this feature is that:

I could help making the thing, but I'm new to community coding, so any guide on how to deliver the code changes to this specific project I'd much appreciate.

neuecc commented 1 year ago

Thanks for the suggestion. I can't really imagine its usefulness, so it would be great if you could show me some pseudo code.

dest-all commented 1 year ago

Thanks for the suggestion. I can't really imagine its usefulness, so it would be great if you could show me some pseudo code.

Here you are.

All the idea will work only in .net 7+.

class CacheManager // Ability to have something like this class is the main goal of the suggested feature 
    void PutToCache(byte[] info) => /*implementation*/ 
    byte[] GetByKey(object key) => /*implementation*/ 

    public void Put<T>(object key, T item) where T : IPackable // for super-efficient outer-service cache storage this service should accept only MemoryPackable types - and alas we cannot set the requirement like 'HasAttribute<MemoryPackable>'
    {
        var infoCached = item.ToByteArray();        
        PutToCache(infoCached);
    }
    public T Get<T>(object key) where T : IPackable<T>
    {
        var infoCached = GetByKey(key);
        return T.FromByteArray(infoCached); 
    }

    /*
    The two public methods demostrate how elegantly we do the type guessing logic at compile type + have to write less code for it.
    */

[PackCascade]
partial interface IPackable // Interface name here is arbitrary - it will a parameter on the source generation step

// With creating different interfaces we allow ourselves to create different scopes for deserialization.
// For example, here we introduced IPackable and created logic around its types. If we add, say, ISendable the logic will be separate and ISendable and IPackable won't cross.

partial abstract class Entity : IPackable // now we treat the class and its heirs as if they had "[MemoryPackable]" above it.
    public long Id { get; }

partial class PersonInfo : Entity
    public string FirstName { get; set; }
    public string LastName { get; set; }

partial class EmployeeInfo : PersonInfo
    public long[] ResponsibleForOrders { get; set; }

partial class Order : Entity
    public long ResponsibleEmployeeId { get; set; }

Generated code.

partial interface IPackable
    byte[] ToByteArray();

partial interface IPackable<TSelf> : IPackable // new interface with awareness of the implementing type - for deserizliation purposes
    static abstract TSelf FromByteArray(byte[] bytes);

partial abstract class Entity
    public abstract byte[] ToByteArray(); // if class is abstract then interface implementation has to be abstract too.
    byte[] IPackable.ToByteArray() => ToByteArray();

// Generate the standard code for MemoryPackSerializer for PersonInfo here

partial class PersonInfo : IPackable<PersonInfo>
    public static PersonInfo FromByteArray(byte[] bytes) => MemoryPackSerializer.Deserialize<PersonInfo>(bytes);
    public override byte[] ToByteArray() => MemoryPackSerializer.Serialize(this);

// Generate the standard code for MemoryPackSerializer for EmployeeInfo here

partial class EmployeeInfo : IPackable<PersonInfo>
    public static EmployeeInfo FromByteArray(byte[] bytes) => MemoryPackSerializer.Deserialize<EmployeeInfo>(bytes);
    public override byte[] ToByteArray() => MemoryPackSerializer.Serialize(this);

// Generate the standard code for MemoryPackSerializer for Order here

partial class Order : IPackable<PersonInfo>
    public static EmployeeInfo FromByteArray(byte[] bytes) => MemoryPackSerializer.Deserialize<EmployeeInfo>(bytes);
    public override byte[] ToByteArray() => MemoryPackSerializer.Serialize(this);`
dest-all commented 1 year ago

Thanks for the suggestion. I can't really imagine its usefulness, so it would be great if you could show me some pseudo code.

Have you made any decision on this one?

neuecc commented 1 year ago

I have a feeling that this is within the scope of what you can devise on your application side. If you prefer generics, there is IMemoryPackable<T>.