Skip to content

Polymorphism

月守 Jason Kuo edited this page Jul 4, 2020 · 2 revisions

Overview

The concept of polymorphism doesn't really exist within this library. There are no attributes that directly describes this behavior.

However, similar effects of Polymorphism can be achieved via [FormatAs]. This attribute allows a source type to be serialized/deserialized with another type's schematic.

For example:

[FormatAs(typeof(AnotherType))]
public SourceType Item { get; }

SourceType will be serialized using AnotherType's schematic, even if SourceType already has a layout of its own. (SourceType doesn't need a layout designed for it when annotated with [FormatAs])

Although there are limitations on what type can be chosen as a parameter for [FormatAs] attribute:

  • The type must be either annotated with any of the Schema Decision Attributes (For example: [BinaryLayout] [BinaryParser] and [InlineData])
  • The type must either be
    • Derived from the annotated type (in this example SourceType) or
    • A constructor that accepts SourceType and have a method that starts with ConvertTo with the returning type being the annotated type (in this example SourceType)

For this article, we will be focusing on the aspect where the target type is derived from the annotated type. As it is the closest thing we have for polymorphism. For more info about [FormatAs] you can always find it out with the documentation of [FormatAs] here.

Providing Conditions

[FormatAs] is also considered a Conditional Attribute which means that the attribute effects can be applied only when the conditionals given are met.

[FormatAs] has constructors with overloads that allow you to specify the conditions. They are also aligned and designed consistently with other Conditional Attributes

Those are :

  • [FormatAs(Type, string, object)]:
    • Type: The type that provides schematic for the annotated type.
    • string: The name of the method, property or field that provides the value to compare with
    • object: The value that the provided value should be equal to.
  • [FormatAs(Type, string, Operator, object)]:
    • Type: The type that provides schematic for the annotated type.
    • string: The name of the method, property or field that provides the value to compare with
    • Operator: The operation to perform (Equal, LargerThan, IsTypeOf)
    • object: The value that the provided value should be compared with.

The conditions are evaluated by the order from top to bottom, and those without conditions can be used as the default case.

Example

For the following example:

[BinaryFormat]
public partial struct MyData {
    private readonly int _typeId;
    
    [FormatAs(typeof(DerivedA), nameof(_typeId), Operand.LargerThan, 3)]
    [FormatAs(typeof(DerivedB), nameof(_typeId), Operand.LargerThan, 5)]
    [FormatAs(typeof(DerivedC), nameof(_typeId), Operand.LargetThan, 7)]
    [FormatAs(typeof(DerivedD))]
    public BaseType Item { get; }
}

If the value _typeId is 9, then Item will be serialized as DerivedA, because the condition (> 3) is evaluated as true and has the highest priority. This also shows that in this example DerivedB and DerivedC are impossible to be serialized.

If the value _typeId in this case is 2, then Item will be serialized as DerivedD.

If no matching condition is found, the data is considered faulted. If you wish this member to be serialized to the default value when no matching condition is found, you should use [FormatAs(null)] or [FormatAs(default)] for the default case.

Clone this wiki locally