AqlaSolutions / AqlaSerializer

Binary serializer with full .NET support!
http://www.aqla.net
Other
17 stars 3 forks source link

How to support inheritance of a class and an interface #29

Closed inethui closed 2 years ago

inethui commented 2 years ago

I have something like this: public class ClassA : ClassB, Interface1

How to model this? Does Aqla support this case?

AqlaSolutions commented 2 years ago

What have you tried already?

inethui commented 2 years ago

This is what I've tried, I got error "A type can only participate in one inheritance hierarchy"

       [Fact]
        public void InheritClassAndInterface()
        {
            MetaType metaTypeA = RuntimeTypeModel.Default.Add(typeof(ClassA), true);
            MetaType metaTypeB = RuntimeTypeModel.Default.Add(typeof(ClassB), true);
            MetaType metaType1 = RuntimeTypeModel.Default.Add(typeof(Interface1), true);

            metaTypeB.AddSubType(1, typeof(ClassA));
            metaType1.AddSubType(1, typeof(ClassA));

            RuntimeTypeModel.Default.CompileInPlace();
        }

        private class ClassA : ClassB, Interface1
        {
        }

        private class ClassB
        {
        }

        public interface Interface1
        {
        }
AqlaSolutions commented 2 years ago

Yeah, it means that each type can have only one base type specified. You still can inherit both but you have to decide to use fields either of type ClassB or of type Interface1. There is also another option: to enable WriteAsDynamicType on interface-typed fields in the [SerializableMember] attribute (while having only ClassA : ClassB inheritance specified in the TypeModel).

AqlaSolutions commented 2 years ago
using AqlaSerializer;
using AqlaSerializer.Meta;

namespace ConsoleApp5
{
    public class MyTest
    {

        public void Execute()
        {
            MetaType metaTypeA = RuntimeTypeModel.Default.Add(typeof(ClassA), true);
            MetaType metaTypeB = RuntimeTypeModel.Default.Add(typeof(ClassB), true);

            metaTypeB.AddSubType(1, typeof(ClassA));

            RuntimeTypeModel.Default.CompileInPlace();

            var c = new TestClass
            {
                ClassA = new ClassA { Value1 = 1, Value2 = 2, Value3 = 3 },
                ClassB = new ClassB { Value2 = 22 },
                Interface1 = new ClassA { Value1 = 1, Value2 = 2, Value3 = 3 },
            };

            var c2 = RuntimeTypeModel.Default.DeepClone(c);

        }

        [SerializableType]
        class TestClass

        {
            [SerializableMember(1, DynamicType = true)]
            public Interface1 Interface1 { get; set; }
            [SerializableMember(2)]
            public ClassB ClassB { get; set; }
            [SerializableMember(3)]
            public ClassA ClassA { get; set; }
        }

        [SerializableType]
        private class ClassA : ClassB, Interface1
        {
            [SerializableMember(1)]
            public int Value3 { get; set; }
            [SerializableMember(2)]
            public int Value1 { get; set; }
        }

        [SerializableType]
        [SerializeDerivedType(1, typeof(ClassA))]
        private class ClassB
        {
            [SerializableMember(2)]
            public int Value2 { get; set; }
        }

        public interface Interface1
        {
            public int Value1 { get; set; }
        }

    }
}
inethui commented 2 years ago

Thanks a lot. This is a great solution. Can "DynamicType = true" be defined through method calls?

AqlaSolutions commented 2 years ago
RuntimeTypeModel.Default.Add(typeof(TestClass), true)[nameof(TestClass.Interface1)].SetSettings(x => x.V.WriteAsDynamicType = true, 0);

For now it can only be set for a member.

inethui commented 2 years ago

This works great. What would be the downsides of this approach? Does it make sense to always mark a field as "DynamicType = true" if its type is an interface? We have more than 10k classes and interfaces. Manually configure each of them is not practical, so we always prefer to set things up programmatically.

AqlaSolutions commented 2 years ago

Sorry, the previous post that I deleted contained wrong information.

If you need to serialize interfaces or System.Object then yes, usually you have to mark them as dynamic type.

This feature writes full runtime type name into output stream. The name is written only once per type so there is no big size overhead even if you have multiple dynamic fields of a same type.

This is also not very safe if you receive your stream from an untrusted source. An attacker may create any of types registered in the model if you use precompiled TypeModel or any available type with supported contract attribute or any available list type if it's executed on RuntimeTypeModel.

Also the LateReference feature doesn't work with DynamicType.

AqlaSolutions commented 2 years ago

To prevent creating non-registered types you can set RuntimeTypeModel.AutoAddMissingTypes to false.

More specifically this is called when a new type is encountered in an input stream: изображение

AqlaSolutions commented 2 years ago

See also this https://github.com/AqlaSolutions/AqlaSerializer/wiki/Security#type-whitelisting

inethui commented 2 years ago

Thank you so much! It has been very helpful.

AqlaSolutions commented 2 years ago

With the latest release such interface is automatically marked as a dynamic type