Skip to content

Make it easier to work with class hierarchies #447

Description

@Daniel-Svensson

Problem

Union types in polytype do not map cleanly to class hierarchies, and does not currently match cleanly KnownType attribute behaviour

  • If you only place DerivedTypeAttribute / KnownTypeAttribute on the base class , then derived types will not know of further derived types.

  • If you instead place DerivedTypeAttribute on all level of the class hierarchy you will get unions of unions of unions
    , where serialization of a nested type might use several layers of "type" information

    In my case I I would get ["Derived2", ["Derived2", {actual class data}

Apart from serialization I like the concepts of the library and hope to be able to use to replace som existing custom reflection code

Background:

I am working with getting serialization based on PolyType to work with existing code bases, many currently using DataContract based serialization.
Where KnownTypeAttribute on the base type is good enough to get good serialization support even on derived types.

Workaround:
Yes it is possible to work around by using for example Nerdbank.MessagePack and manually discover type heirarchies without polytype and then setup all class hierarchies manually, but that

Suggestion of how it could work

  • A derived class will inherit KnownTypeAttribute (for types that can be assigned to that type).
    • This is the expected behaviour if you use KnownTypeAttribute and I think it would make sense for DerivedTypeShape as well
  • Even if a derived type is a union type, the parent's UnionCase should refer to the object shape
    • At least for the case where (DerivedTypeShape's are inherited ) OR (KnownTypeAttribute is used)

Note: Following code uses PolyType attributes, the original case used [DataContract] and [KnownType]

[GenerateShape]
[DerivedTypeShape(typeof(Derived1))]
[DerivedTypeShape(typeof(Derived2))]
public partial class Base
{
    public int Id { get; set; }
}

[GenerateShape]
public partial class Derived1 : Base
{
    public string derived1 { get; set; }
}

[GenerateShape]
public partial class Derived2 : Derived1
{
    public string derived2 { get; set; }
}

[Fact]
public void TestDerived()
{
    var typeProvider = ReflectionTypeShapeProvider.Default;

    ITypeShape<Base> baseShape = typeProvider.GetTypeShapeOrThrow<Base>();
    ITypeShape<Derived1> derived1 = typeProvider.GetTypeShapeOrThrow<Derived1>();
    ITypeShape<Derived2> derived2 = typeProvider.GetTypeShapeOrThrow<Derived2>();

    // Both base and derived1 should know about Derived2 and are union types
    // The UnionCase type of base should point to the actual underlying "Derived1" type, not the union type of "Derived1"
    IUnionTypeShape<Base> baseUnion = Assert.IsType<IUnionTypeShape<Base>>(baseShape, exactMatch: false);
    IUnionTypeShape<Derived1> derivedUnion = Assert.IsType<IUnionTypeShape<Derived1>>(derived1, exactMatch: false);

    Assert.IsNotType<IUnionTypeShape<Derived2>>(derived2, exactMatch: false);
    Assert.IsType<IObjectTypeShape<Derived1>>(baseUnion.UnionCases[0].UnionCaseType.Kind, exactMatch: false);
    Assert.IsNotType<IUnionTypeShape<Derived1>>(baseUnion.UnionCases[0].UnionCaseType, exactMatch: false);

    // Derived1 should be a union type with one case, which is Derived2
    Assert.Single(derivedUnion.UnionCases);
    Assert.IsType<IObjectTypeShape<Derived1>>(derivedUnion.UnionCases[0].UnionCaseType, exactMatch: false);
    Assert.IsNotType<IUnionTypeShape<Derived1>>(derivedUnion.UnionCases[0].UnionCaseType, exactMatch: false);
    Assert.Same(derived2, derivedUnion.UnionCases[0].UnionCaseType);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions