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

Automatically select constructor #73

Open
DaniilSokolyuk opened this issue Nov 4, 2019 · 4 comments
Open

Automatically select constructor #73

DaniilSokolyuk opened this issue Nov 4, 2019 · 4 comments
Assignees

Comments

@DaniilSokolyuk
Copy link

DaniilSokolyuk commented Nov 4, 2019

Hi, i am using Wire(hyperion) and trying use Ceras for my library as main serializer because hyperion is poorly maintained and messagepack is not user friendly for customers:)
There is my model https://gist.github.com/DaniilSokolyuk/64f933c5fe5deb71f0c1cc35d59cf820, i have 2 problems

  1. how to automatically select constructor like Wire
  2. when i configured constructor by ....ConfigType<...>().ConstructBy(() => new MethodModel(null, ...)); and got an error "no source field or property could be found to populate the parameter 'Type declaringType'" but name is matched
@rikimaru0345
Copy link
Owner

Hi,
there's one thing I want to mention first; in case you only need that class you linked because you "manually" transmit MethodInfos: don't! 😄
Ceras already serializes things like FieldInfo, PropertyInfo, Type (well, obviously since that's a fundamental requirement of supporting interfaces / inheritance :P), and also MethodInfo.

Anyway, now as for your questions:

1. how to automatically select constructor like Wire

I'm not sure. How does Wire do it?
There are 3 major ways you can serialize something that has a constructor:
- Adding a constructor without parameters. If there is a ctor with no parameters (must not be private) Ceras will use it.

- Using TypeConfig to select one (as you did)

- Using an uninitialized object.

/// <summary>
/// Create an object without running any of its constructors
/// </summary>
public TypeConfig<T> ConstructByUninitialized()

But keep in mind that this does not call ANY constructor!! Meaning that if your constructor(s) don't just set fields/props, but also cause side effects; then those will not happen. However you can just use the [OnAfterDeserializeAttribute] to run any method after all members have been set 😄

That said; maybe Ceras should try to be smart and automatically select some constructor? Maybe filter out the ones that it couldn't possibly use (those where it can't match up members and parameters automatically), and then take the one with the most amount of parameters? And finally, if that results in multiple potential constructors, just throw.
Hmm, but then again, in pretty much every case where software tries to be smart it just causes more trouble than it's worth in the end 😂
What do you think?

when i configured constructor by...

That does indeed seem like a bug! 😮
How can I replicate it? Can you post some code? The setup of your SerializerConfig etc.

@DaniilSokolyuk
Copy link
Author

DaniilSokolyuk commented Nov 5, 2019

Thanks for the detailed answer!

Ceras already serializes things like FieldInfo, PropertyInfo, Type (well, obviously since that's a fundamental requirement of supporting interfaces / inheritance :P), and also MethodInfo.

Cool, but i also support the other serializers, so it was easier), and I also think MethodInfo stores a lot of garbage 😄

How does Wire do it?

looks like by uninitialized object too, in my test for MethodModel, constructor is not called щт deserialize :(, sorry, i will use uninitialized object in OnConfigNewType :)

How can I replicate it?
Bug repro

using Ceras;
using System;
using System.Linq;
using System.Reflection;

namespace ReproCerasBug
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new SerializerConfig();
            config.Warnings.ExceptionOnStructWithAutoProperties = false;
            config.OnConfigNewType = (t) =>
            {
                t.TargetMembers = TargetMember.AllPublic;

                if (typeof(Exception).IsAssignableFrom(t.Type))
                {
                    t.TargetMembers = TargetMember.All;
                }
            };

            //config.ConfigType<MethodModel>().ConstructBy(() => new MethodModel(null, null, null, null));
            //OR
            config.ConfigType<MethodModel>().ConstructBy(typeof(MethodModel).GetConstructors().OrderByDescending(t=>t.GetParameters().Length).First());

            var ceras = new CerasSerializer(config);

            var methodModel = new MethodModel(typeof(string).GetMethods().First(), new[] { typeof(string) });

            var data = ceras.Serialize(methodModel);

            ;
        }
    }

    public class MethodModel
    {
        public MethodModel(Type declaringType, string methodName, Type[] parameterTypes, Type[] genericArguments)
        {
            DeclaringType = declaringType;
            MethodName = methodName;
            ParameterTypes = parameterTypes;
            GenericArguments = genericArguments;
        }

        public MethodModel(MethodInfo method, Type[] genericArguments) :
            this(
                method.DeclaringType,
                method.Name,
                method.GetParameters().Select(t => t.ParameterType).ToArray(),
                genericArguments)
        {
        }

        public Type DeclaringType { get; }

        public string MethodName { get; }

        public Type[] ParameterTypes { get; }

        public Type[] GenericArguments { get; }
    }

}

@DaniilSokolyuk
Copy link
Author

Using TypeConstruction.ByUninitialized, MethodModel not deserialized again 😃 , i think because setters in not public :)

@rikimaru0345
Copy link
Owner

You'll have to enable serialization of compiler generated fields, and also explicitly target private fields:

config.Advanced.ReadonlyFieldHandling = ReadonlyFieldHandling.ForcedOverwrite;
config.Advanced.SkipCompilerGeneratedFields = false;
config.ConfigType<MethodModel>().ConstructByUninitialized();

and in your OnConfigNewType

t.TargetMembers = TargetMember.PrivateFields;

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