From dfd3bf3b6bcddea48a1703f3d6fd79c25f7954a0 Mon Sep 17 00:00:00 2001 From: Kamran Ayub Date: Wed, 5 Jun 2024 10:46:22 -0500 Subject: [PATCH 1/7] fix(external_games)!: media is nullable --- IGDB/Models/ExternalGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IGDB/Models/ExternalGame.cs b/IGDB/Models/ExternalGame.cs index 8c3757e..f6347ab 100644 --- a/IGDB/Models/ExternalGame.cs +++ b/IGDB/Models/ExternalGame.cs @@ -12,7 +12,7 @@ public class ExternalGame : ITimestamps, IIdentifier, IHasChecksum public IdentityOrValue Game { get; set; } public long? Id { get; set; } - public ExternalGameMedia Media { get; set; } + public ExternalGameMedia? Media { get; set; } public string Name { get; set; } From 6be2c25826acd5db29ab8d3a7c7675ad8c615140 Mon Sep 17 00:00:00 2001 From: Kamran Ayub Date: Wed, 12 Jun 2024 00:45:46 -0500 Subject: [PATCH 2/7] refactor: optimize detection of identity classes for performance --- IGDB/Serialization/IdentityConverter.cs | 32 ++++++++----------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/IGDB/Serialization/IdentityConverter.cs b/IGDB/Serialization/IdentityConverter.cs index 0e433a8..8599306 100644 --- a/IGDB/Serialization/IdentityConverter.cs +++ b/IGDB/Serialization/IdentityConverter.cs @@ -10,8 +10,7 @@ public class IdentityConverter : JsonConverter { public override bool CanConvert(Type objectType) { - return IsAssignableToGenericType(objectType, typeof(IdentityOrValue<>)) || - IsAssignableToGenericType(objectType, typeof(IdentitiesOrValues<>)); + return IsIdentityOrValue(objectType) || IsIdentitiesOrValues(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) @@ -24,7 +23,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist var expandedType = objectType.GetGenericArguments()[0]; var value = reader.Value; - if (IsAssignableToGenericType(objectType, typeof(IdentitiesOrValues<>))) + if (IsIdentitiesOrValues(objectType)) { if (reader.TokenType != JsonToken.StartArray) { @@ -59,7 +58,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist var ctor = objectType.GetConstructor(new[] { typeof(object[]) }); return ctor.Invoke(new[] { convertedValues }); } - else if (IsAssignableToGenericType(objectType, typeof(IdentityOrValue<>))) + else if (IsIdentityOrValue(objectType)) { if (reader.TokenType == JsonToken.StartObject) { @@ -82,11 +81,11 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s { dynamic identity = value; - if (IsAssignableToGenericType(value.GetType(), typeof(IdentitiesOrValues<>))) + if (IsIdentitiesOrValues(value.GetType())) { serializer.Serialize(writer, identity.Ids ?? identity.Values ?? null); } - else if (IsAssignableToGenericType(value.GetType(), typeof(IdentityOrValue<>))) + else if (IsIdentityOrValue(value.GetType())) { serializer.Serialize(writer, identity.Id ?? identity.Value ?? null); } @@ -97,23 +96,12 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s } } - public static bool IsAssignableToGenericType(Type givenType, Type genericType) - { - var interfaceTypes = givenType.GetInterfaces(); - - foreach (var it in interfaceTypes) - { - if (it.IsGenericType && it.GetGenericTypeDefinition() == genericType) - return true; - } - - if (givenType.IsGenericType && givenType.GetGenericTypeDefinition() == genericType) - return true; - - Type baseType = givenType.BaseType; - if (baseType == null) return false; + public static bool IsIdentityOrValue(Type givenType) { + return givenType.Name.Contains("IdentityOrValue`"); + } - return IsAssignableToGenericType(baseType, genericType); + public static bool IsIdentitiesOrValues(Type givenType) { + return givenType.Name.Contains("IdentitiesOrValues`"); } } From a17ecd5589f3fbeb60aeb2b66c1ffe3ee659544f Mon Sep 17 00:00:00 2001 From: Kamran Ayub Date: Wed, 12 Jun 2024 00:51:46 -0500 Subject: [PATCH 3/7] refactor: use typeof --- IGDB/Serialization/IdentityConverter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IGDB/Serialization/IdentityConverter.cs b/IGDB/Serialization/IdentityConverter.cs index 8599306..c89f14e 100644 --- a/IGDB/Serialization/IdentityConverter.cs +++ b/IGDB/Serialization/IdentityConverter.cs @@ -97,11 +97,11 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s } public static bool IsIdentityOrValue(Type givenType) { - return givenType.Name.Contains("IdentityOrValue`"); + return givenType.Name.Contains(typeof(IdentityOrValue<>).Name); } public static bool IsIdentitiesOrValues(Type givenType) { - return givenType.Name.Contains("IdentitiesOrValues`"); + return givenType.Name.Contains(typeof(IdentitiesOrValues<>).Name); } } From f70ea2d321d146612700a9390eb22ee8e7e02c32 Mon Sep 17 00:00:00 2001 From: Kamran Ayub Date: Thu, 13 Jun 2024 06:55:58 -0500 Subject: [PATCH 4/7] refactor: cache typeof --- IGDB/Serialization/IdentityConverter.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/IGDB/Serialization/IdentityConverter.cs b/IGDB/Serialization/IdentityConverter.cs index c89f14e..3dbbf0f 100644 --- a/IGDB/Serialization/IdentityConverter.cs +++ b/IGDB/Serialization/IdentityConverter.cs @@ -96,12 +96,15 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s } } + private static readonly string IdentitiesOrValuesName = typeof(IdentitiesOrValues<>).Name; + private static readonly string IdentityOrValueName = typeof(IdentityOrValue<>).Name; + public static bool IsIdentityOrValue(Type givenType) { - return givenType.Name.Contains(typeof(IdentityOrValue<>).Name); + return givenType.Name.Contains(IdentityOrValueName); } public static bool IsIdentitiesOrValues(Type givenType) { - return givenType.Name.Contains(typeof(IdentitiesOrValues<>).Name); + return givenType.Name.Contains(IdentitiesOrValuesName); } } From 55ad31e257445ac6939e4a6807f060d0a951f51f Mon Sep 17 00:00:00 2001 From: Kamran Ayub Date: Thu, 13 Jun 2024 07:39:04 -0500 Subject: [PATCH 5/7] refactor(perf): use compiled lambda activator --- IGDB/Identity.cs | 5 ++ IGDB/Serialization/IdentityConverter.cs | 88 ++++++++++++++++++++++--- IGDB/Serialization/LamdaActivator.cs | 47 +++++++++++++ 3 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 IGDB/Serialization/LamdaActivator.cs diff --git a/IGDB/Identity.cs b/IGDB/Identity.cs index ac3f74b..527aad1 100644 --- a/IGDB/Identity.cs +++ b/IGDB/Identity.cs @@ -49,5 +49,10 @@ public IdentitiesOrValues(object[] values) var list = values.Select(value => (T)value).ToArray(); Values = list; } + + public IdentitiesOrValues(T[] values) + { + Values = values; + } } } \ No newline at end of file diff --git a/IGDB/Serialization/IdentityConverter.cs b/IGDB/Serialization/IdentityConverter.cs index 3dbbf0f..52a8ce9 100644 --- a/IGDB/Serialization/IdentityConverter.cs +++ b/IGDB/Serialization/IdentityConverter.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using static IGDB.Serialization.LambdaActivator; namespace IGDB { @@ -47,28 +48,33 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist } } + var valuesActivator = GetValuesActivator(objectType); + var identitiesActivator = GetIdentitiesActivator(objectType); + // If any are objects, it means the IDs should be ignored if (values.All(v => v.GetType().IsAssignableFrom(typeof(long)))) { - return Activator.CreateInstance(objectType, values.Cast().ToArray()); + return identitiesActivator(values.Cast().ToArray()); } var objects = values.Where(v => !v.GetType().IsAssignableFrom(typeof(long))); var convertedValues = objects.ToArray(); - var ctor = objectType.GetConstructor(new[] { typeof(object[]) }); - return ctor.Invoke(new[] { convertedValues }); + return valuesActivator(new[] { convertedValues }); } else if (IsIdentityOrValue(objectType)) { + var identityActivator = GetIdentityActivator(objectType); + var valueActivator = GetValueActivator(objectType); + if (reader.TokenType == JsonToken.StartObject) { // objects - return Activator.CreateInstance(objectType, serializer.Deserialize(reader, expandedType)); + return valueActivator(serializer.Deserialize(reader, expandedType)); } else if (reader.TokenType == JsonToken.Integer) { // int ids - return Activator.CreateInstance(objectType, (long)reader.Value); + return identityActivator((long)reader.Value); } } @@ -98,15 +104,79 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s private static readonly string IdentitiesOrValuesName = typeof(IdentitiesOrValues<>).Name; private static readonly string IdentityOrValueName = typeof(IdentityOrValue<>).Name; - - public static bool IsIdentityOrValue(Type givenType) { + + public static bool IsIdentityOrValue(Type givenType) + { return givenType.Name.Contains(IdentityOrValueName); } - public static bool IsIdentitiesOrValues(Type givenType) { + public static bool IsIdentitiesOrValues(Type givenType) + { return givenType.Name.Contains(IdentitiesOrValuesName); } + + private static readonly IDictionary identitiesActivators + = new Dictionary(); + private static readonly IDictionary valuesActivators + = new Dictionary(); + private static readonly IDictionary identityActivators + = new Dictionary(); + private static readonly IDictionary valueActivators + = new Dictionary(); + + private static ObjectActivator GetIdentitiesActivator(Type objectType) + { + if (identitiesActivators.ContainsKey(objectType)) + { + return identitiesActivators[objectType]; + } + + ConstructorInfo ctor = objectType.GetConstructors().Skip(1).First(); + var activator = GetActivator(ctor); + identitiesActivators[objectType] = activator; + return activator; + } + + private static ObjectActivator GetValuesActivator(Type objectType) + { + if (valuesActivators.ContainsKey(objectType)) + { + return valuesActivators[objectType]; + } + + ConstructorInfo ctor = objectType.GetConstructors().Skip(2).First(); + var activator = GetActivator(ctor); + valuesActivators[objectType] = activator; + return activator; + } + + private static ObjectActivator GetIdentityActivator(Type objectType) + { + if (identityActivators.ContainsKey(objectType)) + { + return identityActivators[objectType]; + } + + ConstructorInfo ctor = objectType.GetConstructors().Skip(1).First(); + var activator = GetActivator(ctor); + identityActivators[objectType] = activator; + return activator; + } + + private static ObjectActivator GetValueActivator(Type objectType) + { + if (valueActivators.ContainsKey(objectType)) + { + return valueActivators[objectType]; + } + + ConstructorInfo ctor = objectType.GetConstructors().Skip(2).First(); + var activator = GetActivator(ctor); + valueActivators[objectType] = activator; + return activator; + } } + } \ No newline at end of file diff --git a/IGDB/Serialization/LamdaActivator.cs b/IGDB/Serialization/LamdaActivator.cs new file mode 100644 index 0000000..a4ecf96 --- /dev/null +++ b/IGDB/Serialization/LamdaActivator.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace IGDB.Serialization +{ + /// + /// See: https://rogerjohansson.blog/2008/02/28/linq-expressions-creating-objects/ + /// + public static class LambdaActivator + { + public delegate object ObjectActivator(params object[] args); + + public static ObjectActivator GetActivator(ConstructorInfo ctor) + { + ParameterInfo[] paramsInfo = ctor.GetParameters(); + + ParameterExpression param = + Expression.Parameter(typeof(object[]), "args"); + + Expression[] argsExp = + new Expression[paramsInfo.Length]; + + for (int i = 0; i < paramsInfo.Length; i++) + { + Expression index = Expression.Constant(i); + Type paramType = paramsInfo[i].ParameterType; + + Expression paramAccessorExp = + Expression.ArrayIndex(param, index); + + Expression paramCastExp = + Expression.Convert(paramAccessorExp, paramType); + + argsExp[i] = paramCastExp; + } + + NewExpression newExp = Expression.New(ctor, argsExp); + + LambdaExpression lambda = + Expression.Lambda(typeof(ObjectActivator), newExp, param); + + ObjectActivator compiled = (ObjectActivator)lambda.Compile(); + return compiled; + } + } +} \ No newline at end of file From 180068d4333f14b86ad502f4325af6d8cd3cefca Mon Sep 17 00:00:00 2001 From: Kamran Ayub Date: Thu, 13 Jun 2024 07:47:29 -0500 Subject: [PATCH 6/7] refactor!: fix namespaces, mark LamdaActivator internal but expose ctor getters --- IGDB/IGDBApi.cs | 1 + IGDB/Serialization/IdentityConverter.cs | 8 ++++---- IGDB/Serialization/LamdaActivator.cs | 6 +++--- IGDB/Serialization/UnixTimestampConverter.cs | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/IGDB/IGDBApi.cs b/IGDB/IGDBApi.cs index ed84fb0..ba514e3 100644 --- a/IGDB/IGDBApi.cs +++ b/IGDB/IGDBApi.cs @@ -3,6 +3,7 @@ using System.IO; using System.Threading.Tasks; using IGDB.Models; +using IGDB.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using RestEase; diff --git a/IGDB/Serialization/IdentityConverter.cs b/IGDB/Serialization/IdentityConverter.cs index 52a8ce9..6d6166e 100644 --- a/IGDB/Serialization/IdentityConverter.cs +++ b/IGDB/Serialization/IdentityConverter.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; using static IGDB.Serialization.LambdaActivator; -namespace IGDB +namespace IGDB.Serialization { public class IdentityConverter : JsonConverter { @@ -137,7 +137,7 @@ private static ObjectActivator GetIdentitiesActivator(Type objectType) return activator; } - private static ObjectActivator GetValuesActivator(Type objectType) + public static ObjectActivator GetValuesActivator(Type objectType) { if (valuesActivators.ContainsKey(objectType)) { @@ -150,7 +150,7 @@ private static ObjectActivator GetValuesActivator(Type objectType) return activator; } - private static ObjectActivator GetIdentityActivator(Type objectType) + public static ObjectActivator GetIdentityActivator(Type objectType) { if (identityActivators.ContainsKey(objectType)) { @@ -163,7 +163,7 @@ private static ObjectActivator GetIdentityActivator(Type objectType) return activator; } - private static ObjectActivator GetValueActivator(Type objectType) + public static ObjectActivator GetValueActivator(Type objectType) { if (valueActivators.ContainsKey(objectType)) { diff --git a/IGDB/Serialization/LamdaActivator.cs b/IGDB/Serialization/LamdaActivator.cs index a4ecf96..fc5f9fe 100644 --- a/IGDB/Serialization/LamdaActivator.cs +++ b/IGDB/Serialization/LamdaActivator.cs @@ -4,13 +4,13 @@ namespace IGDB.Serialization { + public delegate object ObjectActivator(params object[] args); + /// /// See: https://rogerjohansson.blog/2008/02/28/linq-expressions-creating-objects/ /// - public static class LambdaActivator + internal static class LambdaActivator { - public delegate object ObjectActivator(params object[] args); - public static ObjectActivator GetActivator(ConstructorInfo ctor) { ParameterInfo[] paramsInfo = ctor.GetParameters(); diff --git a/IGDB/Serialization/UnixTimestampConverter.cs b/IGDB/Serialization/UnixTimestampConverter.cs index 3e3561f..37dabb0 100644 --- a/IGDB/Serialization/UnixTimestampConverter.cs +++ b/IGDB/Serialization/UnixTimestampConverter.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace IGDB +namespace IGDB.Serialization { public class UnixTimestampConverter : JsonConverter { From fb015f8ef5a02cf68d873bfedcaf06814dc8a206 Mon Sep 17 00:00:00 2001 From: Kamran Ayub Date: Thu, 13 Jun 2024 08:21:19 -0500 Subject: [PATCH 7/7] fix: public GetIdentitiesActivator --- IGDB/Serialization/IdentityConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IGDB/Serialization/IdentityConverter.cs b/IGDB/Serialization/IdentityConverter.cs index 6d6166e..5258d97 100644 --- a/IGDB/Serialization/IdentityConverter.cs +++ b/IGDB/Serialization/IdentityConverter.cs @@ -124,7 +124,7 @@ private static readonly IDictionary identityActivators private static readonly IDictionary valueActivators = new Dictionary(); - private static ObjectActivator GetIdentitiesActivator(Type objectType) + public static ObjectActivator GetIdentitiesActivator(Type objectType) { if (identitiesActivators.ContainsKey(objectType)) {