Closed abrasat closed 1 year ago
I modified the class definitions to use the "SerializeWhen" attribute. These are the actual classes:
public enum ValueBlockType : UInt32
{
PlainValue = 1,
ValueWithDescriptor = 2
}
public enum ValueDataType : UInt32
{
Datatype_Invalid = 0,
Datatype_Float = 1,
Datatype_Double = 2,
Datatype_Int16 = 3,
Datatype_Int32 = 4
}
public class ValueDescriptor
{
[FieldOrder(0)]
public byte ProductId { get; set; }
[FieldOrder(1)]
public byte CategoryId { get; set; }
}
public class ValueDataInfo
{
[FieldOrder(0)]
public ValueBlockType BlockType { get; set; } = ValueBlockType.PlainValue;
[FieldOrder(1)]
public UInt32 ParameterId { get; set; }
[FieldOrder(2)]
[FieldLength(8)]
public string Name { get; set; }
[FieldOrder(3)]
public UInt32 NrValues { get; set; }
[FieldOrder(4)]
public ValueDataType DataTypeId { get; set; } = ValueDataType.Datatype_Invalid;
[FieldOrder(5)]
[SerializeWhen(nameof(BlockType), ValueBlockType.PlainValue)]
[Subtype("DataTypeId", ValueDataType.Datatype_Double, typeof(DoublePlainValuesDataBody))]
[Subtype("DataTypeId", ValueDataType.Datatype_Float, typeof(FloatPlainValuesDataBody))]
[Subtype("DataTypeId", ValueDataType.Datatype_Int16, typeof(Int16PlainValuesDataBody))]
[Subtype("DataTypeId", ValueDataType.Datatype_Int32, typeof(Int32PlainValuesDataBody))]
[SubtypeDefault(typeof(EmptyPlainValueDataBlock))]
public PlainValueDataBlock Block { get; set; }
[FieldOrder(6)]
[SerializeWhen(nameof(BlockType), ValueBlockType.ValueWithDescriptor)]
[Subtype("DataTypeId", ValueDataType.Datatype_Double, typeof(DoubleValuesWithDescriptorDataBody))]
[Subtype("DataTypeId", ValueDataType.Datatype_Float, typeof(FloatValuesWithDescriptorDataBody))]
[Subtype("DataTypeId", ValueDataType.Datatype_Int16, typeof(Int16ValuesWithDescriptorDataBody))]
[Subtype("DataTypeId", ValueDataType.Datatype_Int32, typeof(Int32ValuesWithDescriptorDataBody))]
[SubtypeDefault(typeof(EmptyDescriptorDataBlock))]
public ValueWithDescriptorDataBlock DescriptorBlock { get; set; }
}
public abstract class PlainValueDataBlock { }
public class EmptyPlainValueDataBlock : PlainValueDataBlock { }
public abstract class ValueWithDescriptorDataBlock { }
public class EmptyDescriptorDataBlock : ValueWithDescriptorDataBlock { }
public class DoublePlainValuesDataBody : PlainValueDataBlock
{
public List<double> Data { get; set; }
}
public class FloatPlainValuesDataBody : PlainValueDataBlock
{
public List<float> Data { get; set; }
}
public class Int16PlainValuesDataBody : PlainValueDataBlock
{
public List<Int16> Data { get; set; }
}
public class Int32PlainValuesDataBody : PlainValueDataBlock
{
public List<Int32> Data { get; set; }
}
public class ValueDescriptorDoubleValue
{
[FieldOrder(0)]
public ValueDescriptor ValueDescriptor { get; set; }
[FieldOrder(1)]
public double Value { get; set; }
}
public class ValueDescriptorFloatValue
{
[FieldOrder(0)]
public ValueDescriptor ValueDescriptor { get; set; }
[FieldOrder(1)]
public float Value { get; set; }
}
public class ValueDescriptorInt16Value
{
[FieldOrder(0)]
public ValueDescriptor ValueDescriptor { get; set; }
[FieldOrder(1)]
public Int16 Value { get; set; }
}
public class ValueDescriptorInt32Value
{
[FieldOrder(0)]
public ValueDescriptor ValueDescriptor { get; set; }
[FieldOrder(1)]
public Int32 Value { get; set; }
}
public class FloatValuesWithDescriptorDataBody : ValueWithDescriptorDataBlock
{
public List<ValueDescriptorFloatValue> Data { get; set; }
}
public class DoubleValuesWithDescriptorDataBody : ValueWithDescriptorDataBlock
{
public List<ValueDescriptorDoubleValue> Data { get; set; }
}
public class Int16ValuesWithDescriptorDataBody : ValueWithDescriptorDataBlock
{
public List<ValueDescriptorInt16Value> Data { get; set; }
}
public class Int32ValuesWithDescriptorDataBody : ValueWithDescriptorDataBlock
{
public List<ValueDescriptorInt32Value> Data { get; set; }
}
Then I tried to serialize some data like this:
var newData = new ValueDataInfo()
{
BlockType = ValueBlockType.PlainValue,
ParameterId = 1,
DataTypeId = ValueDataType.Datatype_Int32,
Block = new Int32PlainValuesDataBody { Data = new List<int> { 1, 2, 3 } }
};
try
{
var binSer = new BinarySerializer();
using (var memStream = new MemoryStream())
{
binSer.Serialize(memStream, newData);
var binSerBytes = memStream.ToArray();
}
}
catch (Exception exc)
{
}
Unfortunately this does not work as expected and throws an exception: Error serializing member 'DataTypeId'. See inner exception for detail. Inner exception: Binding targets must not be null.
The "SerializeWhen" attribute seems to be ignored and the serializer tries to Serialize all options, not only the one which is selected with the condition from "SerializeWhen". With this modification (the Subtypes are commented) the serialize works fine:
...
[FieldOrder(6)]
[SerializeWhen(nameof(BlockType), ValueBlockType.ValueWithDescriptor)]
//[Subtype("DataTypeId", ValueDataType.Datatype_Double, typeof(DoubleValuesWithDescriptorDataBody))]
//[Subtype("DataTypeId", ValueDataType.Datatype_Float, typeof(FloatValuesWithDescriptorDataBody))]
//[Subtype("DataTypeId", ValueDataType.Datatype_Int16, typeof(Int16ValuesWithDescriptorDataBody))]
//[Subtype("DataTypeId", ValueDataType.Datatype_Int32, typeof(Int32ValuesWithDescriptorDataBody))]
[SubtypeDefault(typeof(EmptyDescriptorDataBlock))]
public ValueWithDescriptorDataBlock DescriptorBlock { get; set; }
...
What is wrong with my approach?
I believe your issue might be this line
[SerializeWhen(nameof(BlockType), nameof(ValueBlockType.PlainValue))]
Should be
[SerializeWhen(nameof(BlockType), ValueBlockType.PlainValue)]
Thanks, but even after changing the line the same exception still occurs...
I dont know how to fix it, but the exception occurs here in ValueNode.cs:
private object SubtypeBindingCallback(TypeNode typeNode)
{
var valueType = GetValueTypeOverride();
if (valueType == null)
{
throw new InvalidOperationException("Binding targets must not be null.");
}
......
It should be checked if the typeNode has any SerializeWhenBindings. If yes, the SerializeWhen condition should be evaluated and if false, no further valueType checks should be performed.
I think this code modification in SubtypeBindingCallback() should fix the problem, but cannot evaluate if any side-effects may occur:
private object SubtypeBindingCallback(TypeNode typeNode)
{
// new code
if (!ShouldSerialize())
{
return UnsetValue;
}
// end new code
var valueType = GetValueTypeOverride();
if (valueType == null)
{
throw new InvalidOperationException("Binding targets must not be null.");
}
var objectTypeNode = (ObjectTypeNode) typeNode;
...
@jefffhaynes could you please check if you can add the fix to the next release?
Unfortunately that fix causes a tiny regression. Trying to noodle through the correct behavior...
Never mind, I think the regression is just a (somewhat mind-bending) invalid use case. Looks good.
I have the following class definitions for my application:
How can the class definitions be modified, to be able to use the "Subtype" attribute, in view to select one of the Double, Float, Int16, Int32 derived classes, based on the DataTypeId value from the ValueDataHeader main class? Is such a binding possible? Or is there any other approach possible, to achieve the desired functionality?