jefffhaynes / BinarySerializer

A declarative serialization framework for controlling formatting of data at the byte and bit level using field bindings, converters, and code.
MIT License
290 stars 62 forks source link

ISubtypeFactory - how to use custom constructor instead of default constructor? #226

Open abrasat opened 1 year ago

abrasat commented 1 year ago

How can the BinarySerializer be configured, to use a SubtypeFactory instance which needs a constructor with parameters? I have the following scenario in my application. In the first approach, only the deserialization would be required.

    public enum MyDataType: UInt32
    {
        Datatype_Invalid = 0,
        Datatype_Bool = 1,
        Datatype_Byte = 2,
        Datatype_Word = 3,
        Datatype_DWord = 4,
        Datatype_Float = 5,
        Datatype_Int16 = 6,
        Datatype_Int32 = 7,
        Datatype_String = 8,
        Datatype_Double = 9,
        Datatype_Spline = 10
    }

    public interface IItemSubtype
    {
    }

    public class ItemTypeByte : IItemSubtype
    {
        public byte Value { get; set; }
    }

    public class ItemTypeInt16 : IItemSubtype
    {
        public short Value { get; set; }
    }

    public class ItemTypeFloat : IItemSubtype
    {
        public float Value { get; set; }
    }

    public class ItemTypeDouble : IItemSubtype
    {
        public double Value { get; set; }
    }

    public class DefaultItemType : IItemSubtype
    {
        public byte[] Data { get; set; }
    }

    public class MyItemSubtypeFactory : ISubtypeFactory
    {
        private IDictionary<string, MyDataType> myDict;

       // how to inject this constructor ???
        public MyItemSubtypeFactory(IDictionary<string, MyDataType> dict)
        {
            myDict = dict;
        }

        public MyItemSubtypeFactory()
        {
        }

        // will not be used, only deserialization is necessary at this point (maybe later)
        public bool TryGetKey(Type valueType, out object key)
        {
            key = null;
            return false;
        }

        public bool TryGetType(object key, out Type type)
        {
            MyDataType keyVal = 0;
            bool keySuccess = myDict.TryGetValue((string)key, out keyVal);

            switch (keyVal)
            {
                case MyDataType.Datatype_Byte:
                    type = typeof(ItemTypeByte);
                    break;
                case MyDataType.Datatype_Int16:
                    type = typeof(ItemTypeInt16);
                    break;
                case MyDataType.Datatype_Float:
                    type = typeof(ItemTypeFloat);
                    break;
                case MyDataType.Datatype_Double:
                    type = typeof(ItemTypeDouble);
                    break;
                case MyDataType.Datatype_Spline:
                    // complex data-type, to be implemented later
                    type = null;
                    return false;
                default:
                    type = null;
                    return false;
            }
            return true;
        }
    }

    public class MyClassWithItemSubtypeFactory
    {
        [FieldOrder(0)]
        [FieldLength(4)]
        public string Key { get; set; }

        [FieldOrder(1)]
        [ItemSubtypeFactory(nameof(Key), typeof(MyItemSubtypeFactory))]
        [ItemSubtypeDefault(typeof(DefaultItemType))]
        public List<IItemSubtype> Items { get; set; }
    }

And the usage:

   // how to inject it into the SubtypeFactory constructor ?
    var dataTypeDictionary = new Dictionary<string, MyDataType>
    {
        { "abc", MyDataType.Datatype_Byte  },
        { "def", MyDataType.Datatype_Int16 },
        { "ghi", MyDataType.Datatype_Float }
    };
    var data = new byte[] { 97, 98, 99, 0, 17, 100, 101, 102, 0, 1, 12, 103, 104, 105, 0, 0x40, 0xb6, 0x66, 0x66 };
    var deserData = binSer.Deserialize<MyClassWithItemSubtypeFactory>(data);

The actual BinarySerializer implementation for TypeNode has only the option to choose the default constructor

var subtypeFactoryAttribute = attributes.OfType<SubtypeFactoryAttribute>().SingleOrDefault();
if (subtypeFactoryAttribute != null)
{
    SubtypeFactoryBinding = GetBinding(subtypeFactoryAttribute);
    SubtypeFactory =
         (ISubtypeFactory) subtypeFactoryAttribute.FactoryType.GetConstructor(Type.EmptyTypes)?.Invoke(null);
}

Maybe there is a possibility to call GetConstructors() to iterate the constructors and choose one of them according to some configuration. Or is it possible to inject somehow into the BinarySerializer an instance of the SubtypeFactory custom implementation?