| @@ -13,23 +13,13 @@ namespace Discord.ETF | |||||
| public unsafe class ETFWriter : IDisposable | public unsafe class ETFWriter : IDisposable | ||||
| { | { | ||||
| private readonly static byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' }; | private readonly static byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' }; | ||||
| private readonly static byte[] _nilExtBytes = new byte[] { (byte)ETFType.NIL_EXT}; | |||||
| private readonly static byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; | private readonly static byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; | ||||
| private readonly static byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; | private readonly static byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; | ||||
| private readonly static MethodInfo _writeTMethod = typeof(ETFWriter).GetTypeInfo() | |||||
| .GetDeclaredMethods(nameof(Write)) | |||||
| .Where(x => x.IsGenericMethodDefinition && x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0]) | |||||
| .Single(); | |||||
| private readonly static MethodInfo _writeNullableTMethod = typeof(ETFWriter).GetTypeInfo() | |||||
| .GetDeclaredMethods(nameof(Write)) | |||||
| .Where(x => | |||||
| { | |||||
| if (!x.IsGenericMethodDefinition) return false; | |||||
| var p = x.GetParameters()[0].ParameterType.GetTypeInfo(); | |||||
| return p.IsGenericType && p.GetGenericTypeDefinition() == typeof(Nullable<>); | |||||
| }) | |||||
| .Single(); | |||||
| private readonly static MethodInfo _writeTMethod = GetGenericWriteMethod(null); | |||||
| private readonly static MethodInfo _writeNullableTMethod = GetGenericWriteMethod(typeof(Nullable<>)); | |||||
| private readonly static MethodInfo _writeDictionaryTMethod = GetGenericWriteMethod(typeof(IDictionary<,>)); | |||||
| private readonly static MethodInfo _writeEnumerableTMethod = GetGenericWriteMethod(typeof(IEnumerable<>)); | |||||
| private readonly static DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | private readonly static DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | ||||
| private readonly Stream _stream; | private readonly Stream _stream; | ||||
| @@ -58,29 +48,6 @@ namespace Discord.ETF | |||||
| _serializers = new ConcurrentDictionary<Type, Delegate>(); | _serializers = new ConcurrentDictionary<Type, Delegate>(); | ||||
| _indirectSerializers = new ConcurrentDictionary<Type, Delegate>(); | _indirectSerializers = new ConcurrentDictionary<Type, Delegate>(); | ||||
| } | } | ||||
| enum EnumTest1 { A, B, C } | |||||
| public static byte[] Test() | |||||
| { | |||||
| using (var stream = new MemoryStream()) | |||||
| { | |||||
| using (var writer = new ETFWriter(stream)) | |||||
| { | |||||
| var request = new API.Client.Rest.SendMessageRequest(109384029348) | |||||
| { | |||||
| Content = "TestMsg", | |||||
| Nonce = null, | |||||
| IsTTS = false | |||||
| }; | |||||
| writer.Write(request); | |||||
| /*writer.Write<EnumTest1>((EnumTest1?)EnumTest1.C); | |||||
| writer.Write((object)(EnumTest1?)EnumTest1.C); | |||||
| writer.Write<EnumTest1>((EnumTest1?)null); | |||||
| writer.Write((object)(EnumTest1?)null);*/ | |||||
| } | |||||
| return stream.ToArray(); | |||||
| } | |||||
| } | |||||
| public void Write(bool value) | public void Write(bool value) | ||||
| { | { | ||||
| @@ -177,18 +144,19 @@ namespace Discord.ETF | |||||
| _stream.Write(_buffer, 0, 9); | _stream.Write(_buffer, 0, 9); | ||||
| } | } | ||||
| public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerMillisecond)); | |||||
| public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); | |||||
| public void Write(byte? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } | |||||
| public void Write(bool? value) { if (value.HasValue) Write((bool)value.Value); else WriteNil(); } | |||||
| public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } | public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } | ||||
| public void Write(ushort? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } | |||||
| public void Write(byte? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } | |||||
| public void Write(short? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } | public void Write(short? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } | ||||
| public void Write(uint? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } | |||||
| public void Write(ushort? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } | |||||
| public void Write(int? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | public void Write(int? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | ||||
| public void Write(ulong? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | |||||
| public void Write(uint? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } | |||||
| public void Write(long? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | public void Write(long? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | ||||
| public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); } | |||||
| public void Write(ulong? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | |||||
| public void Write(double? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | public void Write(double? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | ||||
| public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); } | |||||
| public void Write(DateTime? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | public void Write(DateTime? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } | ||||
| public void Write(byte[] value) | public void Write(byte[] value) | ||||
| @@ -251,10 +219,13 @@ namespace Discord.ETF | |||||
| _buffer[2] = (byte)((length >> 16) & 0xFF); | _buffer[2] = (byte)((length >> 16) & 0xFF); | ||||
| _buffer[3] = (byte)((length >> 8) & 0xFF); | _buffer[3] = (byte)((length >> 8) & 0xFF); | ||||
| _buffer[4] = (byte)(length & 0xFF); | _buffer[4] = (byte)(length & 0xFF); | ||||
| _stream.Write(_buffer, 0, 5); | |||||
| for (int i = 0; i < array.Length; i++) | for (int i = 0; i < array.Length; i++) | ||||
| Write(array[i]); | Write(array[i]); | ||||
| WriteNilExt(); | |||||
| _stream.Write(_buffer, 0, 5); | |||||
| _buffer[0] = (byte)ETFType.NIL_EXT; | |||||
| _stream.Write(_buffer, 0, 1); | |||||
| } | } | ||||
| else | else | ||||
| WriteNil(); | WriteNil(); | ||||
| @@ -269,12 +240,13 @@ namespace Discord.ETF | |||||
| _buffer[2] = (byte)((length >> 16) & 0xFF); | _buffer[2] = (byte)((length >> 16) & 0xFF); | ||||
| _buffer[3] = (byte)((length >> 8) & 0xFF); | _buffer[3] = (byte)((length >> 8) & 0xFF); | ||||
| _buffer[4] = (byte)(length & 0xFF); | _buffer[4] = (byte)(length & 0xFF); | ||||
| _stream.Write(_buffer, 0, 5); | |||||
| foreach (var pair in obj) | foreach (var pair in obj) | ||||
| { | { | ||||
| Write(pair.Key); | Write(pair.Key); | ||||
| Write(pair.Value); | Write(pair.Value); | ||||
| } | } | ||||
| _stream.Write(_buffer, 0, 5); | |||||
| } | } | ||||
| else | else | ||||
| WriteNil(); | WriteNil(); | ||||
| @@ -292,147 +264,137 @@ namespace Discord.ETF | |||||
| WriteNil(); | WriteNil(); | ||||
| } | } | ||||
| private void WriteNil() => _stream.Write(_nilBytes, 0, _nilBytes.Length); | |||||
| public virtual void Flush() => _stream.Flush(); | public virtual void Flush() => _stream.Flush(); | ||||
| public virtual long Seek(int offset, SeekOrigin origin) => _stream.Seek(offset, origin); | public virtual long Seek(int offset, SeekOrigin origin) => _stream.Seek(offset, origin); | ||||
| private Action<ETFWriter, T> CreateSerializer<T>(Type type, TypeInfo typeInfo, bool isObject) | |||||
| #region Emit | |||||
| private Action<ETFWriter, T> CreateSerializer<T>(Type type, TypeInfo typeInfo, bool isDirect) | |||||
| { | { | ||||
| var method = new DynamicMethod(!isObject ? "SerializeETF" : "SerializeIndirectETF", | |||||
| null, new[] { typeof(ETFWriter), isObject? typeof(object) : type }, true); | |||||
| var method = new DynamicMethod(isDirect ? "SerializeETF" : "SerializeIndirectETF", | |||||
| null, new[] { typeof(ETFWriter), isDirect ? type : typeof(object) }, true); | |||||
| var generator = method.GetILGenerator(); | var generator = method.GetILGenerator(); | ||||
| if (typeInfo.IsPrimitive || typeInfo.IsEnum) | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), value | |||||
| if (!isDirect) | |||||
| { | { | ||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), value | |||||
| if (isObject && typeInfo.IsValueType) | |||||
| generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), value | |||||
| EmitETFWriteValue(generator, type, typeInfo); | |||||
| if (typeInfo.IsValueType) //Unbox value types | |||||
| generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), real_value | |||||
| else //Cast reference types | |||||
| generator.Emit(OpCodes.Castclass, type); //ETFWriter(this), real_value | |||||
| generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(type), null); //Call generic version | |||||
| } | } | ||||
| else | else | ||||
| { | |||||
| //Scan for certain interfaces | |||||
| Type dictionaryI = null, enumerableI = null; | |||||
| foreach (var i in typeInfo.ImplementedInterfaces | |||||
| .Where(x => x.IsConstructedGenericType)) | |||||
| { | |||||
| if (i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) | |||||
| { | |||||
| //TODO: Emit null check | |||||
| dictionaryI = i; | |||||
| break; | |||||
| } | |||||
| else if (i.GetGenericTypeDefinition() == typeof(IEnumerable<>)) | |||||
| { | |||||
| //TODO: Emit null check | |||||
| enumerableI = i; | |||||
| break; | |||||
| } | |||||
| } | |||||
| if (dictionaryI != null) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| else if (enumerableI != null) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| else | |||||
| { | |||||
| //TODO: Add field/property names | |||||
| typeInfo.ForEachField(f => | |||||
| { | |||||
| string name; | |||||
| if (!f.IsPublic || !IsETFProperty(f, out name)) return; | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name | |||||
| generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj | |||||
| generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue | |||||
| if (isObject && typeInfo.IsValueType) | |||||
| generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), obj.fieldValue | |||||
| EmitETFWriteValue(generator, f.FieldType); | |||||
| }); | |||||
| typeInfo.ForEachProperty(p => | |||||
| { | |||||
| string name; | |||||
| if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name | |||||
| generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj | |||||
| generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(this), obj.propValue | |||||
| if (isObject && typeInfo.IsValueType) | |||||
| generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), obj.propValue | |||||
| EmitETFWriteValue(generator, p.PropertyType); | |||||
| }); | |||||
| } | |||||
| } | |||||
| EmitWriteValue(generator, type, typeInfo, true); | |||||
| generator.Emit(OpCodes.Ret); | generator.Emit(OpCodes.Ret); | ||||
| return method.CreateDelegate(typeof(Action<ETFWriter, T>)) as Action<ETFWriter, T>; | return method.CreateDelegate(typeof(Action<ETFWriter, T>)) as Action<ETFWriter, T>; | ||||
| } | } | ||||
| private void EmitETFWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo = null) | |||||
| { | |||||
| if (typeInfo == null) | |||||
| typeInfo = type.GetTypeInfo(); | |||||
| private void EmitWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) | |||||
| { | |||||
| //Convert enum types to their base type | //Convert enum types to their base type | ||||
| if (typeInfo.IsEnum) | if (typeInfo.IsEnum) | ||||
| { | { | ||||
| type = Enum.GetUnderlyingType(type); | type = Enum.GetUnderlyingType(type); | ||||
| typeInfo = type.GetTypeInfo(); | typeInfo = type.GetTypeInfo(); | ||||
| } | |||||
| } | |||||
| //Check if this type already has a direct call or simple conversion | |||||
| //Primitives/Enums | |||||
| Type targetType = null; | Type targetType = null; | ||||
| if (!typeInfo.IsEnum && IsType(type, typeof(long), typeof(ulong), typeof(double), typeof(bool), typeof(string), | if (!typeInfo.IsEnum && IsType(type, typeof(long), typeof(ulong), typeof(double), typeof(bool), typeof(string), | ||||
| typeof(sbyte?), typeof(byte?), typeof(short?), typeof(ushort?), | typeof(sbyte?), typeof(byte?), typeof(short?), typeof(ushort?), | ||||
| typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), | typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), | ||||
| typeof(bool?), typeof(float?), typeof(double?), | typeof(bool?), typeof(float?), typeof(double?), | ||||
| typeof(object), typeof(DateTime))) | typeof(object), typeof(DateTime))) | ||||
| targetType = type; //Direct | |||||
| { | |||||
| //No conversion needed | |||||
| targetType = type; | |||||
| } | |||||
| else if (IsType(type, typeof(sbyte), typeof(short), typeof(int))) | else if (IsType(type, typeof(sbyte), typeof(short), typeof(int))) | ||||
| { | { | ||||
| //Convert to long | |||||
| generator.Emit(OpCodes.Conv_I8); | generator.Emit(OpCodes.Conv_I8); | ||||
| targetType = typeof(long); | targetType = typeof(long); | ||||
| } | } | ||||
| else if (IsType(type, typeof(byte), typeof(ushort), typeof(uint))) | else if (IsType(type, typeof(byte), typeof(ushort), typeof(uint))) | ||||
| { | { | ||||
| //Convert to ulong | |||||
| generator.Emit(OpCodes.Conv_U8); | generator.Emit(OpCodes.Conv_U8); | ||||
| targetType = typeof(ulong); | targetType = typeof(ulong); | ||||
| } | } | ||||
| else if (IsType(type, typeof(float))) | else if (IsType(type, typeof(float))) | ||||
| { | { | ||||
| //Convert to double | |||||
| generator.Emit(OpCodes.Conv_R8); | generator.Emit(OpCodes.Conv_R8); | ||||
| targetType = typeof(double); | targetType = typeof(double); | ||||
| } | } | ||||
| //Primitives/Enums | |||||
| if (targetType != null) | if (targetType != null) | ||||
| generator.EmitCall(OpCodes.Call, GetWriteMethod(targetType), null); | generator.EmitCall(OpCodes.Call, GetWriteMethod(targetType), null); | ||||
| //Nullable Non-Primitives | |||||
| else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) | |||||
| //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<>))) | |||||
| 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); | generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); | ||||
| //Structs/Classes | //Structs/Classes | ||||
| else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) | else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) | ||||
| generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(type), null); | |||||
| { | |||||
| if (isTop) | |||||
| { | |||||
| typeInfo.ForEachField(f => | |||||
| { | |||||
| string name; | |||||
| if (!f.IsPublic || !IsETFProperty(f, out name)) return; | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name | |||||
| generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj | |||||
| generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue | |||||
| EmitWriteValue(generator, f.FieldType, f.FieldType.GetTypeInfo(), false); | |||||
| }); | |||||
| typeInfo.ForEachProperty(p => | |||||
| { | |||||
| string name; | |||||
| if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name | |||||
| generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); | |||||
| generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) | |||||
| generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj | |||||
| generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(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) | //Unsupported (decimal, char) | ||||
| else | else | ||||
| throw new InvalidOperationException($"Serializing {type.Name} is not supported."); | throw new InvalidOperationException($"Serializing {type.Name} is not supported."); | ||||
| } | } | ||||
| private bool IsType(Type type, params Type[] types) | |||||
| private static bool IsType(Type type, params Type[] types) | |||||
| { | { | ||||
| for (int i = 0; i < types.Length; i++) | for (int i = 0; i < types.Length; i++) | ||||
| { | { | ||||
| @@ -441,17 +403,7 @@ namespace Discord.ETF | |||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| private MethodInfo GetWriteMethod(Type paramType) | |||||
| { | |||||
| return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write)) | |||||
| .Where(x => x.GetParameters()[0].ParameterType == paramType) | |||||
| .FirstOrDefault(); | |||||
| } | |||||
| private void WriteNil() => _stream.Write(_nilBytes, 0, _nilBytes.Length); | |||||
| private void WriteNilExt() => _stream.Write(_nilExtBytes, 0, _nilExtBytes.Length); | |||||
| private bool IsETFProperty(FieldInfo f, out string name) | |||||
| private static bool IsETFProperty(FieldInfo f, out string name) | |||||
| { | { | ||||
| var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); | var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); | ||||
| if (attrib != null) | if (attrib != null) | ||||
| @@ -462,7 +414,7 @@ namespace Discord.ETF | |||||
| name = null; | name = null; | ||||
| return false; | return false; | ||||
| } | } | ||||
| private bool IsETFProperty(PropertyInfo p, out string name) | |||||
| private static bool IsETFProperty(PropertyInfo p, out string name) | |||||
| { | { | ||||
| var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); | var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); | ||||
| if (attrib != null) | if (attrib != null) | ||||
| @@ -474,6 +426,36 @@ namespace Discord.ETF | |||||
| return false; | return false; | ||||
| } | } | ||||
| private static MethodInfo GetWriteMethod(Type paramType) | |||||
| { | |||||
| return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write)) | |||||
| .Where(x => x.GetParameters()[0].ParameterType == paramType) | |||||
| .FirstOrDefault(); | |||||
| } | |||||
| private static MethodInfo GetGenericWriteMethod(Type genericType) | |||||
| { | |||||
| if (genericType == null) | |||||
| { | |||||
| return typeof(ETFWriter).GetTypeInfo() | |||||
| .GetDeclaredMethods(nameof(Write)) | |||||
| .Where(x => x.IsGenericMethodDefinition && x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0]) | |||||
| .Single(); | |||||
| } | |||||
| else | |||||
| { | |||||
| return typeof(ETFWriter).GetTypeInfo() | |||||
| .GetDeclaredMethods(nameof(Write)) | |||||
| .Where(x => | |||||
| { | |||||
| if (!x.IsGenericMethodDefinition) return false; | |||||
| var p = x.GetParameters()[0].ParameterType.GetTypeInfo(); | |||||
| return p.IsGenericType && p.GetGenericTypeDefinition() == genericType; | |||||
| }) | |||||
| .Single(); | |||||
| } | |||||
| } | |||||
| #endregion | |||||
| #region IDisposable | #region IDisposable | ||||
| private bool _isDisposed = false; | private bool _isDisposed = false; | ||||