Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serializing polymorphic records with constructor parameters #9026

Open
slawomirpiotrowski opened this issue May 27, 2024 · 3 comments
Open

Comments

@slawomirpiotrowski
Copy link

Is serializing polymorphic records with constructor parameters supposed to work?

I mean something like this:

[GenerateSerializer]
public abstract record BaseRecord
{
  public abstract int test();
}

[GenerateSerializer]
public record DerivedRecord(int Value) : BaseRecord
{
  public override int test() => Value;
}

And then:

public interface ITestGrain : IGrainWithGuidKey
{
  Task<int> test(BaseRecord param);
}

public class TestGrain : Grain, ITestGrain
{
  public async Task<int> test(BaseRecord param)
  {
    return param.test();
  }
}

In my tests Value always has default value (zero). So result of the call is always zero (if serialization is involved, so not in local calls).

If I make Value a property instead of a constructor parameter then it works, but it means I can't easily make it read only.

@MickaelThumerel
Copy link

  1. Try "record class" instead of "Record"
  2. Try removing abstract word.

@slawomirpiotrowski
Copy link
Author

@MickaelThumerel it didn't change anything. This doesn't work:

    [GenerateSerializer]
    public record class BaseRecord
    {
        public virtual int test() => -1;
    }

    [GenerateSerializer]
    public record class DerivedRecord(int Value) : BaseRecord
    {
        public override int test()
            => Value;
    }

This doesn't work either:

    [GenerateSerializer]
    public abstract record BaseRecord(int Value)
    {
        public abstract int test();
    }

    [GenerateSerializer]
    public record DerivedRecord(int Value) : BaseRecord(Value)
    {
        public override int test()
            => Value;
    }

Need to get rid of constructor parameter to make it work, for example:

    [GenerateSerializer]
    public record BaseRecord
    {
        public virtual int test() => -1;
    }

    [GenerateSerializer]
    public record DerivedRecord : BaseRecord
    {
        [Id(0)]
        public int Value { get; set; }
        
        public override int test()
            => Value;
    }

Obviously using ordinary class also works:

    [GenerateSerializer]
    public abstract class BaseRecord
    {
        public abstract int test();
    }

    [GenerateSerializer]
    public class DerivedRecord : BaseRecord
    {
        [Id(0)]
        public int Value { get; set; }
        
        public override int test()
            => Value;
    }

It looks like constructor parameters are lost during default serialization / deserialization. Obviously can also write custom serializer to deal with that.

@MickaelThumerel
Copy link

A record have 2 constructors. One with the parameter you pass and another (also called a copy constructor (LIKE c++)) that take another instance and make a copy.

It true that many Mapper or serializer framework are not ready to process record properly.

In a project call Democrite i encounter a similar problem.
Example : https://github.com/Nexai-net/democrite/blob/main/src/Frameworks/Democrite.Framework.Core.Abstractions/Surrogates/ConcretTypeSurrogate.cs

I choose to create a base interface as common type and record structure as surrogate.
To be able to managed the polymorphism i had to create a converter for each sub type and a parent one that convert the ISurrogate in concret type.

Serialization, surrogate ... is an heavy point to write but it's due to a choice of performance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants