| @@ -1,26 +1,49 @@ | |||||
| /*using System; | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | |||||
| using System.IO; | using System.IO; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Reflection; | |||||
| using System.Reflection.Emit; | |||||
| using System.Text; | using System.Text; | ||||
| namespace Discord.ETF | namespace Discord.ETF | ||||
| { | { | ||||
| public class ETFReader | |||||
| public class ETFReader : IDisposable | |||||
| { | { | ||||
| private static readonly ConcurrentDictionary<Type, Delegate> _deserializers; | |||||
| private static readonly Dictionary<Type, MethodInfo> _readMethods = GetPrimitiveReadMethods(); | |||||
| private readonly Stream _stream; | private readonly Stream _stream; | ||||
| private readonly byte[] _buffer; | private readonly byte[] _buffer; | ||||
| private readonly bool _leaveOpen; | private readonly bool _leaveOpen; | ||||
| private readonly Encoding _encoding; | private readonly Encoding _encoding; | ||||
| private readonly ConcurrentDictionary<Type, Delegate> _serializers, _indirectSerializers; | |||||
| public ETFReader(Stream stream, bool leaveOpen = false) | |||||
| { | |||||
| if (stream == null) throw new ArgumentNullException(nameof(stream)); | |||||
| private void ReadNil(bool allow) | |||||
| _stream = stream; | |||||
| _leaveOpen = leaveOpen; | |||||
| _buffer = new byte[11]; | |||||
| _encoding = Encoding.UTF8; | |||||
| } | |||||
| private bool ReadNil(bool ignoreLength = false) | |||||
| { | { | ||||
| if (!allow) throw new InvalidDataException(); | |||||
| if (!ignoreLength) | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| byte length = _buffer[0]; | |||||
| if (length != 3) return false; | |||||
| } | |||||
| _stream.Read(_buffer, 0, 3); | _stream.Read(_buffer, 0, 3); | ||||
| if (_buffer[0] != 'n' || _buffer[1] != 'i' || _buffer[2] != 'l') | |||||
| throw new InvalidDataException(); | |||||
| if (_buffer[0] == 'n' && _buffer[1] == 'i' && _buffer[2] == 'l') | |||||
| return true; | |||||
| return false; | |||||
| } | } | ||||
| private void ReadTrue() | private void ReadTrue() | ||||
| { | { | ||||
| @@ -35,138 +58,407 @@ namespace Discord.ETF | |||||
| throw new InvalidDataException(); | throw new InvalidDataException(); | ||||
| } | } | ||||
| public bool? ReadBool(bool allowNil) | |||||
| public bool? ReadNullableBool() | |||||
| { | { | ||||
| _stream.Read(_buffer, 0, 2); | |||||
| switch ((ETFType)_buffer[0]) | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT) | |||||
| { | { | ||||
| case ETFType.SMALL_ATOM_EXT: | |||||
| switch (_buffer[1]) //Length | |||||
| { | |||||
| case 3: | |||||
| ReadNil(allowNil); | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| switch (_buffer[0]) //Length | |||||
| { | |||||
| case 3: | |||||
| if (ReadNil()) | |||||
| return null; | return null; | ||||
| case 4: | |||||
| ReadTrue(); | |||||
| return true; | |||||
| case 5: | |||||
| ReadFalse(); | |||||
| return false; | |||||
| } | |||||
| break; | |||||
| break; | |||||
| case 4: | |||||
| ReadTrue(); | |||||
| return true; | |||||
| case 5: | |||||
| ReadFalse(); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| throw new InvalidDataException(); | |||||
| } | |||||
| public bool ReadBool() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT) | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| switch (_buffer[0]) //Length | |||||
| { | |||||
| case 4: | |||||
| ReadTrue(); | |||||
| return true; | |||||
| case 5: | |||||
| ReadFalse(); | |||||
| return false; | |||||
| } | |||||
| } | } | ||||
| throw new InvalidDataException(); | throw new InvalidDataException(); | ||||
| } | } | ||||
| public long? ReadInteger(bool allowNil) | |||||
| public int ReadSByte() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return (sbyte)ReadLongInternal(type); | |||||
| } | |||||
| public int? ReadNullableSByte() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return (sbyte)ReadLongInternal(type); | |||||
| } | |||||
| public uint ReadByte() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return (byte)ReadLongInternal(type); | |||||
| } | |||||
| public uint? ReadNullableByte() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return (byte)ReadLongInternal(type); | |||||
| } | |||||
| public int ReadShort() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return (short)ReadLongInternal(type); | |||||
| } | |||||
| public int? ReadNullableShort() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return (short)ReadLongInternal(type); | |||||
| } | |||||
| public uint ReadUShort() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return (ushort)ReadLongInternal(type); | |||||
| } | |||||
| public uint? ReadNullableUShort() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return (ushort)ReadLongInternal(type); | |||||
| } | |||||
| public int ReadInt() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return (int)ReadLongInternal(type); | |||||
| } | |||||
| public int? ReadNullableInt() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return (int)ReadLongInternal(type); | |||||
| } | |||||
| public uint ReadUInt() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return (uint)ReadLongInternal(type); | |||||
| } | |||||
| public uint? ReadNullableUInt() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return (uint)ReadLongInternal(type); | |||||
| } | |||||
| public long ReadLong() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return ReadLongInternal(type); | |||||
| } | |||||
| public long? ReadNullableLong() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return ReadLongInternal(type); | |||||
| } | |||||
| public ulong ReadULong() | |||||
| { | { | ||||
| _stream.Read(_buffer, 0, 1); | _stream.Read(_buffer, 0, 1); | ||||
| ETFType type = (ETFType)reader.ReadByte(); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return (ulong)ReadLongInternal(type); | |||||
| } | |||||
| public ulong? ReadNullableULong() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return (ulong)ReadLongInternal(type); | |||||
| } | |||||
| public long ReadLongInternal(ETFType type) | |||||
| { | |||||
| switch (type) | switch (type) | ||||
| { | { | ||||
| case ETFType.SMALL_ATOM_EXT: | |||||
| ReadNil(allowNil); | |||||
| return null; | |||||
| case ETFType.SMALL_INTEGER_EXT: | case ETFType.SMALL_INTEGER_EXT: | ||||
| _stream.Read(_buffer, 0, 1); | _stream.Read(_buffer, 0, 1); | ||||
| return (_buffer[0] << 24) | (_buffer[1] << 16) | | |||||
| (_buffer[2] << 8) | (_buffer[3]); | |||||
| return _buffer[0]; | |||||
| case ETFType.INTEGER_EXT: | case ETFType.INTEGER_EXT: | ||||
| _stream.Read(_buffer, 0, 4); | _stream.Read(_buffer, 0, 4); | ||||
| return ??; | |||||
| return (_buffer[0] << 24) | (_buffer[1] << 16) | (_buffer[2] << 8) | (_buffer[3]); | |||||
| case ETFType.SMALL_BIG_EXT: | case ETFType.SMALL_BIG_EXT: | ||||
| return ??; | |||||
| _stream.Read(_buffer, 0, 2); | |||||
| bool isPositive = _buffer[0] == 0; | |||||
| byte count = _buffer[1]; | |||||
| int shiftValue = (count - 1) * 8; | |||||
| ulong value = 0; | |||||
| _stream.Read(_buffer, 0, count); | |||||
| for (int i = 0; i < count; i++, shiftValue -= 8) | |||||
| value = value + _buffer[i] << shiftValue; | |||||
| if (!isPositive) | |||||
| return -(long)value; | |||||
| else | |||||
| return (long)value; | |||||
| } | } | ||||
| throw new InvalidDataException(); | throw new InvalidDataException(); | ||||
| } | } | ||||
| public void Write(sbyte value) => Write((long)value); | |||||
| public void Write(byte value) => Write((ulong)value); | |||||
| public void Write(short value) => Write((long)value); | |||||
| public void Write(ushort value) => Write((ulong)value); | |||||
| public void Write(int value) => Write((long)value); | |||||
| public void Write(uint value) => Write((ulong)value); | |||||
| public void Write(long value) | |||||
| public float ReadSingle() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return (float)ReadDoubleInternal(type); | |||||
| } | |||||
| public float? ReadNullableSingle() | |||||
| { | { | ||||
| if (value >= byte.MinValue && value <= byte.MaxValue) | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return (float)ReadDoubleInternal(type); | |||||
| } | |||||
| public double ReadDouble() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| return ReadDoubleInternal(type); | |||||
| } | |||||
| public double? ReadNullableDouble() | |||||
| { | |||||
| _stream.Read(_buffer, 0, 1); | |||||
| ETFType type = (ETFType)_buffer[0]; | |||||
| if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; | |||||
| return ReadDoubleInternal(type); | |||||
| } | |||||
| public double ReadDoubleInternal(ETFType type) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| public string ReadString() | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| public byte[] ReadByteArray() | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| #region Emit | |||||
| private static Func<ETFReader, T> CreateDeserializer<T>(Type type, TypeInfo typeInfo) | |||||
| where T : new() | |||||
| { | |||||
| var method = new DynamicMethod("DeserializeETF", type, new[] { typeof(ETFReader) }, true); | |||||
| var generator = method.GetILGenerator(); | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) | |||||
| EmitReadValue(generator, type, typeInfo, true); | |||||
| generator.Emit(OpCodes.Ret); | |||||
| return method.CreateDelegate(typeof(Func<ETFReader, T>)) as Func<ETFReader, T>; | |||||
| } | |||||
| private static void EmitReadValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) | |||||
| { | |||||
| //Convert enum types to their base type | |||||
| if (typeInfo.IsEnum) | |||||
| { | { | ||||
| _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT; | |||||
| _buffer[1] = (byte)value; | |||||
| _stream.Write(_buffer, 0, 2); | |||||
| type = Enum.GetUnderlyingType(type); | |||||
| typeInfo = type.GetTypeInfo(); | |||||
| } | } | ||||
| else if (value >= int.MinValue && value <= int.MaxValue) | |||||
| //Primitives/Enums | |||||
| if (!typeInfo.IsEnum && IsType(type, typeof(sbyte), typeof(byte), typeof(short), | |||||
| typeof(ushort), typeof(int), typeof(uint), typeof(long), | |||||
| typeof(ulong), typeof(double), typeof(bool), typeof(string), | |||||
| typeof(sbyte?), typeof(byte?), typeof(short?), typeof(ushort?), | |||||
| typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), | |||||
| typeof(bool?), typeof(float?), typeof(double?) | |||||
| /*typeof(object), typeof(DateTime)*/)) | |||||
| { | { | ||||
| _buffer[0] = (byte)ETFType.INTEGER_EXT; | |||||
| _buffer[1] = (byte)(value >> 24); | |||||
| _buffer[2] = (byte)(value >> 16); | |||||
| _buffer[3] = (byte)(value >> 8); | |||||
| _buffer[4] = (byte)value; | |||||
| _stream.Write(_buffer, 0, 5); | |||||
| //No conversion needed | |||||
| generator.EmitCall(OpCodes.Call, GetReadMethod(type), null); | |||||
| } | } | ||||
| else | |||||
| //Dictionaries | |||||
| /*else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces | |||||
| .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) | |||||
| { | |||||
| generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | |||||
| } | |||||
| //Enumerable | |||||
| else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces | |||||
| .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) | |||||
| { | { | ||||
| _buffer[0] = (byte)ETFType.SMALL_BIG_EXT; | |||||
| if (value < 0) | |||||
| generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | |||||
| } | |||||
| //Nullable Structs | |||||
| else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && | |||||
| typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) | |||||
| { | |||||
| generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | |||||
| } | |||||
| //Structs/Classes | |||||
| else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) | |||||
| { | |||||
| if (isTop) | |||||
| { | { | ||||
| _buffer[2] = 1; //Is negative | |||||
| value = -value; | |||||
| } | |||||
| typeInfo.ForEachField(f => | |||||
| { | |||||
| string name; | |||||
| if (!f.IsPublic || !IsETFProperty(f, out name)) return; | |||||
| byte bytes = 0; | |||||
| while (value > 0) | |||||
| _buffer[3 + bytes++] = (byte)(value >>= 8); | |||||
| _buffer[1] = bytes; //Encoded bytes | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) | |||||
| generator.Emit(OpCodes.Ldstr, name); //ETFReader(this), name | |||||
| generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) | |||||
| generator.Emit(OpCodes.Ldarg_1); //ETFReader(this), obj | |||||
| generator.Emit(OpCodes.Ldfld, f); //ETFReader(this), obj.fieldValue | |||||
| EmitWriteValue(generator, f.FieldType, f.FieldType.GetTypeInfo(), false); | |||||
| }); | |||||
| _stream.Write(_buffer, 0, 3 + bytes); | |||||
| } | |||||
| typeInfo.ForEachProperty(p => | |||||
| { | |||||
| string name; | |||||
| if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) | |||||
| generator.Emit(OpCodes.Ldstr, name); //ETFReader(this), name | |||||
| generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) | |||||
| generator.Emit(OpCodes.Ldarg_1); //ETFReader(this), obj | |||||
| generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFReader(this), obj.propValue | |||||
| EmitWriteValue(generator, p.PropertyType, p.PropertyType.GetTypeInfo(), false); | |||||
| }); | |||||
| } | |||||
| else | |||||
| { | |||||
| //While we could drill deeper and make a large serializer that also serializes all subclasses, | |||||
| //it's more efficient to serialize on a per-type basis via another Write<T> call. | |||||
| generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | |||||
| } | |||||
| }*/ | |||||
| //Unsupported (decimal, char) | |||||
| else | |||||
| throw new InvalidOperationException($"Deserializing {type.Name} is not supported."); | |||||
| } | } | ||||
| public void Write(ulong value) | |||||
| private static bool IsType(Type type, params Type[] types) | |||||
| { | { | ||||
| if (value <= byte.MaxValue) | |||||
| for (int i = 0; i < types.Length; i++) | |||||
| { | { | ||||
| _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT; | |||||
| _buffer[1] = (byte)value; | |||||
| _stream.Write(_buffer, 0, 2); | |||||
| if (type == types[i]) | |||||
| return true; | |||||
| } | } | ||||
| else if (value <= int.MaxValue) | |||||
| return false; | |||||
| } | |||||
| private static bool IsETFProperty(FieldInfo f, out string name) | |||||
| { | |||||
| var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); | |||||
| if (attrib != null) | |||||
| { | { | ||||
| _buffer[0] = (byte)ETFType.INTEGER_EXT; | |||||
| _buffer[1] = (byte)(value >> 24); | |||||
| _buffer[2] = (byte)(value >> 16); | |||||
| _buffer[3] = (byte)(value >> 8); | |||||
| _buffer[4] = (byte)value; | |||||
| _stream.Write(_buffer, 0, 5); | |||||
| name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? f.Name; | |||||
| return true; | |||||
| } | } | ||||
| else | |||||
| name = null; | |||||
| return false; | |||||
| } | |||||
| private static bool IsETFProperty(PropertyInfo p, out string name) | |||||
| { | |||||
| var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); | |||||
| if (attrib != null) | |||||
| { | { | ||||
| _buffer[0] = (byte)ETFType.SMALL_BIG_EXT; | |||||
| _buffer[2] = 0; //Always positive | |||||
| byte bytes = 0; | |||||
| while (value > 0) | |||||
| _buffer[3 + bytes++] = (byte)(value >>= 8); | |||||
| _buffer[1] = bytes; //Encoded bytes | |||||
| _stream.Write(_buffer, 0, 3 + bytes); | |||||
| name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? p.Name; | |||||
| return true; | |||||
| } | } | ||||
| name = null; | |||||
| return false; | |||||
| } | |||||
| private static MethodInfo GetReadMethod(string name) | |||||
| => typeof(ETFReader).GetTypeInfo().GetDeclaredMethods(name).Single(); | |||||
| private static MethodInfo GetReadMethod(Type type) | |||||
| { | |||||
| MethodInfo method; | |||||
| if (_readMethods.TryGetValue(type, out method)) | |||||
| return method; | |||||
| return null; | |||||
| } | } | ||||
| private static Dictionary<Type, MethodInfo> GetPrimitiveReadMethods() | |||||
| { | |||||
| return new Dictionary<Type, MethodInfo> | |||||
| { | |||||
| { typeof(bool), GetReadMethod(nameof(ReadBool)) }, | |||||
| { typeof(bool?), GetReadMethod(nameof(ReadNullableBool)) }, | |||||
| { typeof(byte), GetReadMethod(nameof(ReadByte)) }, | |||||
| { typeof(byte?), GetReadMethod(nameof(ReadNullableByte)) }, | |||||
| { typeof(sbyte), GetReadMethod(nameof(ReadSByte)) }, | |||||
| { typeof(sbyte?), GetReadMethod(nameof(ReadNullableSByte)) }, | |||||
| { typeof(short), GetReadMethod(nameof(ReadShort)) }, | |||||
| { typeof(short?), GetReadMethod(nameof(ReadNullableShort)) }, | |||||
| { typeof(ushort), GetReadMethod(nameof(ReadUShort)) }, | |||||
| { typeof(ushort?), GetReadMethod(nameof(ReadNullableUShort)) }, | |||||
| { typeof(int), GetReadMethod(nameof(ReadInt)) }, | |||||
| { typeof(int?), GetReadMethod(nameof(ReadNullableInt)) }, | |||||
| { typeof(uint), GetReadMethod(nameof(ReadUInt)) }, | |||||
| { typeof(uint?), GetReadMethod(nameof(ReadNullableUInt)) }, | |||||
| { typeof(long), GetReadMethod(nameof(ReadLong)) }, | |||||
| { typeof(long?), GetReadMethod(nameof(ReadNullableLong)) }, | |||||
| { typeof(ulong), GetReadMethod(nameof(ReadULong)) }, | |||||
| { typeof(ulong?), GetReadMethod(nameof(ReadNullableULong)) }, | |||||
| { typeof(float), GetReadMethod(nameof(ReadSingle)) }, | |||||
| { typeof(float?), GetReadMethod(nameof(ReadNullableSingle)) }, | |||||
| { typeof(double), GetReadMethod(nameof(ReadDouble)) }, | |||||
| { typeof(double?), GetReadMethod(nameof(ReadNullableDouble)) }, | |||||
| }; | |||||
| } | |||||
| #endregion | |||||
| #region IDisposable | |||||
| private bool _isDisposed = false; | |||||
| public void Write(float value) => Write((double)value); | |||||
| public unsafe void Write(double value) | |||||
| protected virtual void Dispose(bool disposing) | |||||
| { | { | ||||
| ulong value2 = *(ulong*)&value; | |||||
| _buffer[0] = (byte)ETFType.NEW_FLOAT_EXT; | |||||
| _buffer[1] = (byte)(value2 >> 56); | |||||
| _buffer[2] = (byte)(value2 >> 48); | |||||
| _buffer[3] = (byte)(value2 >> 40); | |||||
| _buffer[4] = (byte)(value2 >> 32); | |||||
| _buffer[5] = (byte)(value2 >> 24); | |||||
| _buffer[6] = (byte)(value2 >> 16); | |||||
| _buffer[7] = (byte)(value2 >> 8); | |||||
| _buffer[8] = (byte)value2; | |||||
| _stream.Write(_buffer, 0, 9); | |||||
| if (!_isDisposed) | |||||
| { | |||||
| if (disposing) | |||||
| { | |||||
| if (_leaveOpen) | |||||
| _stream.Flush(); | |||||
| else | |||||
| _stream.Dispose(); | |||||
| } | |||||
| _isDisposed = true; | |||||
| } | |||||
| } | } | ||||
| public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); | |||||
| public void Dispose() => Dispose(true); | |||||
| #endregion | |||||
| } | } | ||||
| }*/ | |||||
| } | |||||