/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="FixedSchema" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" /> /// is a <see cref="FixedSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> /// otherwise. /// </returns> /// <exception cref="UnsupportedTypeException"> /// Thrown when <see cref="T:System.Byte[]" /> cannot be converted to <paramref name="type" />. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema is FixedSchema fixedSchema) { var readFixed = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(int) }); try { return(BinaryDeserializerBuilderCaseResult.FromExpression( BuildConversion( Expression.Call( context.Reader, readFixed, Expression.Constant(fixedSchema.Size)), type))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {fixedSchema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryFixedDeserializerBuilderCase)} can only be applied to {nameof(FixedSchema)}s."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="NullSchema" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" /> /// is a <see cref="NullSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> /// otherwise. /// </returns> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema is NullSchema) { return(BinaryDeserializerBuilderCaseResult.FromExpression( Expression.Default(type))); } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryNullDeserializerBuilderCase)} can only be applied to {nameof(NullSchema)}s."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="TimestampLogicalType" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" /> /// has a <see cref="TimestampLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> /// otherwise. /// </returns> /// <exception cref="UnsupportedSchemaException"> /// Thrown when <paramref name="schema" /> is not a <see cref="LongSchema" /> or when /// <paramref name="schema" /> does not have a <see cref="MicrosecondTimestampLogicalType" /> /// or a <see cref="MillisecondTimestampLogicalType" />. /// </exception> /// <exception cref="UnsupportedTypeException"> /// Thrown when <see cref="DateTime" /> cannot be converted to <paramref name="type" />. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema.LogicalType is TimestampLogicalType) { if (schema is not LongSchema) { throw new UnsupportedSchemaException(schema, $"{nameof(TimestampLogicalType)} deserializers can only be built for {nameof(LongSchema)}s."); } var factor = schema.LogicalType switch { MicrosecondTimestampLogicalType => TimeSpan.TicksPerMillisecond / 1000, MillisecondTimestampLogicalType => TimeSpan.TicksPerMillisecond, _ => throw new UnsupportedSchemaException(schema, $"{schema.LogicalType} is not a supported {nameof(TimestampLogicalType)}."), }; var readInteger = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes); Expression expression = Expression.Call(context.Reader, readInteger); var addTicks = typeof(DateTime) .GetMethod(nameof(DateTime.AddTicks), new[] { typeof(long) }); try { // return Epoch.AddTicks(value * factor); return(BinaryDeserializerBuilderCaseResult.FromExpression( BuildConversion( Expression.Call( Expression.Constant(Epoch), addTicks, Expression.Multiply(expression, Expression.Constant(factor))), type))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryTimestampDeserializerBuilderCase)} can only be applied to schemas with a {nameof(TimestampLogicalType)}."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="StringSchema" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" /> /// is a <see cref="StringSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> /// otherwise. /// </returns> /// <exception cref="UnsupportedTypeException"> /// Thrown when <see cref="string" /> cannot be converted to <paramref name="type" />. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema is StringSchema stringSchema) { var readString = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadString), Type.EmptyTypes); try { return(BinaryDeserializerBuilderCaseResult.FromExpression( BuildConversion(Expression.Call(context.Reader, readString), type))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {stringSchema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryStringDeserializerBuilderCase)} can only be applied to {nameof(StringSchema)}s."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="DurationLogicalType" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" /> /// has a <see cref="DurationLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> /// otherwise. /// </returns> /// <exception cref="UnsupportedSchemaException"> /// Thrown when <paramref name="schema" /> is not a <see cref="FixedSchema" /> with size /// <c>12</c>. /// </exception> /// <exception cref="UnsupportedTypeException"> /// Thrown when <see cref="TimeSpan" /> cannot be converted to <paramref name="type" />. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema.LogicalType is DurationLogicalType) { if (!(schema is FixedSchema fixedSchema && fixedSchema.Size == DurationLogicalType.DurationSize)) { throw new UnsupportedSchemaException(schema); } var readFixed = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(int) }); Expression read = Expression.Call(context.Reader, readFixed, Expression.Constant(4)); if (!BitConverter.IsLittleEndian) { var buffer = Expression.Variable(read.Type); var reverse = typeof(Array) .GetMethod(nameof(Array.Reverse), new[] { typeof(Array) }); read = Expression.Block( new[] { buffer }, Expression.Assign(buffer, read), Expression.Call(null, reverse, Expression.Convert(buffer, typeof(Array))), buffer); } var toUInt32 = typeof(BitConverter) .GetMethod(nameof(BitConverter.ToUInt32), new[] { typeof(byte[]), typeof(int) }); read = Expression.ConvertChecked( Expression.Call(null, toUInt32, read, Expression.Constant(0)), typeof(long)); var exceptionConstructor = typeof(OverflowException) .GetConstructor(new[] { typeof(string) }); var timeSpanConstructor = typeof(TimeSpan) .GetConstructor(new[] { typeof(long) }); try { return(BinaryDeserializerBuilderCaseResult.FromExpression( BuildConversion( Expression.Block( Expression.IfThen( Expression.NotEqual(read, Expression.Constant(0L)), Expression.Throw( Expression.New( exceptionConstructor, Expression.Constant($"Durations containing months cannot be accurately deserialized to a {nameof(TimeSpan)}.")))), Expression.New( timeSpanConstructor, Expression.AddChecked( Expression.MultiplyChecked(read, Expression.Constant(TimeSpan.TicksPerDay)), Expression.MultiplyChecked(read, Expression.Constant(TimeSpan.TicksPerMillisecond))))), type))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryDurationDeserializerBuilderCase)} can only be applied schemas with a {nameof(DurationLogicalType)}."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="UnionSchema" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" /> /// is a <see cref="UnionSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> /// otherwise. /// </returns> /// <exception cref="UnsupportedSchemaException"> /// Thrown when <paramref name="schema" /> has no <see cref="UnionSchema.Schemas" />. /// </exception> /// <exception cref="UnsupportedTypeException"> /// Thrown when <paramref name="type" /> cannot be mapped to each <see cref="Schema" /> in /// <paramref name="schema" />. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema is UnionSchema unionSchema) { if (unionSchema.Schemas.Count < 1) { throw new UnsupportedSchemaException(schema, "A deserializer cannot be built for an empty union."); } var readInteger = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes); Expression expression = Expression.Call(context.Reader, readInteger); // create a mapping for each schema in the union: var cases = unionSchema.Schemas.Select((child, index) => { var selected = SelectType(type, child); var underlying = Nullable.GetUnderlyingType(selected); if (child is NullSchema && selected.IsValueType && underlying == null) { throw new UnsupportedTypeException(type, $"A deserializer for {unionSchema} cannot be built for {selected} because it contains {nameof(NullSchema)}."); } var @case = DeserializerBuilder.BuildExpression(selected, child, context); return(Expression.SwitchCase( BuildConversion(@case, type), Expression.Constant((long)index))); }); var position = typeof(BinaryReader) .GetProperty(nameof(BinaryReader.Index)); var exceptionConstructor = typeof(InvalidEncodingException) .GetConstructor(new[] { typeof(long), typeof(string), typeof(Exception) }); try { // generate a switch on the index: return(BinaryDeserializerBuilderCaseResult.FromExpression( Expression.Switch( expression, Expression.Throw( Expression.New( exceptionConstructor, Expression.Property(context.Reader, position), Expression.Constant($"Invalid union index; expected a value in [0-{unionSchema.Schemas.Count}). This may indicate invalid encoding earlier in the stream."), Expression.Constant(null, typeof(Exception))), type), cases.ToArray()))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {unionSchema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryUnionDeserializerBuilderCase)} can only be applied to {nameof(UnionSchema)}s."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="MapSchema" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="type" /> /// is a dictionary type and <paramref name="schema" /> is a <see cref="MapSchema" />; an /// unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> otherwise. /// </returns> /// <exception cref="UnsupportedTypeException"> /// Thrown when <paramref name="type" /> is not assignable from any supported dictionary /// <see cref="Type" /> and does not have a constructor that can be used to instantiate it. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema is MapSchema mapSchema) { var dictionaryTypes = GetDictionaryTypes(type); if (dictionaryTypes is not null || type == typeof(object)) { // support dynamic mapping: var keyType = dictionaryTypes?.Key ?? typeof(string); var valueType = dictionaryTypes?.Value ?? typeof(object); var instantiateDictionary = BuildIntermediateDictionary(type, keyType, valueType); var readInteger = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes); var readKey = DeserializerBuilder .BuildExpression(keyType, new StringSchema(), context); var readValue = DeserializerBuilder .BuildExpression(valueType, mapSchema.Value, context); var dictionary = Expression.Parameter(instantiateDictionary.Type); var index = Expression.Variable(typeof(long)); var size = Expression.Variable(typeof(long)); var outer = Expression.Label(); var inner = Expression.Label(); var add = dictionary.Type .GetMethod("Add", new[] { readKey.Type, readValue.Type }); // var dictionary = new ...; // // outer: while (true) // { // var size = reader.ReadInteger(); // // // if the block is empty, the map is complete: // if (size == 0L) break outer; // // // if the block size is negative, the number of bytes in the block // // follows, so read and discard: // if (size < 0L) // { // size *= -1L; // reader.ReadInteger(); // } // // var index = 0; // // inner: while (true) // { // // primitive for: // if (index++ == size) break inner; // // dictionary.Add(..., ...); // } // } // // return dictionary; Expression expression = Expression.Block( new[] { dictionary, index, size }, Expression.Assign(dictionary, instantiateDictionary), Expression.Loop( Expression.Block( Expression.Assign(size, Expression.Call(context.Reader, readInteger)), Expression.IfThen( Expression.Equal(size, Expression.Constant(0L)), Expression.Break(outer)), Expression.IfThen( Expression.LessThan(size, Expression.Constant(0L)), Expression.Block( Expression.MultiplyAssign(size, Expression.Constant(-1L)), Expression.Call(context.Reader, readInteger))), Expression.Assign(index, Expression.Constant(0L)), Expression.Loop( Expression.Block( Expression.IfThen( Expression.Equal(Expression.PostIncrementAssign(index), size), Expression.Break(inner)), Expression.Call(dictionary, add, readKey, readValue)), inner)), outer), dictionary); if (!type.IsAssignableFrom(expression.Type) && GetDictionaryConstructor(type) is ConstructorInfo constructor) { expression = Expression.New(constructor, expression); } try { return(BinaryDeserializerBuilderCaseResult.FromExpression( BuildConversion(expression, type))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {mapSchema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedTypeException(type, $"{nameof(BinaryMapDeserializerBuilderCase)} can only be applied to dictionary types."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for an <see cref="EnumSchema" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" /> /// is an <see cref="EnumSchema" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> /// otherwise. /// </returns> /// <exception cref="UnsupportedTypeException"> /// Thrown when <paramref name="type" /> is an enum type without a matching member for each /// symbol in <paramref name="schema" /> or when <see cref="string" /> cannot be converted /// to <paramref name="type" />. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema is EnumSchema enumSchema) { var readInteger = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes); Expression expression = Expression.ConvertChecked( Expression.Call(context.Reader, readInteger), typeof(int)); var underlying = Nullable.GetUnderlyingType(type) ?? type; // enum fields will always be public static, so no need to expose binding flags: var fields = underlying.GetFields(BindingFlags.Public | BindingFlags.Static); var cases = underlying.IsEnum ? enumSchema.Symbols .Select((symbol, index) => { var match = fields.SingleOrDefault(field => IsMatch(symbol, field)); if (enumSchema.Default != null) { match ??= fields.SingleOrDefault(field => IsMatch(enumSchema.Default, field)); } if (match == null) { throw new UnsupportedTypeException(type, $"{type} has no value that matches {symbol} and no default value is defined."); } return(Expression.SwitchCase( BuildConversion(Expression.Constant(Enum.Parse(underlying, match.Name)), type), Expression.Constant(index))); }) : enumSchema.Symbols .Select((symbol, index) => { return(Expression.SwitchCase( BuildConversion(Expression.Constant(symbol), type), Expression.Constant(index))); }); var position = typeof(BinaryReader) .GetProperty(nameof(BinaryReader.Index)) .GetGetMethod(); var exceptionConstructor = typeof(InvalidEncodingException) .GetConstructor(new[] { typeof(long), typeof(string), typeof(Exception) }); try { // generate a switch on the index: return(BinaryDeserializerBuilderCaseResult.FromExpression( Expression.Switch( expression, Expression.Throw( Expression.New( exceptionConstructor, Expression.Property(context.Reader, position), Expression.Constant($"Invalid enum index; expected a value in [0-{enumSchema.Symbols.Count}). This may indicate invalid encoding earlier in the stream."), Expression.Constant(null, typeof(Exception))), type), cases.ToArray()))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {enumSchema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryEnumDeserializerBuilderCase)} can only be applied to {nameof(EnumSchema)}s."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for an <see cref="ArraySchema" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="type" /> /// is an enumerable type and <paramref name="schema" /> is an <see cref="ArraySchema" />; /// an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> otherwise. /// </returns> /// <exception cref="UnsupportedTypeException"> /// Thrown when <paramref name="type" /> is not assignable from any supported array or /// collection <see cref="Type" /> and does not have a constructor that can be used to /// instantiate it. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema is ArraySchema arraySchema) { var enumerableType = GetEnumerableType(type); if (enumerableType is not null || type == typeof(object)) { // support dynamic mapping: var itemType = enumerableType ?? typeof(object); var instantiateCollection = BuildIntermediateCollection(type, itemType); var readInteger = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadInteger), Type.EmptyTypes); var readItem = DeserializerBuilder .BuildExpression(itemType, arraySchema.Item, context); var collection = Expression.Parameter(instantiateCollection.Type); var index = Expression.Variable(typeof(long)); var size = Expression.Variable(typeof(long)); var outer = Expression.Label(); var inner = Expression.Label(); var add = collection.Type .GetMethod("Add", new[] { readItem.Type }); // var collection = new List<T>(); // // outer: while (true) // { // var size = reader.ReadInteger(); // // // if the block is empty, the array is complete: // if (size == 0L) break outer; // // // if the block size is negative, the number of bytes in the block // // follows, so read and discard: // if (size < 0L) // { // size *= -1L; // reader.ReadInteger(); // } // // var index = 0; // // inner: while (true) // { // // primitive for: // if (index++ == size) break inner; // // collection.Add(...); // } // } // // return collection; Expression expression = Expression.Block( new[] { collection, index, size }, Expression.Assign(collection, instantiateCollection), Expression.Loop( Expression.Block( Expression.Assign(size, Expression.Call(context.Reader, readInteger)), Expression.IfThen( Expression.Equal(size, Expression.Constant(0L)), Expression.Break(outer)), Expression.IfThen( Expression.LessThan(size, Expression.Constant(0L)), Expression.Block( Expression.MultiplyAssign(size, Expression.Constant(-1L)), Expression.Call(context.Reader, readInteger))), Expression.Assign(index, Expression.Constant(0L)), Expression.Loop( Expression.Block( Expression.IfThen( Expression.Equal(Expression.PostIncrementAssign(index), size), Expression.Break(inner)), Expression.Call(collection, add, readItem)), inner)), outer), collection); if (!type.IsAssignableFrom(expression.Type) && GetCollectionConstructor(type) is ConstructorInfo constructor) { expression = Expression.New(constructor, expression); } try { return(BinaryDeserializerBuilderCaseResult.FromExpression( BuildConversion(expression, type))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {arraySchema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedTypeException(type, $"{nameof(BinaryArrayDeserializerBuilderCase)} can only be applied to enumerable types."))); } }
/// <summary> /// Builds a <see cref="BinaryDeserializer{T}" /> for a <see cref="DecimalLogicalType" />. /// </summary> /// <returns> /// A successful <see cref="BinaryDeserializerBuilderCaseResult" /> if <paramref name="schema" /> /// has a <see cref="DecimalLogicalType" />; an unsuccessful <see cref="BinaryDeserializerBuilderCaseResult" /> /// otherwise. /// </returns> /// <exception cref="UnsupportedSchemaException"> /// Thrown when <paramref name="schema" /> is not a <see cref="BytesSchema" /> or a /// <see cref="FixedSchema "/>. /// </exception> /// <exception cref="UnsupportedTypeException"> /// Thrown when <see cref="decimal" /> cannot be converted to <paramref name="type" />. /// </exception> /// <inheritdoc /> public virtual BinaryDeserializerBuilderCaseResult BuildExpression(Type type, Schema schema, BinaryDeserializerBuilderContext context) { if (schema.LogicalType is DecimalLogicalType decimalLogicalType) { var precision = decimalLogicalType.Precision; var scale = decimalLogicalType.Scale; Expression expression; // figure out the size: if (schema is BytesSchema) { var readBytes = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadBytes), Type.EmptyTypes); expression = Expression.Call(context.Reader, readBytes); } else if (schema is FixedSchema fixedSchema) { var readFixed = typeof(BinaryReader) .GetMethod(nameof(BinaryReader.ReadFixed), new[] { typeof(long) }); expression = Expression.Call(context.Reader, readFixed, Expression.Constant((long)fixedSchema.Size)); } else { throw new UnsupportedSchemaException(schema); } // declare variables for in-place transformation: var bytes = Expression.Variable(typeof(byte[])); var remainder = Expression.Variable(typeof(BigInteger)); var divide = typeof(BigInteger) .GetMethod(nameof(BigInteger.DivRem), new[] { typeof(BigInteger), typeof(BigInteger), typeof(BigInteger).MakeByRefType() }); var integerConstructor = typeof(BigInteger) .GetConstructor(new[] { typeof(byte[]) }); var reverse = typeof(Array) .GetMethod(nameof(Array.Reverse), new[] { typeof(Array) }); // var bytes = ...; // // // BigInteger is little-endian, so reverse: // Array.Reverse(bytes); // // var whole = BigInteger.DivRem(new BigInteger(bytes), BigInteger.Pow(10, scale), out var remainder); // var fraction = (decimal)remainder / (decimal)Math.Pow(10, scale); // // return whole + fraction; expression = Expression.Block( new[] { bytes, remainder }, Expression.Assign(bytes, expression), Expression.Call(null, reverse, bytes), Expression.Add( Expression.ConvertChecked( Expression.Call( null, divide, Expression.New(integerConstructor, bytes), Expression.Constant(BigInteger.Pow(10, scale)), remainder), typeof(decimal)), Expression.Divide( Expression.ConvertChecked(remainder, typeof(decimal)), Expression.Constant((decimal)Math.Pow(10, scale))))); try { return(BinaryDeserializerBuilderCaseResult.FromExpression( BuildConversion(expression, type))); } catch (InvalidOperationException exception) { throw new UnsupportedTypeException(type, $"Failed to map {schema} to {type}.", exception); } } else { return(BinaryDeserializerBuilderCaseResult.FromException(new UnsupportedSchemaException(schema, $"{nameof(BinaryDecimalDeserializerBuilderCase)} can only be applied schemas with a {nameof(DecimalLogicalType)}."))); } }