diff --git a/DEMO/DEMO.csproj b/DEMO/DEMO.csproj
index 89a32d4..480b2a2 100644
--- a/DEMO/DEMO.csproj
+++ b/DEMO/DEMO.csproj
@@ -1,5 +1,6 @@
+
Exe
net7.0
@@ -16,11 +17,12 @@
-
+
+
+
-
diff --git a/DEMO/DynamicTypes.cs b/DEMO/DynamicTypes.cs
index 38f23c0..5ae71c7 100644
--- a/DEMO/DynamicTypes.cs
+++ b/DEMO/DynamicTypes.cs
@@ -33,7 +33,7 @@ public partial class D : B
private int Bab { get; set; }
[NoosonDynamicType(typeof(int), typeof(string))]
- public object SomeOther { get; set; }
+ public object? SomeOther { get; set; }
}
//
// void Test(System.IO.BinaryReader br)
diff --git a/DEMO/Program.cs b/DEMO/Program.cs
index 3ccd5d9..a438c98 100644
--- a/DEMO/Program.cs
+++ b/DEMO/Program.cs
@@ -5,7 +5,11 @@
using System.Collections.Generic;
using System.Drawing;
using System.IO;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Runtime.CompilerServices;
using NonSucking.Framework.Serialization;
+using NonSucking.Framework.Serialization.Advanced;
using static DEMO.SUTMessage;
namespace DEMO
@@ -28,6 +32,8 @@ static void Main(string[] args)
bll.NameOfList = "ABC";
var conv = Newtonsoft.Json.JsonConvert.SerializeObject(bll, new JsonSerializerSettings { });
+ var containerType = new ContainingClass()
+ { RuntimeValue = new RuntimeTypeTestA("huhu") { SomeValue = 1234 } };
var sutMessage = new SUTMessage()
{
AlternativUser = new User { Name = "Okay" },
@@ -58,7 +64,9 @@ static void Main(string[] args)
Type = 897987414,
UsersList = new List() { new User() { Name = "1User" }, new User() { Name = "2User" }, new User() { Name = "3User" }, new User() { Name = "4User" } },
X = 123123,
- UnmanagedTypes = new() { SomePos = new Point(1, 2), SomeTime = DateTime.Parse("2022-02-22 12:34")}
+ UnmanagedTypes = new() { SomePos = new Point(1, 2), SomeTime = DateTime.Parse("2022-02-22 12:34")},
+ RuntimeValue = containerType,
+ GenericRuntimeValue = new Generic() { Prop = new[] { containerType, new ContainingClass() { RuntimeValue = new RuntimeTypeTestB(12) { SomeValue = 12345} }} }
};
//var sut = new SinglePropTest
@@ -69,13 +77,13 @@ static void Main(string[] args)
//};
using (var ms = new FileStream("sut.save", FileMode.OpenOrCreate))
{
- using var bw = new BinaryWriter(ms);
+ using var bw = new NoosonBinaryWriter(ms);
sutMessage.Serialize(bw);
}
using (var ms = new FileStream("sut.save", FileMode.Open))
{
- using var br = new BinaryReader(ms);
+ using var br = new NoosonBinaryReader(ms);
var sutMessageDes = SUTMessage.Deserialize(br);
if (sutMessageDes == sutMessage)
diff --git a/DEMO/RuntimeTypeTest.cs b/DEMO/RuntimeTypeTest.cs
new file mode 100644
index 0000000..aec7fd7
--- /dev/null
+++ b/DEMO/RuntimeTypeTest.cs
@@ -0,0 +1,269 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using NonSucking.Framework.Serialization;
+
+namespace DEMO;
+
+[Nooson]
+public partial class RuntimeTypeTestBase : IEquatable
+{
+ public int SomeValue { get; set; }
+
+ public bool Equals(RuntimeTypeTestBase? other)
+ {
+ if (ReferenceEquals(null, other))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ if (other.GetType() != GetType())
+ return false;
+
+ return SomeValue == other.SomeValue;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj.GetType() != this.GetType())
+ {
+ return false;
+ }
+
+ return Equals((RuntimeTypeTestBase)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return SomeValue;
+ }
+}
+
+[Nooson]
+public partial class RuntimeTypeTestA : RuntimeTypeTestBase, IEquatable
+{
+ public RuntimeTypeTestA(string someOtherValue)
+ {
+ SomeOtherValue = someOtherValue;
+ }
+
+ public string SomeOtherValue { get; }
+
+ public bool Equals(RuntimeTypeTestA? other)
+ {
+ if (ReferenceEquals(null, other))
+ {
+ return false;
+ }
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+ return SomeOtherValue == other.SomeOtherValue && SomeValue == other.SomeValue;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj.GetType() != this.GetType())
+ {
+ return false;
+ }
+
+ return Equals((RuntimeTypeTestA)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return SomeOtherValue.GetHashCode();
+ }
+}
+
+[Nooson]
+public partial class Generic : IEquatable>
+{
+ [NoosonDynamicType(Resolver = typeof(UnknownTypeResolver))]
+ public T[] Prop { get; set; }
+
+
+ public bool Equals(Generic? other)
+ {
+ if (other is null)
+ return false;
+ return Prop.SequenceEqual(other.Prop);
+ }
+}
+
+[Nooson]
+public partial class RuntimeTypeTestB : RuntimeTypeTestBase, IEquatable
+{
+ public RuntimeTypeTestB(int someOtherValue)
+ {
+ SomeOtherValue = someOtherValue;
+ }
+
+ public int SomeOtherValue { get; }
+
+ public bool Equals(RuntimeTypeTestB? other)
+ {
+ if (ReferenceEquals(null, other))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return SomeOtherValue == other.SomeOtherValue && SomeValue == other.SomeValue;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj.GetType() != this.GetType())
+ {
+ return false;
+ }
+
+ return Equals((RuntimeTypeTestB)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return SomeOtherValue;
+ }
+}
+
+[Nooson]
+public partial class RuntimeTypeTestC : RuntimeTypeTestBase
+{
+ public RuntimeTypeTestC(string someOtherValue)
+ {
+ SomeOtherValue = someOtherValue;
+ }
+
+ public string SomeOtherValue { get; }
+}
+
+[Nooson]
+public partial class ContainingClass : IEquatable
+{
+ [NoosonDynamicType(typeof(RuntimeTypeTestC), Resolver = typeof(Resolver))]
+ public RuntimeTypeTestBase RuntimeValue { get; set; }
+
+ public bool Equals(ContainingClass? other)
+ {
+ if (ReferenceEquals(null, other))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ return RuntimeValue.Equals(other.RuntimeValue);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj.GetType() != this.GetType())
+ {
+ return false;
+ }
+
+ return Equals((ContainingClass)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return RuntimeValue.GetHashCode();
+ }
+}
+
+internal class UnknownTypeResolver : NoosonRuntimeTypeResolver
+{
+ public static UnknownTypeResolver Instance = new();
+ protected override Type ResolveType(string identifier)
+ {
+ return Type.GetType(identifier) ?? throw new ArgumentException();
+ }
+
+ protected override string SolveType(Type type)
+ {
+ return type.AssemblyQualifiedName ?? throw new ArgumentException();
+ }
+}
+
+
+partial struct SomeWrapperStruct
+{
+ public SomeWrapperStruct(T identifier)
+ {
+ Identifier = identifier;
+ }
+
+ public T Identifier { get; }
+}
+internal class Resolver : NoosonRuntimeTypeResolver>
+{
+ public static Resolver Instance = new();
+ protected override Type ResolveType(SomeWrapperStruct identifier)
+ {
+ return Type.GetType(identifier.Identifier?.ToString() ?? "") ?? throw new ArgumentOutOfRangeException();
+ }
+
+ protected override SomeWrapperStruct SolveType(Type type)
+ {
+ var val = type.AssemblyQualifiedName
+ ?? type.FullName
+ ?? throw new ArgumentOutOfRangeException();
+ return new SomeWrapperStruct((T)(object)val);
+ }
+}
\ No newline at end of file
diff --git a/DEMO/SUTMessage.cs b/DEMO/SUTMessage.cs
index 561d987..2c707d3 100644
--- a/DEMO/SUTMessage.cs
+++ b/DEMO/SUTMessage.cs
@@ -56,17 +56,17 @@ public ComplainBaseWithCtor(string complain, string valueNever)
this.valueNever = valueNever;
}
- public void FirstSerialize(BinaryWriter bw)
+ public void FirstSerialize(IBinaryWriter bw)
{
bw.Write(FirstCustom);
}
- public static void FirstSerialize(BinaryWriter bw, string first)
+ public static void FirstSerialize(IBinaryWriter bw, string first)
{
bw.Write(first);
}
- public static string FirstDeserialize(BinaryReader br)
+ public static string FirstDeserialize(IBinaryReader br)
{
return br.ReadString();
}
@@ -111,6 +111,9 @@ public partial class SUTMessage : IEquatable
public int X { get; set; }
public int countPositions { get; set; }
+
+ public ContainingClass RuntimeValue { get; set; }
+ public Generic GenericRuntimeValue { get; set; }
[NoosonInclude]
private int randomField = new Random().Next();
@@ -147,6 +150,7 @@ public static IUser DeserializeIUser(BinaryReader br)
}
public override bool Equals(object obj) => Equals(obj as SUTMessage);
+
public bool Equals(SUTMessage other) =>
other is not null
&& Positions.SequenceEqual(other.Positions)
@@ -170,7 +174,9 @@ other is not null
&& countPositions == other.countPositions
&& randomField == other.randomField
&& forCtor == other.forCtor
- && UnmanagedTypes == other.UnmanagedTypes;
+ && UnmanagedTypes == other.UnmanagedTypes
+ && RuntimeValue.Equals(other.RuntimeValue)
+ && GenericRuntimeValue.Equals(other.GenericRuntimeValue);
/*
EqualityComparer>.Default.Equals(UsersList, other.UsersList) && EqualityComparer>.Default.Equals(ComplainsBases, other.ComplainsBases) && EqualityComparer>.Default.Equals(Countings, other.Countings) && EqualityComparer>.Default.Equals(CountingDic, other.CountingDic) && EqualityComparer>.Default.Equals(ReadOnlyCountings, other.ReadOnlyCountings) && EqualityComparer>.Default.Equals(ReadOnlyCountingsButSetable, other.ReadOnlyCountingsButSetable)
*/
@@ -199,6 +205,8 @@ public override int GetHashCode()
hash.Add(countPositions);
hash.Add(randomField);
hash.Add(forCtor);
+ hash.Add(RuntimeValue);
+ hash.Add(GenericRuntimeValue);
return hash.ToHashCode();
}
@@ -211,25 +219,30 @@ public class User : IUser, IEquatable
public int DoSomething()
=> 12;
- public void Serialize(BinaryWriter writer)
+ public void Serialize(T writer)
+ where T : IBinaryWriter
{
writer.Write(Name);
}
- public void SerializeMe(BinaryWriter bw)
+ public void SerializeMe(T bw)
+ where T : IBinaryWriter
{
bw.Write(Name);
}
- public static void SerializeMe(BinaryWriter bw, IUser user)
+ public static void SerializeMe(T bw, IUser user)
+ where T : IBinaryWriter
{
bw.Write(user.Name);
}
- public static User Deserialize(BinaryReader reader)
+ public static User Deserialize(T reader)
+ where T : IBinaryReader
{
return new User() { Name = reader.ReadString() };
}
- public static User DeserializeMe(BinaryReader reader)
+ public static User DeserializeMe(T reader)
+ where T : IBinaryReader
{
return new User() { Name = reader.ReadString() };
}
diff --git a/NonSucking.Framework.Extension/NonSucking.Framework.Extension.csproj b/NonSucking.Framework.Extension/NonSucking.Framework.Extension.csproj
index 59c9e80..b94fc6c 100644
--- a/NonSucking.Framework.Extension/NonSucking.Framework.Extension.csproj
+++ b/NonSucking.Framework.Extension/NonSucking.Framework.Extension.csproj
@@ -1,6 +1,6 @@
-
+
netcoreapp3.1;net6.0
false
diff --git a/NonSucking.Framework.Serialization.Advanced/AssemblyNameCache.cs b/NonSucking.Framework.Serialization.Advanced/AssemblyNameCache.cs
new file mode 100644
index 0000000..1a7596b
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/AssemblyNameCache.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class AssemblyNameCache
+{
+ internal readonly struct Names
+ {
+ private readonly string? serializeName;
+ private readonly string? deserializeName;
+ private readonly string? deserializeOutName;
+ private const string DefaultSerializeName = "Serialize";
+ private const string DefaultDeserializeName = "Deserialize";
+ public Names(string? serializeName, string? deserializeName, string? deserializeOutName)
+ {
+ this.serializeName = serializeName;
+ this.deserializeName = deserializeName;
+ this.deserializeOutName = deserializeOutName;
+ }
+
+ public static Names Combine(Names a, Names b)
+ {
+ return new Names(a.serializeName ?? b.serializeName, a.deserializeName ?? b.deserializeName, a.deserializeOutName ?? b.deserializeOutName);
+ }
+
+ public string SerializeName => serializeName ?? DefaultSerializeName;
+
+ public string DeserializeName => deserializeName ?? DefaultDeserializeName;
+ public string DeserializeOutName => deserializeOutName ?? DefaultDeserializeName;
+ }
+ private static readonly Dictionary AssemblyNameMap = new();
+
+ internal static T? FirstOrNull(IEnumerable enumerable, Func predicate)
+ where T : struct
+ {
+ foreach (var v in enumerable)
+ {
+ if (predicate(v))
+ return v;
+ }
+ return null;
+ }
+ private static Names GetAssemblyConfig(Assembly assembly)
+ {
+ var assemblyAttr = assembly.GetCustomAttributesData().FirstOrDefault(
+ x => x.AttributeType.FullName == Consts.NoosonConfigurationAttribute);
+ if (assemblyAttr is null)
+ return new Names(null, null, null);
+ var deserializeName = FirstOrNull(assemblyAttr.NamedArguments,
+ argument => argument.MemberName == "NameOfStaticDeserializeWithCtor")?.TypedValue.Value?.ToString();
+ var deserializeOutName = FirstOrNull(assemblyAttr.NamedArguments,
+ argument => argument.MemberName == "NameOfStaticDeserializeWithOutParams")?.TypedValue.Value?.ToString();
+ var serializeName = FirstOrNull(assemblyAttr.NamedArguments,
+ argument => argument.MemberName == "NameOfSerialize")?.TypedValue.Value?.ToString();
+ return new Names(serializeName, deserializeName, deserializeOutName);
+ }
+ public static Names ResolveAssemblyConfig(Assembly assembly)
+ {
+#if NET6_0_OR_GREATER
+ ref var names = ref CollectionsMarshal.GetValueRefOrAddDefault(AssemblyNameMap, assembly, out var exists);
+ if (!exists)
+ {
+ names = GetAssemblyConfig(assembly);
+ }
+ return names;
+#else
+ if (!AssemblyNameMap.TryGetValue(assembly, out var names))
+ {
+ names = GetAssemblyConfig(assembly);
+ AssemblyNameMap.Add(assembly, names);
+ }
+ return names;
+#endif
+ }
+
+
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/BaseGenerator.cs b/NonSucking.Framework.Serialization.Advanced/BaseGenerator.cs
new file mode 100644
index 0000000..c82d69b
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/BaseGenerator.cs
@@ -0,0 +1,264 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class BaseGenerator
+{
+ public BaseGenerator(BaseGeneratorContext currentContext)
+ {
+ CurrentContext = currentContext;
+ }
+
+ public void PushContext(BaseGeneratorContext context)
+ {
+ Contexts.Push(context);
+ CurrentContext = context;
+ }
+
+ public BaseGeneratorContext PopContext()
+ {
+ var popped = Contexts.Pop();
+ CurrentContext = Contexts.Peek();
+ return popped;
+ }
+ public void EmitCall(MethodInfo method)
+ {
+ Il.Emit(method.IsStatic ? OpCodes.Call : OpCodes.Callvirt, method);
+ }
+ public void EmitIfInverted(OpCode invertedBranch, Action? ifScope, Action? elseScope)
+ {
+ EmitIf(InvertBranch(invertedBranch), elseScope, ifScope);
+ }
+
+ public void EmitIf(OpCode branchCode, Action? ifScope,
+ Action? elseScope)
+ {
+ var elseLabel = elseScope is null ? (Label?)null : Il.DefineLabel();
+ var endLabel = Il.DefineLabel();
+ Il.Emit(branchCode, elseLabel ?? endLabel);
+ ifScope?.Invoke(this);
+ if (elseLabel is not null)
+ {
+ if (ifScope != null)
+ Il.Emit(OpCodes.Br, endLabel);
+ Il.MarkLabel(elseLabel.Value);
+ elseScope?.Invoke(this);
+ }
+ Il.MarkLabel(endLabel);
+ }
+ public void EmitIf(Action? elseScope, params (OpCode branchCode, Action check, Action body)[] ifs)
+ {
+ if (ifs.Length == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(ifs));
+ }
+
+ var nested = elseScope;
+
+ for (int i = ifs.Length - 1; i >= 0; i--)
+ {
+ var (elifBranchCode, elifCheck, elifBody) = ifs[i];
+ var nestedCopy = nested;
+ nested = gen =>
+ {
+ elifCheck(gen);
+ gen.EmitIf(elifBranchCode, elifBody, nestedCopy);
+ };
+ }
+
+ nested!(this);
+ }
+ public void EmitIf(OpCode branchCode, Action? ifScope,
+ Action? elseScope, params (OpCode branchCode, Action check, Action body)[] elseIfs)
+ {
+ var nested = elseScope;
+
+ for (int i = elseIfs.Length - 1; i >= 0; i--)
+ {
+ var (elifBranchCode, elifCheck, elifBody) = elseIfs[i];
+ var nestedCopy = nested;
+ nested = gen =>
+ {
+ elifCheck(gen);
+ gen.EmitIf(elifBranchCode, elifBody, nestedCopy);
+ };
+ }
+
+ EmitIf(branchCode, ifScope,
+ gen =>
+ {
+ nested?.Invoke(gen);
+ }
+ );
+ }
+
+ public void EmitFor(Func init, Action check, Action increment, Action body)
+ {
+ var forLoopStart = Il.DefineLabel();
+ var forLoopEnd = Il.DefineLabel();
+ var variable = init(this);
+ Il.MarkLabel(forLoopStart);
+ check(this, variable);
+ Il.Emit(OpCodes.Brfalse, forLoopEnd);
+ body(this, forLoopStart, forLoopEnd, variable);
+ increment(this, variable);
+ Il.Emit(OpCodes.Br, forLoopStart);
+ Il.MarkLabel(forLoopEnd);
+ }
+
+ public void EmitWhile(Action check, Action body)
+ {
+ var loopStart = Il.DefineLabel();
+ var loopEnd = Il.DefineLabel();
+ Il.MarkLabel(loopStart);
+ check(this);
+ Il.Emit(OpCodes.Brfalse, loopEnd);
+ body(this, loopStart, loopEnd);
+ Il.Emit(OpCodes.Br, loopStart);
+ Il.MarkLabel(loopEnd);
+ }
+
+ public void EmitDo(Action check, Action body)
+ {
+ var loopStart = Il.DefineLabel();
+ var loopEnd = Il.DefineLabel();
+ Il.MarkLabel(loopStart);
+ body(this, loopStart, loopEnd);
+ check(this);
+ Il.Emit(OpCodes.Brtrue, loopStart);
+ Il.MarkLabel(loopEnd);
+ }
+
+ public void EmitTryCatchFinally(Action body, Action? finalBlock, params (Type? filter, Action bodyGenerator)[]? exceptionBlocks)
+ {
+ var exBlock = Il.BeginExceptionBlock();
+ var exitLabel = Il.DefineLabel();
+ body(this, exBlock);
+ if (exceptionBlocks is not null && exceptionBlocks.Length > 0)
+ {
+ if (exceptionBlocks.Length > 1)
+ Il.BeginExceptFilterBlock();
+ foreach (var (filter, bodyGenerator) in exceptionBlocks)
+ {
+ Il.BeginCatchBlock(filter);
+ bodyGenerator(this, exBlock);
+ }
+ if (finalBlock is not null)
+ Il.BeginFinallyBlock();
+ }
+ else
+ Il.BeginFinallyBlock();
+
+ finalBlock?.Invoke(this, exitLabel);
+
+ Il.MarkLabel(exitLabel);
+ Il.EndExceptionBlock();
+ }
+
+ public void EmitIncrement()
+ {
+ Il.Emit(OpCodes.Ldc_I4_1);
+ Il.Emit(OpCodes.Add);
+ }
+
+ public void EmitIncrement(LocalBuilder var)
+ {
+ Il.Emit(OpCodes.Ldloc, var);
+ EmitIncrement();
+ Il.Emit(OpCodes.Stloc, var);
+ }
+
+ public void EmitForEach(Type enumerableType, Action getEnumerable, Action body)
+ {
+ var getEnumerator = Helper.GetMethodIncludingInterfaces(enumerableType, "GetEnumerator", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
+ if (getEnumerator is null)
+ throw new ArgumentException($"Not a valid enumerable type(no GetEnumerator found): '{enumerableType.FullName}'");
+ var enumeratorType = getEnumerator.ReturnType;
+ var moveNext = Helper.GetMethodIncludingInterfaces(enumeratorType, "MoveNext", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
+ if (moveNext is null || moveNext.ReturnType != typeof(bool))
+ throw new ArgumentException($"Not a valid enumerator type(no valid MoveNext returning bool found): '{enumeratorType.FullName}'");
+ var getCurrent = Helper.GetPropertyIncludingInterfaces(enumeratorType, "Current", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)?.GetMethod;
+ if (getCurrent is null)
+ throw new ArgumentException(
+ $"Not a valid enumerator(no valid Current get method found): '{enumeratorType.FullName}'");
+ var dispose = Helper.GetMethodInInterfaces(enumeratorType, "Dispose", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
+ if (dispose is null)
+ throw new ArgumentException(
+ $"Not a valid enumerator(no valid Dispose method found): '{enumeratorType.FullName}'");
+ var enumeratorVariable = Il.DeclareLocal(enumeratorType);
+ var currentVariable = Il.DeclareLocal(getCurrent.ReturnType);
+ getEnumerable(this);
+ Il.Emit(OpCodes.Callvirt, getEnumerator);
+ Il.Emit(OpCodes.Stloc, enumeratorVariable);
+ EmitTryCatchFinally(
+ (_, _) =>
+ {
+ EmitWhile(
+ gen =>
+ {
+ gen.EmitLoadLocRef(enumeratorVariable);
+ gen.Il.Emit(OpCodes.Callvirt, moveNext);
+ },
+ (gen, loopStart, loopEnd) =>
+ {
+ gen.EmitLoadLocRef(enumeratorVariable);
+ gen.Il.Emit(OpCodes.Callvirt, getCurrent);
+ gen.Il.Emit(OpCodes.Stloc, currentVariable);
+ body(gen, loopStart, loopEnd, currentVariable);
+ }
+ );
+ },
+ (gen, exitLabel) =>
+ {
+ bool isValueType = enumeratorVariable.LocalType.IsValueType;
+ if (!isValueType)
+ {
+ gen.EmitLoadLocRef(enumeratorVariable);
+ gen.Il.Emit(OpCodes.Brfalse, exitLabel);
+ }
+
+ gen.EmitLoadLocRef(enumeratorVariable);
+
+ if (isValueType)
+ {
+ gen.Il.Emit(OpCodes.Constrained, enumeratorVariable.LocalType);
+ }
+ gen.Il.Emit(OpCodes.Callvirt, dispose);
+ });
+ }
+
+ private void EmitLoadLocRef(LocalBuilder var)
+ {
+ Il.Emit(var.LocalType.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc, var);
+ }
+
+ public static MethodInfo GetDelegateInvoker(Type type)
+ {
+ var invoke = type.GetMethod("Invoke", BindingFlags.Public | BindingFlags.Instance);
+ if (invoke is null)
+ throw new ArgumentException($"No valid invoke method found for type: {type.FullName}");
+ return invoke;
+ }
+ private static OpCode InvertBranch(OpCode code)
+ {
+ if (code == OpCodes.Brfalse_S)
+ return OpCodes.Brtrue_S;
+ if (code == OpCodes.Brfalse)
+ return OpCodes.Brtrue;
+ if (code == OpCodes.Brtrue_S)
+ return OpCodes.Brfalse_S;
+ if (code == OpCodes.Brtrue)
+ return OpCodes.Brfalse;
+ throw new ArgumentOutOfRangeException(nameof(code));
+ }
+
+ public Dictionary ContextMap { get; } = new();
+ public BaseGeneratorContext CurrentContext { get; private set; }
+ public ILGenerator Il => CurrentContext.Il;
+ public Stack Contexts { get; } = new();
+ public bool IsTopLevel => CurrentContext.IsTopLevel;
+
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/BaseGeneratorContext.cs b/NonSucking.Framework.Serialization.Advanced/BaseGeneratorContext.cs
new file mode 100644
index 0000000..f051066
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/BaseGeneratorContext.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+public abstract class BaseGeneratorContext
+{
+ public enum ContextType
+ {
+ Serialize,
+ Deserialize
+ }
+ public readonly TypeBuilder? typeBuilder;
+ public class TypeContext : IEquatable
+ {
+ public TypeContext(Type type, NullabilityInfo nullabilityInfo)
+ {
+ Type = type;
+ NullabilityInfo = nullabilityInfo;
+ }
+
+ public Type Type { get; }
+ public NullabilityInfo NullabilityInfo { get; }
+
+ public bool Equals(TypeContext? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Type == other.Type && Equals(NullabilityInfo, other.NullabilityInfo);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != GetType()) return false;
+ return Equals((TypeContext)obj);
+ }
+
+ private static bool Equals(NullabilityInfo a, NullabilityInfo b)
+ {
+ if (a.Type != b.Type ||
+ a.ReadState != b.ReadState ||
+ a.WriteState != b.WriteState)
+ return false;
+
+ if (a.ElementType is not null && b.ElementType is not null)
+ {
+ if (!Equals(a.ElementType, b.ElementType))
+ return false;
+ }
+ else if(a.ElementType != b.ElementType)
+ {
+ return false;
+ }
+
+ if (a.GenericTypeArguments.Length != b.GenericTypeArguments.Length)
+ return false;
+
+ for (int i = 0; i < a.GenericTypeArguments.Length; i++)
+ {
+ if (!Equals(a.GenericTypeArguments[i], b.GenericTypeArguments[i]))
+ return false;
+ }
+
+ return true;
+ }
+
+ private static int GetHashCode(NullabilityInfo a)
+ {
+ return HashCode.Combine(a.Type.GetHashCode(), a.ReadState.GetHashCode(), a.WriteState.GetHashCode());
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Type.GetHashCode(), GetHashCode(NullabilityInfo));
+ }
+ }
+ public BaseGeneratorContext(TypeContext valueType, ContextType contextType,
+ Action getReaderWriter, bool isTopLevel, MethodBuilder method)
+ {
+ if (contextType != ContextType.Serialize && contextType != ContextType.Deserialize)
+ throw new ArgumentOutOfRangeException(nameof(contextType));
+ ValueType = valueType;
+ Type = contextType;
+ GetReaderWriter = getReaderWriter;
+ IsTopLevel = isTopLevel;
+ Method = method;
+ Il = Method.GetILGenerator();
+ }
+ public BaseGeneratorContext(Type readerWriterType, TypeContext valueType, ContextType contextType, Action getReaderWriter, bool isTopLevel)
+ {
+ if (contextType != ContextType.Serialize && contextType != ContextType.Deserialize)
+ throw new ArgumentOutOfRangeException(nameof(contextType));
+
+ ValueType = valueType;
+ Type = contextType;
+ GetReaderWriter = getReaderWriter;
+ IsTopLevel = isTopLevel;
+ var methodName = contextType == ContextType.Serialize ? "Serialize" : "Deserialize";
+ var retType = contextType == ContextType.Serialize ? null : valueType.Type;
+ var paramTypes = contextType == ContextType.Serialize
+ ? new[] { readerWriterType, valueType.Type }
+ : new[] { readerWriterType };
+
+ var assemblyName = new AssemblyName($"{valueType.Type.ToAssemblyNameString()}{readerWriterType}");
+ var assemblyBuilder =
+ AssemblyBuilder.DefineDynamicAssembly(assemblyName,
+ AssemblyBuilderAccess.Run);
+ // Add IgnoresAccessChecksTo attribute
+
+ var ignoreAccessChecksToCtor = typeof(IgnoresAccessChecksToAttribute).GetConstructor(new[] { typeof(string) })
+ ?? throw new InvalidProgramException(
+ $"{nameof(IgnoresAccessChecksToAttribute)} constructor with string argument is missing!");
+
+ var entryAssemblyName = Assembly.GetEntryAssembly()?.GetName().Name
+ ?? throw new InvalidOperationException("Could not resolve entry assembly name");
+ var callingAssemblyName = Assembly.GetCallingAssembly().GetName().Name
+ ?? throw new InvalidOperationException("Could not resolve calling assembly name");
+
+ var ignoresAccessChecksTo = new CustomAttributeBuilder
+ (
+ ignoreAccessChecksToCtor,
+ new object[] { entryAssemblyName }
+ );
+
+ assemblyBuilder.SetCustomAttribute(ignoresAccessChecksTo);
+ ignoresAccessChecksTo = new CustomAttributeBuilder
+ (
+ ignoreAccessChecksToCtor,
+ new object[] { callingAssemblyName }
+ );
+
+ assemblyBuilder.SetCustomAttribute(ignoresAccessChecksTo);
+ var mod = assemblyBuilder.DefineDynamicModule(assemblyName.Name!);
+ var typeBuilderLocal = mod.DefineType("SerializeStuff");
+ typeBuilder = typeBuilderLocal;
+ Method = typeBuilderLocal.DefineMethod(methodName,
+ MethodAttributes.Public | MethodAttributes.Static,
+ CallingConventions.Standard,
+ retType, paramTypes);
+ Il = Method.GetILGenerator();
+ AssemblyBuilder = assemblyBuilder;
+ }
+public AssemblyBuilder AssemblyBuilder { get; }
+ public Type Build()
+ {
+ if (typeBuilder is null)
+ throw new InvalidOperationException("Cannot Build type for already existent method");
+ return typeBuilder.CreateType();
+ }
+
+ public Action GetReaderWriter { get; }
+ public Action? GetValue { get; set; }
+ public Action? GetValueRef { get; set; }
+ public Action? SetValue { get; set; }
+
+ public ILGenerator Il { get; }
+
+ public MethodBuilder Method { get; }
+
+ public TypeContext ValueType { get; }
+ public ContextType Type { get; }
+ public bool IsTopLevel { get; }
+
+ public abstract BaseGeneratorContext SubContext(TypeContext valueType, bool? isTopLevel);
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Consts.cs b/NonSucking.Framework.Serialization.Advanced/Consts.cs
new file mode 100644
index 0000000..3b45a9b
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Consts.cs
@@ -0,0 +1,14 @@
+namespace NonSucking.Framework.Serialization.Advanced;
+
+public static class Consts
+{
+ internal const string NoosonNamespace = "NonSucking.Framework.Serialization";
+ internal const string NoosonConfigurationAttribute = $"{NoosonNamespace}.NoosonConfigurationAttribute";
+ internal const string NoosonCustomAttribute = $"{NoosonNamespace}.NoosonCustomAttribute";
+ internal const string NoosonIgnoreAttribute = $"{NoosonNamespace}.NoosonIgnoreAttribute";
+ internal const string NoosonIncludeAttribute = $"{NoosonNamespace}.NoosonIncludeAttribute";
+ internal const string NoosonOrderAttribute = $"{NoosonNamespace}.NoosonOrderAttribute";
+ internal const string NoosonParameterAttribute = $"{NoosonNamespace}.NoosonParameterAttribute";
+ internal const string NoosonPreferredCtorAttribute = $"{NoosonNamespace}.NoosonPreferredCtorAttribute";
+ internal const string LocalVariableSuffix = "_️"; //VARIATION SELECTOR-16 _, looks better and provides better uniqueness
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/DeserializeGenerating.cs b/NonSucking.Framework.Serialization.Advanced/DeserializeGenerating.cs
new file mode 100644
index 0000000..53c8c42
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/DeserializeGenerating.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+public static class DeserializeGenerating
+{
+ public static MethodInfo GenerateDeserialize(FieldInfo prop)
+ {
+ return GenerateDeserialize(prop.FieldType, NullabilityHelper.Context.Create(prop));
+ }
+
+ public static MethodInfo GenerateDeserialize(PropertyInfo prop)
+ {
+ return GenerateDeserialize(prop.PropertyType, NullabilityHelper.Context.Create(prop));
+ }
+
+
+ public static MethodInfo GenerateDeserialize(Type type, bool isNullable)
+ {
+ return GenerateDeserialize(type, NullabilityHelper.CreateNullable(type, isNullable));
+ }
+
+ private static MethodInfo GenerateDeserialize(Type type, NullabilityInfo nullability)
+ {
+ var gen = new DeserializeGenerator(typeof(TReader), TypeNameCache.GetTypeConfig(type),
+ new BaseGeneratorContext.TypeContext(type, nullability),
+ BaseGeneratorContext.ContextType.Deserialize);
+ var generatedContext = gen.CurrentContext;
+
+ var generatedType = generatedContext.Build();
+
+ return generatedType.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static)!;
+ }
+ public static Delegate GenerateDeserializeDelegate(Type type, bool isNullable)
+ {
+ var m = GenerateDeserialize(type, isNullable);
+ var reader = Expression.Parameter(typeof(TReader), "reader");
+ return Expression.Lambda(Expression.Call(m, reader), reader).Compile();
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/DeserializeGenerator.cs b/NonSucking.Framework.Serialization.Advanced/DeserializeGenerator.cs
new file mode 100644
index 0000000..0f7ccd5
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/DeserializeGenerator.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class DeserializeGenerator : BaseGenerator
+{
+ public DeserializeGenerator(Type readerType, TypeNameCache.TypeConfig typeConfig, BaseGeneratorContext.TypeContext valueType, BaseGeneratorContext.ContextType contextType)
+ : base(new DeserializeGeneratorContext(readerType, valueType, gen => gen.Emit(OpCodes.Ldarg_0), true))
+ {
+ if (contextType == BaseGeneratorContext.ContextType.Serialize)
+ {
+ CurrentContext.GetValue = gen => gen.Emit(OpCodes.Ldarg_1);
+ CurrentContext.GetValueRef = valueType.Type.IsValueType
+ ? gen => gen.Emit(OpCodes.Ldarga, (short)1)
+ : CurrentContext.GetValue;
+ }
+ else
+ {
+ var resVal = CurrentContext.Il.DeclareLocal(valueType.Type);
+ CurrentContext.SetValue = gen => gen.Emit(OpCodes.Stloc, resVal);
+ CurrentContext.GetValue = gen => gen.Emit(OpCodes.Ldloc, resVal);
+ CurrentContext.GetValueRef = valueType.Type.IsValueType
+ ? gen => gen.Emit(OpCodes.Ldloca, resVal)
+ : CurrentContext.GetValue;
+ }
+ PushContext(CurrentContext);
+ if (!Create(typeConfig))
+ {
+ throw new TypeSerializerException($"Unable to create dynamic deserializer for type config: {typeConfig}");
+ }
+ }
+
+ public bool GenerateDeserialize(DeserializeGeneratorContext context, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ ContextMap.TryAdd(context.ValueType, context);
+ PushContext(context);
+ ContextMap.TryAdd(context.ValueType, context);
+
+ var res = CustomMethodCallSerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ NullableSerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ !IsTopLevel && RecursiveCallSerializer.Deserialize(this, typeConfig) ||
+ SpecialTypeSerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ EnumSerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ KnownSimpleTypeSerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ UnmanagedTypeSerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ MethodCallSerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ ArraySerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ ListSerializer.Deserialize(this, typeConfig, baseRecursionDepth) ||
+ PublicPropertySerializer.Deserialize(this, typeConfig, baseRecursionDepth);
+
+ _ = PopContext();
+ return res;
+ }
+
+ private bool Create(TypeNameCache.TypeConfig typeConfig)
+ {
+ var res = GenerateDeserialize(CurrentContext, typeConfig);
+ if (!res)
+ return false;
+ CurrentContext.GetValue!(Il);
+ Il.Emit(OpCodes.Ret);
+ while (Contexts.Count > 1)
+ _ = PopContext();
+ return res;
+ }
+
+ public new DeserializeGeneratorContext CurrentContext => (DeserializeGeneratorContext)base.CurrentContext;
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/DeserializeGeneratorContext.cs b/NonSucking.Framework.Serialization.Advanced/DeserializeGeneratorContext.cs
new file mode 100644
index 0000000..4974cbf
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/DeserializeGeneratorContext.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+public class DeserializeGeneratorContext : BaseGeneratorContext
+{
+ public DeserializeGeneratorContext(Type readerType, TypeContext valueType, Action getReaderWriter, bool isTopLevel, MethodBuilder method)
+ : base(valueType, ContextType.Deserialize, getReaderWriter, isTopLevel, method)
+ {
+ ReaderType = readerType;
+ }
+
+ public DeserializeGeneratorContext(Type readerType, TypeContext valueType, Action getReaderWriter, bool isTopLevel)
+ : base(readerType, valueType, ContextType.Deserialize, getReaderWriter, isTopLevel)
+ {
+ ReaderType = readerType;
+ }
+
+ public Type ReaderType { get; }
+
+ public override DeserializeGeneratorContext SubContext(TypeContext valueType, bool? isTopLevel)
+ {
+ return new DeserializeGeneratorContext(ReaderType, valueType, GetReaderWriter, isTopLevel ?? IsTopLevel, Method);
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Helper.cs b/NonSucking.Framework.Serialization.Advanced/Helper.cs
new file mode 100644
index 0000000..126943a
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Helper.cs
@@ -0,0 +1,292 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+public static class Helper
+{
+ private static readonly MethodInfo GenericMethodUnmanagedCheck =
+ typeof(RuntimeHelpers).GetMethod(nameof(RuntimeHelpers.IsReferenceOrContainsReferences), BindingFlags.Public | BindingFlags.Static)
+ ?? throw new InvalidProgramException($"{nameof(RuntimeHelpers)} does not contain a {nameof(RuntimeHelpers.IsReferenceOrContainsReferences)} method");
+ public static bool IsUnmanaged(this Type t)
+ {
+ try
+ {
+ return !(bool)GenericMethodUnmanagedCheck.MakeGenericMethod(t).Invoke(null,null)!;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+ internal static bool ForAll(this IEnumerable list, IList second, Func check)
+ {
+ int index = 0;
+ foreach (var item in list)
+ {
+ if (second.Count > index)
+ {
+ if (!check(item, second[index]))
+ return false;
+ }
+ else
+ return false;
+ index++;
+ }
+
+ return second.Count == index;
+ }
+ public static bool IsAssignableFromOpenGeneric(this Type type, Type assignTo)
+ {
+ return IsAssignableToOpenGeneric(assignTo, type);
+ }
+ public static bool IsAssignableToOpenGeneric(this Type type, Type assignTo)
+ {
+ var interfaces = type.GetInterfaces();
+
+ foreach (var i in interfaces)
+ {
+ if (i.IsGenericType && i.GetGenericTypeDefinition() == assignTo)
+ return true;
+ }
+
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == assignTo)
+ return true;
+
+ return type.BaseType is { } baseType && IsAssignableToOpenGeneric(baseType, assignTo);
+ }
+
+ public static Type? GetOpenGenericType(this Type type, Type generic)
+ {
+ Type? currentType = type;
+ while (currentType is not null)
+ {
+ var interfaces = currentType.GetInterfaces();
+
+ foreach (var i in interfaces)
+ {
+ if (i.IsGenericType && i.GetGenericTypeDefinition() == generic) return i;
+ }
+
+ if (currentType.IsGenericType && currentType.GetGenericTypeDefinition() == generic)
+ return currentType;
+
+ currentType = currentType.BaseType;
+ }
+
+ return null;
+ }
+
+ public static string ToAssemblyNameString(this Type type)
+ {
+ return type.ToString().Replace(',', '.');
+ }
+ public static string GetSpecialTypeReadMethodName(TypeCode code)
+ {
+ switch (code)
+ {
+ case TypeCode.Boolean:
+ return "ReadBoolean";
+ case TypeCode.Char:
+ return "ReadChar";
+ case TypeCode.SByte:
+ return "ReadSByte";
+ case TypeCode.Byte:
+ return "ReadByte";
+ case TypeCode.Int16:
+ return "ReadInt16";
+ case TypeCode.UInt16:
+ return "ReadUInt16";
+ case TypeCode.Int32:
+ return "ReadInt32";
+ case TypeCode.UInt32:
+ return "ReadUInt32";
+ case TypeCode.Int64:
+ return "ReadInt64";
+ case TypeCode.UInt64:
+ return "ReadUInt64";
+ case TypeCode.Single:
+ return "ReadSingle";
+ case TypeCode.Double:
+ return "ReadDouble";
+ case TypeCode.Decimal:
+ return "ReadDecimal";
+ case TypeCode.String:
+ return "ReadString";
+ case TypeCode.Empty:
+ case TypeCode.Object:
+ case TypeCode.DBNull:
+ case TypeCode.DateTime:
+ default:
+ break;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(code), code, null);
+ }
+ internal static MethodInfo? TryGetRead(DeserializeGenerator generator, Type type, string methodName)
+ {
+ var readerType = generator.CurrentContext.ReaderType;
+ return MethodResolver.GetBestMatch(readerType,
+ methodName,
+ BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
+ Type.EmptyTypes, type);
+ }
+ internal static MethodInfo? TryGetRead(DeserializeGenerator generator, Type type, TypeCode typeCode, out string methodName)
+ {
+ methodName = GetSpecialTypeReadMethodName(typeCode);
+ return TryGetRead(generator, type, methodName);
+ }
+ internal static MethodInfo? TryGetRead(DeserializeGenerator generator, Type type, out string methodName)
+ {
+ return TryGetRead(generator, type, Type.GetTypeCode(type), out methodName);
+ }
+
+ internal static MethodInfo? TryGetRead(DeserializeGenerator generator, Type type, TypeCode typeCode)
+ => TryGetRead(generator, type, typeCode, out _);
+
+ internal static MethodInfo? TryGetRead(DeserializeGenerator generator, Type type)
+ => TryGetRead(generator, type, out _);
+ internal static MethodInfo GetRead(DeserializeGenerator generator, Type type, TypeCode typeCode)
+ {
+ var readerType = generator.CurrentContext.ReaderType;
+ return TryGetRead(generator, type, typeCode, out var methodName)
+ ?? throw new NotSupportedException($"Reader {readerType} is missing {methodName}({type}) overload.");
+ }
+ internal static MethodInfo GetRead(DeserializeGenerator generator, Type type)
+ {
+ var readerType = generator.CurrentContext.ReaderType;
+ return TryGetRead(generator, type, out var methodName)
+ ?? throw new NotSupportedException($"Reader {readerType} is missing {methodName}({type}) overload.");
+ }
+ internal static MethodInfo GetRead(DeserializeGenerator generator, Type type, string methodName)
+ {
+ var readerType = generator.CurrentContext.ReaderType;
+ return TryGetRead(generator, type, methodName)
+ ?? throw new NotSupportedException($"Reader {readerType} is missing {methodName}({type}) overload.");
+ }
+
+
+ internal static MethodInfo? TryGetWrite(SerializeGenerator generator, Type type, string methodName = "Write")
+ {
+ var writerType = generator.CurrentContext.WriterType;
+ return MethodResolver.GetBestMatch(
+ writerType,
+ methodName,
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic,
+ new[] { type }, null);
+ }
+ internal static MethodInfo GetWrite(SerializeGenerator generator, Type type, string methodName = "Write")
+ {
+ var writerType = generator.CurrentContext.WriterType;
+ return TryGetWrite(generator, type, methodName)
+ ?? throw new NotSupportedException($"Writer {writerType} is missing {methodName}({type}) overload.");
+ }
+ internal static T? GetFirstMemberWithBase(Type? type,
+ Func predicate,
+ int maxRecursion = int.MaxValue,
+ int currentIteration = 0)
+ {
+ if (type is null)
+ return default;
+ if (currentIteration++ > maxRecursion)
+ return default;
+
+ foreach (var member in type.GetMembers())
+ {
+ if (member is T t && predicate(t))
+ return t;
+ }
+
+ return GetFirstMemberWithBase(type.BaseType, predicate, maxRecursion,
+ currentIteration);
+ }
+ internal static IEnumerable<(MemberInfo memberInfo, int depth)> GetMembersWithBase(Type? type,
+ int maxRecursion = int.MaxValue, int currentIteration = 0)
+ {
+ if (currentIteration++ > maxRecursion)
+ yield break;
+
+ if (type is null)
+ yield break;
+ foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
+ {
+ if (member.DeclaringType != type)
+ continue;
+ // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
+ switch (member.MemberType)
+ {
+ // Exclude CompilerGenerated EqualityContract from serialization process
+ case MemberTypes.Property when member.Name == "EqualityContract" && member.GetCustomAttribute() is not null:
+ continue;
+ case MemberTypes.Property:
+ if (member.GetCustomAttributesData().FirstOrDefault(x =>
+ x.AttributeType.FullName == Consts.NoosonIgnoreAttribute) is not null)
+ continue;
+ yield return (member, currentIteration);
+ break;
+ case MemberTypes.Field:
+ if (member.GetCustomAttributesData().FirstOrDefault(x =>
+ x.AttributeType.FullName == Consts.NoosonIncludeAttribute) is null)
+ continue;
+ yield return (member, currentIteration);
+ break;
+ }
+ }
+
+ foreach (var item in GetMembersWithBase(type.BaseType, maxRecursion, currentIteration))
+ {
+ yield return item;
+ }
+ }
+ internal static bool MatchIdentifierWithPropName(string identifier, string parameterName)
+ {
+ var index = identifier.IndexOf(Consts.LocalVariableSuffix, StringComparison.Ordinal);
+ if (index > -1)
+ identifier = identifier.Remove(index);
+ index = parameterName.IndexOf(Consts.LocalVariableSuffix, StringComparison.Ordinal);
+ if (index > -1)
+ parameterName = parameterName.Remove(index);
+
+ return char.ToLowerInvariant(identifier[0]) == char.ToLowerInvariant(parameterName[0])
+ && string.Equals(identifier[1..], parameterName[1..]);
+ }
+ internal static MethodInfo? GetMethodIncludingInterfaces(Type type, string name, BindingFlags bindingFlags)
+ {
+ var res = type.GetMethod(name, bindingFlags);
+ if (res is not null)
+ return res;
+
+ return GetMethodInInterfaces(type, name, bindingFlags);
+ }
+
+ internal static MethodInfo? GetMethodInInterfaces(Type type, string name, BindingFlags bindingFlags)
+ {
+ foreach (var i in type.GetInterfaces())
+ {
+ var res = i.GetMethod(name, bindingFlags);
+ if (res is not null)
+ return res;
+ }
+
+ return null;
+ }
+
+
+ internal static PropertyInfo? GetPropertyIncludingInterfaces(Type type, string name, BindingFlags bindingFlags)
+ {
+ var res = type.GetProperty(name, bindingFlags);
+ if (res is not null)
+ return res;
+ foreach (var i in type.GetInterfaces())
+ {
+ res = i.GetProperty(name, bindingFlags);
+ if (res is not null)
+ return res;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/IgnoresAccessChecksToAttribute.cs b/NonSucking.Framework.Serialization.Advanced/IgnoresAccessChecksToAttribute.cs
new file mode 100644
index 0000000..204f598
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/IgnoresAccessChecksToAttribute.cs
@@ -0,0 +1,15 @@
+// ReSharper disable once CheckNamespace
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public class IgnoresAccessChecksToAttribute : Attribute
+ {
+ public IgnoresAccessChecksToAttribute(string assemblyName)
+ {
+ AssemblyName = assemblyName;
+ }
+
+ // ReSharper disable once UnusedAutoPropertyAccessor.Global
+ public string AssemblyName { get; }
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/MethodResolver.cs b/NonSucking.Framework.Serialization.Advanced/MethodResolver.cs
new file mode 100644
index 0000000..5e844a1
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/MethodResolver.cs
@@ -0,0 +1,223 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Loader;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+
+internal class MethodResolver
+{
+ private static readonly Dictionary> RegisteredExtensionMethods = new();
+ private static readonly HashSet AlreadyRegistered = new();
+ private static readonly HashSet AlreadyRegisteredAssemblies = new();
+
+ readonly struct AssemblyNameKey : IEquatable
+ {
+ public AssemblyNameKey(AssemblyName assemblyName)
+ {
+ AssemblyName = assemblyName;
+ }
+
+ public AssemblyName AssemblyName { get; }
+
+ public bool Equals(AssemblyNameKey other)
+ {
+ return AssemblyName.FullName.Equals(other.AssemblyName.FullName);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is AssemblyNameKey other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return AssemblyName.FullName.GetHashCode();
+ }
+
+ public static implicit operator AssemblyNameKey(AssemblyName name)
+ {
+ return new(name);
+ }
+ }
+
+ static MethodResolver()
+ {
+ // Default resolve extension methods
+ AnalyzeExtensionMethodsRecurse(Assembly.GetCallingAssembly());
+ AnalyzeExtensionMethodsRecurse(Assembly.GetEntryAssembly());
+ }
+ internal static IEnumerable GetRegisteredExtensionMethods(string name)
+ {
+ return RegisteredExtensionMethods.TryGetValue(name, out var methods)
+ ? methods
+ : Array.Empty();
+ }
+
+ internal static void AnalyzeExtensionMethods(Type type)
+ {
+ if (!type.IsClass || !type.IsSealed || !type.IsAbstract || type.GetCustomAttribute() is null)
+ return;
+
+ if (!AlreadyRegistered.Add(type))
+ return;
+
+ var methods = type.GetMethods();
+ Array.Sort(methods, (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
+ var previousMethodName = "";
+ List resolvedMethods = null!;
+ foreach (var m in methods)
+ {
+ if (m.GetCustomAttribute() is null)
+ continue;
+ if (previousMethodName != m.Name)
+ {
+ ref var refList = ref CollectionsMarshal.GetValueRefOrAddDefault(RegisteredExtensionMethods, m.Name, out var exists);
+ if (exists)
+ resolvedMethods = refList!;
+ else
+ refList = resolvedMethods = new List();
+ previousMethodName = m.Name;
+ }
+
+ resolvedMethods.Add(m);
+ }
+ }
+
+ internal static void AnalyzeExtensionMethodsRecurse(Assembly? assembly)
+ {
+ if (assembly is null)
+ return;
+ AnalyzeExtensionMethods(assembly);
+ foreach (var referencedName in assembly.GetReferencedAssemblies())
+ {
+ if (AlreadyRegisteredAssemblies.Contains(referencedName))
+ continue;
+ var referencedAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(referencedName);
+ AnalyzeExtensionMethodsRecurse(referencedAssembly);
+ }
+ }
+ internal static void AnalyzeExtensionMethods(Assembly assembly)
+ {
+ if (!AlreadyRegisteredAssemblies.Add(assembly.GetName()))
+ return;
+ foreach (var t in assembly.GetTypes())
+ {
+ AnalyzeExtensionMethods(t);
+ }
+ }
+ private static Type[] MatchGenericArguments(MethodInfo x, ParameterInfo[] parameters, Type[] parameterTypes, Type returnType)
+ {
+ // TODO: cannot match generic parameters that are not used as parameter types
+ var genArguments = x.GetGenericArguments();
+ var matchedParameters = new Type[genArguments.Length];
+
+ void MatchNew(int index, Type newlyMatched)
+ {
+ var previouslyMatched = matchedParameters[index];
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (previouslyMatched is null || newlyMatched.IsAssignableFrom(previouslyMatched))
+ {
+ matchedParameters[index] = newlyMatched;
+ }
+ }
+
+ for (var index = 0; index < genArguments.Length; index++)
+ {
+ var genArg = genArguments[index];
+
+ if (x.ReturnType == genArg)
+ {
+ MatchNew(index, returnType);
+ }
+
+ for (var pIndex = 0; pIndex < parameters.Length; pIndex++)
+ {
+ var genParam = parameters[pIndex];
+ if (genParam.ParameterType != genArg)
+ continue;
+ var newlyMatched = parameterTypes[pIndex];
+ MatchNew(index, newlyMatched);
+ }
+ }
+
+ return matchedParameters;
+ }
+
+ private static IEnumerable SelectMethods(IEnumerable methodInfos, Type[] parameterTypes, Type? returnType)
+ {
+ return methodInfos.Select(x =>
+ {
+ try
+ {
+ var parameters = x.GetParameters();
+ if (parameters.Length != parameterTypes.Length)
+ return null;
+ if (x.IsGenericMethodDefinition)
+ {
+ var matched = MatchGenericArguments(x, parameters, parameterTypes, returnType ?? typeof(void));
+ var genMethod = x.MakeGenericMethod(matched);
+ if (genMethod.ReturnType != (returnType ?? typeof(void)))
+ return null;
+ return genMethod;
+ }
+
+ return x;
+ }
+ catch (ArgumentException)
+ {
+ return null;
+ }
+ }).Where(x => x is not null).OfType();
+ }
+ internal static MethodInfo? GetBestMatch(Type type, string methodName, BindingFlags bindingFlags, Type[] parameterTypes, Type? returnType)
+ {
+ var methods = SelectMethods(type.GetMethods(bindingFlags)
+ .Where(x => x.Name == methodName), parameterTypes, returnType).ToArray();
+
+ if (GetPerfectMatch(parameterTypes, methods) is { } perfectMatch)
+ return perfectMatch;
+
+ // ReSharper disable once CoVariantArrayConversion
+ var bestInstanceMethod = methods.Length == 0 ? null : Type.DefaultBinder.SelectMethod(bindingFlags, methods, parameterTypes, null) as MethodInfo;
+ if (bestInstanceMethod is null)
+ {
+ var parameterTypesWithThis = parameterTypes.Prepend(type).ToArray();
+ var extMethods = SelectMethods(GetRegisteredExtensionMethods(methodName), parameterTypesWithThis, returnType).ToArray();
+ if (GetPerfectMatch(parameterTypesWithThis, extMethods) is { } perfectExtMatch)
+ return perfectExtMatch;
+ if (extMethods.Length == 0)
+ return null;
+ // ReSharper disable once CoVariantArrayConversion
+ return Type.DefaultBinder.SelectMethod(bindingFlags, extMethods, parameterTypesWithThis, null) as MethodInfo;
+ }
+
+ return bestInstanceMethod;
+ }
+
+ private static MethodInfo? GetPerfectMatch(Type[] parameterTypes, MethodInfo[] methods)
+ {
+ var perfectMatch = methods.FirstOrDefault(
+ x =>
+ {
+ if (x.IsGenericMethod)
+ return false;
+ var parameters = x.GetParameters();
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ if (parameters[i].ParameterType != parameterTypes[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ });
+ return perfectMatch;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/NonSucking.Advanced.props b/NonSucking.Framework.Serialization.Advanced/NonSucking.Advanced.props
new file mode 100644
index 0000000..52d66a7
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/NonSucking.Advanced.props
@@ -0,0 +1,6 @@
+
+
+
+ $(DefineConstants);__NOOSON_ADVANCED_SERIALIZATION__
+
+
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/NonSucking.Framework.Serialization.Advanced.csproj b/NonSucking.Framework.Serialization.Advanced/NonSucking.Framework.Serialization.Advanced.csproj
index 533e94e..deb0817 100644
--- a/NonSucking.Framework.Serialization.Advanced/NonSucking.Framework.Serialization.Advanced.csproj
+++ b/NonSucking.Framework.Serialization.Advanced/NonSucking.Framework.Serialization.Advanced.csproj
@@ -1,12 +1,13 @@
- netstandard2.0;netstandard2.1;net7.0
+ net7.0
+ enable
NonSucking.Framework.Serialization
10.0
-
+
diff --git a/NonSucking.Framework.Serialization.Advanced/NullabilityHelper.cs b/NonSucking.Framework.Serialization.Advanced/NullabilityHelper.cs
new file mode 100644
index 0000000..e2753d3
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/NullabilityHelper.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+public static class NullabilityHelper
+{
+ public delegate NullabilityInfo NullabilityInfoCreationDelegate(
+ Type type,
+ NullabilityState readState,
+ NullabilityState writeState,
+ NullabilityInfo? elementType,
+ NullabilityInfo[] typeArguments);
+
+ public static readonly NullabilityInfoCreationDelegate CreateNullability;
+
+ static NullabilityHelper()
+ {
+ var ctorParamTypes = new[]
+ {
+ typeof(Type),
+ typeof(NullabilityState),
+ typeof(NullabilityState),
+ typeof(NullabilityInfo),
+ typeof(NullabilityInfo[])
+ };
+ var ctor = typeof(NullabilityInfo).GetConstructor(
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic,
+ ctorParamTypes) ?? throw new InvalidProgramException($"Could not find constructor for '{nameof(NullabilityInfo)}'");
+
+ var ctorParams = ctorParamTypes.Select(Expression.Parameter).ToArray();
+
+ CreateNullability = Expression.Lambda(Expression.New(ctor, ctorParams), ctorParams).Compile();
+ }
+ public static readonly NullabilityInfoContext Context = new();
+ internal static NullabilityInfo CreateNullable(Type type, bool isNullable)
+ {
+ if (type.IsGenericType && type.IsAssignableToOpenGeneric(typeof(Nullable<>)))
+ isNullable = true;
+
+ var elementType = type.IsSZArray ? CreateNullable(type.GetElementType()!, false) : null;
+ var typeArguments = type.IsGenericType
+ ? type.GenericTypeArguments.Select(x => CreateNullable(x, false)).ToArray()
+ : Array.Empty();
+
+ return CreateNullability(
+ type,
+ isNullable ? NullabilityState.Nullable : NullabilityState.NotNull,
+ isNullable ? NullabilityState.Nullable : NullabilityState.NotNull,
+ elementType,
+ typeArguments);
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/SerializeGenerating.cs b/NonSucking.Framework.Serialization.Advanced/SerializeGenerating.cs
new file mode 100644
index 0000000..eb4e2ad
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/SerializeGenerating.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+public static class SerializeGenerating
+{
+ public static MethodInfo GenerateSerialize(FieldInfo prop)
+ {
+ return GenerateSerialize(prop.FieldType, NullabilityHelper.Context.Create(prop));
+ }
+ public static MethodInfo GenerateSerialize(PropertyInfo prop)
+ {
+ return GenerateSerialize(prop.PropertyType, NullabilityHelper.Context.Create(prop));
+ }
+
+ public static MethodInfo GenerateSerialize(Type type, bool isNullable)
+ {
+ return GenerateSerialize(type, NullabilityHelper.CreateNullable(type, isNullable));
+ }
+
+ private static MethodInfo GenerateSerialize(Type type, NullabilityInfo nullability)
+ {
+ var gen = new SerializeGenerator(typeof(TWriter), TypeNameCache.GetTypeConfig(type),
+ new BaseGeneratorContext.TypeContext(type, nullability));
+ return gen.CurrentContext.Build().GetMethod("Serialize", BindingFlags.Public | BindingFlags.Static)!;
+ }
+
+ public static Delegate GenerateSerializeDelegate(Type type, bool isNullable)
+ {
+ var m = GenerateSerialize(type, isNullable);
+ var writer = Expression.Parameter(typeof(TWriter), "writer");
+ var value = Expression.Parameter(type, "value");
+ return Expression.Lambda(Expression.Call(m, writer, value), writer, value).Compile();
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/SerializeGenerator.cs b/NonSucking.Framework.Serialization.Advanced/SerializeGenerator.cs
new file mode 100644
index 0000000..0f6c8a0
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/SerializeGenerator.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class SerializeGenerator : BaseGenerator
+{
+ public SerializeGenerator(Type writerType, TypeNameCache.TypeConfig typeConfig,
+ BaseGeneratorContext.TypeContext valueType)
+ : base( new SerializeGeneratorContext(writerType, valueType, gen => gen.Emit(OpCodes.Ldarg_0), true))
+ {
+ CurrentContext.GetValue = gen => gen.Emit(OpCodes.Ldarg_1);
+ CurrentContext.GetValueRef = valueType.Type.IsValueType
+ ? gen => gen.Emit(OpCodes.Ldarga, (short)1)
+ : CurrentContext.GetValue;
+
+ PushContext(CurrentContext);
+ if (!Create(typeConfig))
+ {
+ throw new TypeSerializerException($"Unable to create dynamic serializer for type config: {typeConfig}");
+ }
+ }
+
+ public bool GenerateSerialize(SerializeGeneratorContext context, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ PushContext(context);
+
+ ContextMap.TryAdd(context.ValueType, context);
+
+ var res = CustomMethodCallSerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ NullableSerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ !IsTopLevel && RecursiveCallSerializer.Serialize(this, typeConfig) ||
+ SpecialTypeSerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ EnumSerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ KnownSimpleTypeSerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ UnmanagedTypeSerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ MethodCallSerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ ArraySerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ ListSerializer.Serialize(this, typeConfig, baseRecursionDepth) ||
+ PublicPropertySerializer.Serialize(this, typeConfig, baseRecursionDepth);
+
+ _ = PopContext();
+ return res;
+ }
+
+ private bool Create(TypeNameCache.TypeConfig typeConfig)
+ {
+ var res = GenerateSerialize(CurrentContext, typeConfig);
+ if (!res)
+ return false;
+ Il.Emit(OpCodes.Ret);
+
+ while (Contexts.Count > 1)
+ _ = PopContext();
+
+ return res;
+ }
+
+ public new SerializeGeneratorContext CurrentContext => (SerializeGeneratorContext)base.CurrentContext;
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/SerializeGeneratorContext.cs b/NonSucking.Framework.Serialization.Advanced/SerializeGeneratorContext.cs
new file mode 100644
index 0000000..db07455
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/SerializeGeneratorContext.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+public class SerializeGeneratorContext : BaseGeneratorContext
+{
+ public SerializeGeneratorContext(Type writerType, TypeContext valueType, Action getReaderWriter, bool isTopLevel, MethodBuilder method)
+ : base(valueType, ContextType.Serialize, getReaderWriter, isTopLevel, method)
+ {
+ WriterType = writerType;
+ }
+
+ public SerializeGeneratorContext(Type writerType, TypeContext valueType, Action getReaderWriter, bool isTopLevel)
+ : base(writerType, valueType, ContextType.Serialize, getReaderWriter, isTopLevel)
+ {
+ WriterType = writerType;
+ }
+ public Type WriterType { get; }
+
+ public override SerializeGeneratorContext SubContext(TypeContext valueType, bool? isTopLevel)
+ {
+ return new SerializeGeneratorContext(WriterType, valueType, GetReaderWriter, isTopLevel ?? IsTopLevel, Method);
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/ArraySerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/ArraySerializer.cs
new file mode 100644
index 0000000..b3e2983
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/ArraySerializer.cs
@@ -0,0 +1,259 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class ArraySerializer
+{
+ private static readonly MethodInfo GetLength = MethodResolver.GetBestMatch(typeof(Array), "GetLength",
+ BindingFlags.Instance | BindingFlags.Public,
+ new[] { typeof(int) }, typeof(int))
+ ?? throw new ArgumentNullException("Array.GetLength not found", (Exception?)null);
+
+ private static void CreateNestedFor(int rank, T generator, LocalBuilder[] loopVariables, LocalBuilder[] countVariables, Action body)
+ where T : BaseGenerator
+ {
+ var previousMethod = body;
+ for (int i = rank - 1; i >= 0; i--)
+ {
+ var iCopyGen = i;
+ var previousMethodCopyGen = previousMethod;
+ previousMethod = gen =>
+ {
+ var iCopyFor = iCopyGen;
+ var previousMethodCopyFor = previousMethodCopyGen;
+ gen.EmitFor(
+ g =>
+ {
+ g.Il.Emit(OpCodes.Ldc_I4_0);
+ g.Il.Emit(OpCodes.Stloc, loopVariables[iCopyFor]);
+ return loopVariables[iCopyFor];
+ },
+ (g, var) =>
+ {
+ g.Il.Emit(OpCodes.Ldloc, var!);
+ g.Il.Emit(OpCodes.Ldloc, countVariables[iCopyFor]);
+ g.Il.Emit(OpCodes.Clt);
+ },
+ (g, var) =>
+ {
+ g.EmitIncrement(var!);
+ },
+ (g, _, _, _) =>
+ {
+ previousMethodCopyFor((T)g);
+ });
+ };
+ }
+
+ previousMethod(generator);
+ }
+
+
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType;
+ if (!type.Type.IsArray)
+ return false;
+ var rank = type.Type.GetArrayRank();
+ if (rank < 1)
+ return false;
+ var elementType = type.Type.GetElementType()
+ ?? throw new InvalidOperationException($"{type.Type} is an array but array element type could not be resolved");
+
+ var getterMethod = MethodResolver.GetBestMatch(type.Type, "Get", BindingFlags.Instance | BindingFlags.Public,
+ Enumerable.Repeat(typeof(int), rank).ToArray(), elementType)
+ ?? throw new InvalidOperationException($"{type.Type} is an array but get method could not be resolved");
+
+ var loopVariables = new LocalBuilder[rank];
+ var countVariables = new LocalBuilder[rank];
+ void ForBody(SerializeGenerator gen)
+ {
+ gen.CurrentContext.GetValue!(gen.Il);
+ for (int i = 0; i < rank; i++)
+ {
+ gen.Il.Emit(OpCodes.Ldloc, loopVariables[i]);
+ }
+ if (rank == 1)
+ gen.Il.Emit(OpCodes.Ldelem, elementType);
+ else
+ gen.Il.Emit(OpCodes.Callvirt, getterMethod);
+ var elementVar = gen.Il.DeclareLocal(elementType);
+ gen.Il.Emit(OpCodes.Stloc, elementVar);
+
+ var subContext =
+ gen.CurrentContext.SubContext(
+ new BaseGeneratorContext.TypeContext(elementType, type.NullabilityInfo.ElementType!),
+ null);
+ subContext.GetValue = _ => gen.Il.Emit(OpCodes.Ldloc, elementVar);
+ subContext.GetValueRef = elementType.IsValueType
+ ? _ => gen.Il.Emit(OpCodes.Ldloca, elementVar)
+ : subContext.GetValue;
+
+ var tpConfig = TypeNameCache.GetTypeConfig(elementType);
+
+ _ = gen.GenerateSerialize(subContext, tpConfig);
+ }
+
+ var writeInt = Helper.GetWrite(generator, typeof(int));
+
+ for (int i = 0; i < rank; i++)
+ {
+ var countVar = generator.Il.DeclareLocal(typeof(int));
+ generator.CurrentContext.GetValue!(generator.Il);
+ if (rank == 1)
+ {
+ var length = type.Type.GetProperty("Length")!;
+ generator.Il.Emit(OpCodes.Callvirt, length.GetMethod!);
+ }
+ else
+ {
+ generator.Il.Emit(OpCodes.Ldc_I4, i);
+ generator.Il.Emit(OpCodes.Callvirt, GetLength);
+ }
+ generator.Il.Emit(OpCodes.Stloc, countVar);
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, countVar);
+ generator.Il.Emit(OpCodes.Callvirt, writeInt);
+
+
+ countVariables[i] = countVar;
+
+ loopVariables[i] = generator.Il.DeclareLocal(typeof(int));
+ }
+
+ CreateNestedFor(rank, generator, loopVariables, countVariables, ForBody);
+
+
+ return true;
+ }
+
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType;
+ if (!type.Type.IsArray)
+ return false;
+ var rank = type.Type.GetArrayRank();
+ if (rank < 1)
+ return false;
+
+ var elementType = type.Type.GetElementType()
+ ?? throw new InvalidOperationException($"{type.Type} is an array but array element type could not be resolved");
+
+ var setterMethod = MethodResolver.GetBestMatch(type.Type, "Set", BindingFlags.Instance | BindingFlags.Public,
+ Enumerable.Repeat(typeof(int), rank).Append(elementType).ToArray(), null)
+ ?? throw new InvalidOperationException($"{type.Type} is an array but set method could not be resolved");
+
+ var loopVariables = new LocalBuilder[rank];
+ var countVariables = new LocalBuilder[rank];
+
+ void ForBody(DeserializeGenerator gen)
+ {
+ var subContext =
+ gen.CurrentContext.SubContext(
+ new BaseGeneratorContext.TypeContext(elementType, type.NullabilityInfo.ElementType!),
+ null);
+
+ var bodyResVar = gen.Il.DeclareLocal(elementType);
+ subContext.SetValue = g => g.Emit(OpCodes.Stloc, bodyResVar);
+ subContext.GetValue = g => g.Emit(OpCodes.Ldloc, bodyResVar);
+ subContext.GetValueRef = elementType.IsValueType
+ ? g => g.Emit(OpCodes.Ldloca, bodyResVar)
+ : subContext.GetValue;
+
+ var tpConfig = TypeNameCache.GetTypeConfig(elementType);
+ _ = gen.GenerateDeserialize(subContext, tpConfig);
+
+ gen.CurrentContext.GetValue!(gen.Il);
+ for (int i = 0; i < rank; i++)
+ {
+ gen.Il.Emit(OpCodes.Ldloc, loopVariables[i]);
+ }
+
+ gen.Il.Emit(OpCodes.Ldloc, bodyResVar);
+ if (rank == 1)
+ gen.Il.Emit(GetStElem(elementType));
+ else
+ gen.Il.Emit(OpCodes.Callvirt, setterMethod);
+
+ }
+
+ // var resArray = generator.IL.DeclareLocal(type);
+ // generator.CurrentContext.GetValue = (g) => g.Emit(OpCodes.Ldloc, resArray);
+ //
+ var readInt = Helper.GetRead(generator, typeof(int));
+
+ for (int i = 0; i < rank; i++)
+ {
+ var countVar = generator.Il.DeclareLocal(typeof(int));
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Callvirt, readInt);
+ generator.Il.Emit(OpCodes.Stloc, countVar);
+
+ countVariables[i] = countVar;
+
+ loopVariables[i] = generator.Il.DeclareLocal(typeof(int));
+ }
+
+ for (int i = 0; i < rank; i++)
+ {
+ generator.Il.Emit(OpCodes.Ldloc, countVariables[i]);
+ }
+
+ if (rank == 1)
+ {
+ generator.Il.Emit(OpCodes.Newarr, elementType);
+ }
+ else
+ {
+ var arrayCtor = type.Type.GetConstructor(Enumerable.Repeat(typeof(int), rank).ToArray())
+ ?? throw new InvalidOperationException($"{type.Type} is an array but constructor could not be resolved");
+
+ generator.Il.Emit(OpCodes.Newobj, arrayCtor);
+ }
+
+ generator.CurrentContext.SetValue!(generator.Il);
+
+
+ CreateNestedFor(rank, generator, loopVariables, countVariables, ForBody);
+ //
+ // generator.CurrentContext.GetValue = g => g.Emit(OpCodes.Ldloc, resArray);
+ // generator.CurrentContext.GetValue = g => g.Emit(OpCodes.Ldloc, resArray);
+
+ return true;
+ }
+
+ private static OpCode GetStElem(Type type)
+ {
+ if (!type.IsValueType)
+ return OpCodes.Stelem_Ref;
+ // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
+ switch (Type.GetTypeCode(type))
+ {
+ case TypeCode.Byte:
+ case TypeCode.SByte:
+ case TypeCode.Boolean:
+ return OpCodes.Stelem_I1;
+ case TypeCode.Char:
+ return OpCodes.Stelem_I2;
+ case TypeCode.Int32:
+ case TypeCode.UInt32:
+ return OpCodes.Stelem_I4;
+ case TypeCode.Int64:
+ case TypeCode.UInt64:
+ return OpCodes.Stelem_I8;
+ case TypeCode.Single:
+ return OpCodes.Stelem_R4;
+ case TypeCode.Double:
+ return OpCodes.Stelem_R8;
+ }
+
+ if (type == typeof(IntPtr) || type == typeof(UIntPtr))
+ return OpCodes.Stelem_I;
+
+ return OpCodes.Stelem;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/CustomMethodCallSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/CustomMethodCallSerializer.cs
new file mode 100644
index 0000000..7ece33d
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/CustomMethodCallSerializer.cs
@@ -0,0 +1,18 @@
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class CustomMethodCallSerializer
+{
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ if (!typeConfig.IsCustomMethodCall)
+ return false;
+
+ return MethodCallSerializer.Serialize(generator, typeConfig);
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ if (!typeConfig.IsCustomMethodCall)
+ return false;
+ return MethodCallSerializer.Deserialize(generator, typeConfig);
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/EnumSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/EnumSerializer.cs
new file mode 100644
index 0000000..07d6f38
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/EnumSerializer.cs
@@ -0,0 +1,42 @@
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class EnumSerializer
+{
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+ if (!type.IsEnum)
+ return false;
+ var serializerMethod = Helper.TryGetWrite(generator, type.GetEnumUnderlyingType());
+ if (serializerMethod is null)
+ return false;
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.CurrentContext.GetValue!(generator.Il);
+
+ generator.Il.Emit(OpCodes.Callvirt, serializerMethod);
+ return true;
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+ if (!type.IsEnum)
+ return false;
+ var underlyingType = type.GetEnumUnderlyingType();
+ var deserializerMethod = Helper.TryGetRead(generator, underlyingType);
+ if (deserializerMethod is null)
+ {
+ return false;
+ }
+
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Call, deserializerMethod);
+
+ generator.CurrentContext.SetValue!(generator.Il);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/KnownSimpleTypeSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/KnownSimpleTypeSerializer.cs
new file mode 100644
index 0000000..c2ec3c3
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/KnownSimpleTypeSerializer.cs
@@ -0,0 +1,363 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Numerics;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class KnownSimpleTypeSerializer
+{
+ private enum KnownTypes
+ {
+ None,
+ IpAddress,
+ Guid,
+ BigInteger
+ }
+ private static KnownTypes GetKnownType(Type type)
+ {
+ if (type == typeof(IPAddress))
+ {
+ return KnownTypes.IpAddress;
+ }
+ if (type == typeof(Guid))
+ {
+ return KnownTypes.Guid;
+ }
+ if (type == typeof(BigInteger))
+ {
+ return KnownTypes.BigInteger;
+ }
+ return KnownTypes.None;
+ }
+
+ private static void GetIpAddressSize(BaseGenerator generator, LocalBuilder addressFamilyVar)
+ {
+ generator.EmitIf(gen =>
+ {
+ var throwNotSupported = typeof(NotSupportedException).GetConstructor(
+ BindingFlags.Instance | BindingFlags.Public,
+ Type.EmptyTypes)
+ ?? throw new InvalidProgramException("NotSupportedException is missing an empty constructor");
+ gen.Il.Emit(OpCodes.Newobj, throwNotSupported);
+ gen.Il.Emit(OpCodes.Throw);
+ },
+ (
+ OpCodes.Bne_Un,
+ gen =>
+ {
+ gen.Il.Emit(OpCodes.Ldloc, addressFamilyVar);
+ gen.Il.Emit(OpCodes.Ldc_I4, (int)AddressFamily.InterNetworkV6);
+ },
+ gen =>
+ {
+ gen.Il.Emit(OpCodes.Ldc_I4, 16);
+ }
+ ),
+ (
+ OpCodes.Bne_Un,
+ gen =>
+ {
+ gen.Il.Emit(OpCodes.Ldloc, addressFamilyVar);
+ gen.Il.Emit(OpCodes.Ldc_I4, (int)AddressFamily.InterNetwork);
+ },
+ gen =>
+ {
+ gen.Il.Emit(OpCodes.Ldc_I4, 4);
+ }
+ )
+ );
+ }
+
+ private static MethodInfo? GetTryWriteBytes(Type? discardType = null)
+ {
+ var paramTypes = discardType is not null ? new[] { typeof(Span), discardType.MakeByRefType() } : new[] { typeof(Span) };
+ return typeof(T).GetMethod(
+ "TryWriteBytes",
+ BindingFlags.Instance | BindingFlags.Public,
+ paramTypes);
+ }
+ private static MethodInfo? GetTryWriteBytesBigInt(Type discardType)
+ {
+ var paramTypes = new[] { typeof(Span), discardType.MakeByRefType(), typeof(bool), typeof(bool)};
+ return typeof(BigInteger).GetMethod(
+ "TryWriteBytes",
+ BindingFlags.Instance | BindingFlags.Public,
+ paramTypes);
+ }
+ private static void TryWriteBytes(BaseGenerator generator, MethodInfo tryWriteBytes, LocalBuilder buffer, LocalBuilder? discard = null)
+ {
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, buffer);
+ if (discard is not null)
+ {
+ generator.Il.Emit(OpCodes.Ldloca, discard);
+ }
+
+ foreach (var _ in tryWriteBytes.GetParameters().Skip(discard is not null ? 2 : 1))
+ {
+ generator.Il.Emit(OpCodes.Ldc_I4_0);
+ }
+
+ generator.Il.Emit(OpCodes.Callvirt, tryWriteBytes);
+ generator.Il.Emit(OpCodes.Pop);
+ }
+
+ private static LocalBuilder GetSerializableBuffer(SerializeGenerator generator, Type type, KnownTypes knownType)
+ {
+ var writeInt = Helper.GetWrite(generator, typeof(int));
+
+ switch (knownType)
+ {
+ case KnownTypes.IpAddress:
+ {
+ var addressFamilyProp = type.GetProperty("AddressFamily", BindingFlags.Instance | BindingFlags.Public)!;
+ var addressFamilyVar = generator.Il.DeclareLocal(typeof(AddressFamily));
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ generator.Il.Emit(OpCodes.Callvirt, addressFamilyProp.GetMethod!);
+ generator.Il.Emit(OpCodes.Stloc, addressFamilyVar);
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, addressFamilyVar);
+ generator.Il.Emit(OpCodes.Callvirt, writeInt);
+
+ var tryWriteBytes = GetTryWriteBytes(typeof(int));
+
+ LocalBuilder bufferVar;
+ if (tryWriteBytes is null)
+ {
+ bufferVar = GetSimpleByteBuffer(generator, type, "GetAddressBytes");
+ }
+ else
+ {
+ GetIpAddressSize(generator, addressFamilyVar);
+
+ var bufferSizeVar = generator.Il.DeclareLocal(typeof(int));
+
+ generator.Il.Emit(OpCodes.Dup);
+ generator.Il.Emit(OpCodes.Stloc, bufferSizeVar);
+ generator.Il.Emit(OpCodes.Localloc);
+ generator.Il.Emit(OpCodes.Ldloc, bufferSizeVar);
+
+ bufferVar = CreateSpanByteBuffer(generator);
+
+ var discardInt = bufferSizeVar; // Reuse bufferSizeVar for out discard
+
+ TryWriteBytes(generator, tryWriteBytes, bufferVar, discardInt);
+ }
+
+ return bufferVar;
+ }
+ case KnownTypes.Guid:
+ {
+ var tryWriteBytes = GetTryWriteBytes();
+
+ LocalBuilder bufferVar;
+ if (tryWriteBytes is null)
+ {
+ bufferVar = GetSimpleByteBuffer(generator, type, "ToByteArray");
+ }
+ else
+ {
+ generator.Il.Emit(OpCodes.Ldc_I4, 16);
+ generator.Il.Emit(OpCodes.Localloc);
+ generator.Il.Emit(OpCodes.Ldc_I4, 16);
+
+ bufferVar = CreateSpanByteBuffer(generator);
+ TryWriteBytes(generator, tryWriteBytes, bufferVar);
+ }
+
+ return bufferVar;
+ }
+ case KnownTypes.BigInteger:
+ {
+ var getByteCount = type.GetMethod("GetByteCount", BindingFlags.Instance | BindingFlags.Public)!;
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ if (getByteCount.GetParameters().Length == 1)
+ generator.Il.Emit(OpCodes.Ldc_I4_0);
+ generator.Il.Emit(OpCodes.Callvirt, getByteCount);
+ var bufferSizeVar = generator.Il.DeclareLocal(typeof(int));
+ generator.Il.Emit(OpCodes.Stloc, bufferSizeVar);
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, bufferSizeVar);
+ generator.Il.Emit(OpCodes.Callvirt, writeInt);
+
+ var tryWriteBytes = GetTryWriteBytesBigInt(bufferSizeVar.LocalType);
+
+ LocalBuilder bufferVar;
+ if (tryWriteBytes is null)
+ {
+ bufferVar = GetSimpleByteBuffer(generator, type, "ToByteArray");
+ }
+ else
+ {
+ generator.Il.Emit(OpCodes.Ldloc, bufferSizeVar);
+ generator.Il.Emit(OpCodes.Localloc);
+ generator.Il.Emit(OpCodes.Ldloc, bufferSizeVar);
+
+ bufferVar = CreateSpanByteBuffer(generator);
+ TryWriteBytes(generator, tryWriteBytes, bufferVar, bufferSizeVar);
+ }
+
+ return bufferVar;
+ }
+ default:
+ throw new NotSupportedException($"{knownType} is not a valid type to be serialized.");
+ }
+ }
+
+ private static LocalBuilder CreateSpanByteBuffer(BaseGenerator generator)
+ {
+ var byteSpanCtor = typeof(Span).GetConstructor(BindingFlags.Instance | BindingFlags.Public,
+ new[] { typeof(void*), typeof(int) })
+ ?? throw new InvalidProgramException("Span is missing constructor(void*, int).");
+
+ var bufferVar = generator.Il.DeclareLocal(typeof(Span));
+ generator.Il.Emit(OpCodes.Newobj, byteSpanCtor);
+
+ generator.Il.Emit(OpCodes.Stloc, bufferVar);
+ return bufferVar;
+ }
+
+ private static LocalBuilder GetSimpleByteBuffer(BaseGenerator generator, Type type, string methodName)
+ {
+ var getAddressBytes = type.GetMethod(methodName,
+ BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes);
+ if (getAddressBytes is null)
+ throw new NotSupportedException(
+ $"{type} type is missing 'TryWriteBytes(Span, out int)' or '{methodName}' method");
+
+ var bufferVar = generator.Il.DeclareLocal(getAddressBytes.ReturnType);
+
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ generator.Il.Emit(OpCodes.Callvirt, getAddressBytes);
+
+ generator.Il.Emit(OpCodes.Stloc, bufferVar);
+ return bufferVar;
+ }
+
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+
+ var knownType = GetKnownType(type);
+
+ if (knownType == KnownTypes.None)
+ return false;
+
+ var buffer = GetSerializableBuffer(generator, type, knownType);
+
+ var writeMethod = generator.CurrentContext.WriterType.GetMethod("Write",
+ BindingFlags.Public | BindingFlags.Instance, new[] { buffer.LocalType });
+
+ if (writeMethod is null && buffer.LocalType != typeof(ReadOnlySpan))
+ {
+ writeMethod ??= generator.CurrentContext.WriterType.GetMethod("Write",
+ BindingFlags.Public | BindingFlags.Instance, new[] { typeof(ReadOnlySpan) });
+ }
+
+ if (writeMethod is null)
+ throw new NotImplementedException($"Missing Write implementation on writer for type {buffer.LocalType}");
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, buffer);
+ generator.Il.Emit(OpCodes.Callvirt, writeMethod);
+
+ return true;
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+ var readInt = Helper.GetRead(generator, typeof(int));
+
+ var knownType = GetKnownType(type);
+
+ if (knownType == KnownTypes.None)
+ return false;
+
+ var ctor = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance,
+ new[] { typeof(ReadOnlySpan), typeof(bool), typeof(bool) });
+ bool useSpans = true;
+ if (ctor is null)
+ {
+ useSpans = false;
+ ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public,
+ new[] { typeof(byte[]) });
+ }
+
+ if (ctor is null)
+ {
+ throw new NotSupportedException($"No matching {knownType} ctor found taking either 'byte[]' or 'ReadOnlySpan'");
+ }
+
+ switch (knownType)
+ {
+ case KnownTypes.IpAddress:
+ var addressFamilyVar = generator.Il.DeclareLocal(typeof(AddressFamily));
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Callvirt, readInt);
+ generator.Il.Emit(OpCodes.Stloc, addressFamilyVar);
+
+ GetIpAddressSize(generator, addressFamilyVar);
+
+ break;
+ case KnownTypes.Guid:
+ generator.Il.Emit(OpCodes.Ldc_I4, 16);
+ break;
+ case KnownTypes.BigInteger:
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Callvirt, readInt);
+ break;
+ default:
+ throw new NotSupportedException($"{knownType} is not supported to be serialized.");
+ }
+
+ LocalBuilder bufferVar;
+ var bufferSizeVar = generator.Il.DeclareLocal(typeof(int));
+ if (useSpans)
+ {
+ generator.Il.Emit(OpCodes.Dup);
+ generator.Il.Emit(OpCodes.Stloc, bufferSizeVar);
+ generator.Il.Emit(OpCodes.Localloc);
+ generator.Il.Emit(OpCodes.Ldloc, bufferSizeVar);
+
+ bufferVar = CreateSpanByteBuffer(generator);
+ var readBytes = MethodResolver.GetBestMatch(generator.CurrentContext.ReaderType, "ReadBytes",
+ BindingFlags.Public | BindingFlags.Instance,
+ new[] { typeof(Span) }, null)
+ ?? throw new InvalidOperationException($"{generator.CurrentContext.ReaderType} does not have a valid void ReadBytes(Span) overload.");
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, bufferVar);
+ generator.EmitCall(readBytes);
+ }
+ else
+ {
+ var readBytes = MethodResolver.GetBestMatch(generator.CurrentContext.ReaderType, "ReadBytes",
+ BindingFlags.Public | BindingFlags.Instance,
+ new[] { typeof(int) }, typeof(byte[]))
+ ?? throw new InvalidOperationException($"{generator.CurrentContext.ReaderType} does not have a valid byte[] ReadBytes(int) overload.");
+ generator.Il.Emit(OpCodes.Stloc, bufferSizeVar);
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, bufferSizeVar);
+ generator.EmitCall(readBytes);
+
+ bufferVar = generator.Il.DeclareLocal(typeof(byte[]));
+ generator.Il.Emit(OpCodes.Stloc, bufferVar);
+ }
+
+ generator.Il.Emit(OpCodes.Ldloc, bufferVar);
+ foreach (var _ in ctor.GetParameters().Skip(1))
+ {
+ generator.Il.Emit(OpCodes.Ldc_I4_0);
+ }
+ generator.Il.Emit(OpCodes.Newobj, ctor);
+
+ generator.CurrentContext.SetValue!(generator.Il);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/ListSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/ListSerializer.cs
new file mode 100644
index 0000000..d083b8d
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/ListSerializer.cs
@@ -0,0 +1,272 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal static class ListSerializer
+{
+ private static NullabilityInfo CreateListNullability(NullabilityInfo info)
+ {
+ return NullabilityHelper.CreateNullability(info.Type, info.ReadState,
+ info.ReadState, info.ElementType, info.GenericTypeArguments);
+ }
+
+ private static NullabilityInfo GetItemNullability(Type type)
+ {
+ var info = NullabilityHelper.Context.Create(
+ Helper.GetMethodIncludingInterfaces(type, "GetEnumerator", BindingFlags.Instance | BindingFlags.Public)!.ReturnType.GetProperty("Current")!);
+ return CreateListNullability(info);
+ }
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+
+ var readonlyCollection = type.GetOpenGenericType(typeof(IReadOnlyCollection<>));
+ if (readonlyCollection is null)
+ return false;
+ var elementType = readonlyCollection.GenericTypeArguments[0];
+
+ var getCountProp = Helper.GetPropertyIncludingInterfaces(type, "Count",
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)
+ ?? throw new InvalidOperationException($"{type} is a List but does not have a valid Count property.");
+
+ if (getCountProp.GetMethod is null)
+ throw new InvalidOperationException($"{type} is a List but does not have a valid Count getter.");
+
+ var inheritanceDepth = GetInheritanceDepth(type);
+
+ if (inheritanceDepth > -1)
+ {
+ var subContext =
+ generator.CurrentContext.SubContext(generator.CurrentContext.ValueType,
+ null);
+ subContext.SetValue = generator.CurrentContext.SetValue;
+ subContext.GetValue = generator.CurrentContext.GetValue;
+ subContext.GetValueRef = generator.CurrentContext.GetValueRef;
+
+ generator.PushContext(subContext);
+
+ _ = PublicPropertySerializer.Serialize(generator, typeConfig, inheritanceDepth);
+
+ _ = generator.PopContext();
+ }
+
+ var writeInt = Helper.GetWrite(generator, typeof(int));
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ generator.Il.Emit(OpCodes.Callvirt, getCountProp.GetMethod);
+ generator.Il.Emit(OpCodes.Callvirt, writeInt);
+
+ generator.EmitForEach(type, gen => gen.CurrentContext.GetValue!(gen.Il),
+ (gen, cont, br, item) =>
+ {
+ var subContext =
+ generator.CurrentContext.SubContext(new BaseGeneratorContext.TypeContext(elementType, GetItemNullability(type)),
+ null);
+ subContext.GetValue = g => g.Emit(OpCodes.Ldloc, item);
+ subContext.GetValueRef = elementType.IsValueType
+ ? g => g.Emit(OpCodes.Ldloca, item)
+ : subContext.GetValue;
+
+ var tpConf = TypeNameCache.GetTypeConfig(elementType);
+
+ _ = generator.GenerateSerialize(subContext, tpConf);
+ });
+
+ return true;
+ }
+
+ private static ConstructorInfo? GetCtor(Type type)
+ {
+ return type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, new[] { typeof(int) })
+ ?? type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, Type.EmptyTypes);
+ }
+
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+
+ var readonlyCollection = type.GetOpenGenericType(typeof(IReadOnlyCollection<>));
+ if (readonlyCollection is null)
+ return false;
+ var elementType = readonlyCollection.GenericTypeArguments[0];
+
+
+ var inheritanceDepth = GetInheritanceDepth(type);
+
+ if (inheritanceDepth > -1)
+ {
+ var subContext =
+ generator.CurrentContext.SubContext(generator.CurrentContext.ValueType,
+ null);
+ subContext.SetValue = generator.CurrentContext.SetValue;
+ subContext.GetValue = generator.CurrentContext.GetValue;
+ subContext.GetValueRef = generator.CurrentContext.GetValueRef;
+
+ generator.PushContext(subContext);
+
+ _ = PublicPropertySerializer.Deserialize(generator, typeConfig, inheritanceDepth);
+
+ _ = generator.PopContext();
+ }
+
+ var readInt = Helper.GetRead(generator, typeof(int));
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Callvirt, readInt);
+
+ var countVar = generator.Il.DeclareLocal(typeof(int));
+ generator.Il.Emit(OpCodes.Stloc, countVar);
+
+ ConstructorInfo? ctor = null;
+ Type? baseInterfaceType = null;
+
+ var elementTypeGenParams = new[] { elementType };
+ if (type is { IsAbstract: false, IsInterface: false })
+ {
+ ctor = GetCtor(type);
+ baseInterfaceType = type;
+ }
+ if (typeof(IReadOnlySet<>).MakeGenericType(elementTypeGenParams) is {} setType && setType.IsAssignableFrom(type))
+ {
+ ctor ??= GetCtor(setType);
+ baseInterfaceType = setType;
+ }
+ else
+ {
+ if (elementType.IsGenericType && elementType.IsAssignableToOpenGeneric(typeof(KeyValuePair<,>)))
+ {
+ var keyType = elementType.GenericTypeArguments[0];
+ var valueType = elementType.GenericTypeArguments[1];
+ var genericParams = new[] { keyType, valueType };
+ if (typeof(IDictionary).MakeGenericType(genericParams) is {} dictType && dictType.IsAssignableFrom(type)
+ || typeof(IReadOnlyDictionary<,>).MakeGenericType(genericParams) is {} readOnlyDictType && readOnlyDictType.IsAssignableFrom(type))
+ {
+ ctor ??= GetCtor(typeof(Dictionary<,>).MakeGenericType(genericParams));
+
+ baseInterfaceType = typeof(ICollection).MakeGenericType(elementTypeGenParams);
+ }
+ }
+
+ if (typeof(IList).MakeGenericType(elementTypeGenParams) is {} listType && listType.IsAssignableFrom(type)
+ || typeof(IReadOnlyCollection<>).MakeGenericType(elementTypeGenParams) is {} readOnlyListType && readOnlyListType.IsAssignableFrom(type))
+ {
+ ctor ??= GetCtor(typeof(List<>).MakeGenericType(elementTypeGenParams));
+ baseInterfaceType = typeof(ICollection).MakeGenericType(elementTypeGenParams);
+ }
+ }
+
+ if (ctor is null)
+ {
+ throw new NotSupportedException();
+ }
+
+ var tempListVar = generator.Il.DeclareLocal(ctor.DeclaringType!);
+ if (ctor.GetParameters().Length == 1)
+ {
+ generator.Il.Emit(OpCodes.Ldloc, countVar);
+ }
+ generator.Il.Emit(OpCodes.Newobj, ctor);
+
+ generator.Il.Emit(OpCodes.Stloc, tempListVar);
+
+ MethodInfo? GetAddMethod(Type? t)
+ {
+ if (t is null)
+ return null;
+ return t.GetMethod(nameof(IList.Add),
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic, elementTypeGenParams)
+ ?? t.GetMethod(nameof(Stack.Push),
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic, elementTypeGenParams)
+ ?? t.GetMethod(nameof(Queue.Enqueue),
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic, elementTypeGenParams);
+ }
+
+ var addMethod = GetAddMethod(tempListVar.LocalType)
+ ?? GetAddMethod(baseInterfaceType);
+
+ if (addMethod is null)
+ throw new NotSupportedException();
+
+ generator.EmitFor(
+ g =>
+ {
+ var counter = g.Il.DeclareLocal(typeof(int));
+ g.Il.Emit(OpCodes.Ldc_I4_0);
+ g.Il.Emit(OpCodes.Stloc, counter);
+ return counter;
+ },
+ (g, counter) =>
+ {
+ g.Il.Emit(OpCodes.Ldloc, counter!);
+ g.Il.Emit(OpCodes.Ldloc, countVar);
+ g.Il.Emit(OpCodes.Clt);
+ },
+ (g, counter) =>
+ {
+ g.EmitIncrement(counter!);
+ },
+ (g, _, _, _) =>
+ {
+
+ var item = g.Il.DeclareLocal(elementType);
+
+ var subContext =
+ generator.CurrentContext.SubContext(new BaseGeneratorContext.TypeContext(elementType, GetItemNullability(type)),
+ null);
+ subContext.GetValue = gen => gen.Emit(OpCodes.Ldloc, item);
+ subContext.GetValueRef = elementType.IsValueType
+ ? gen => gen.Emit(OpCodes.Ldloca, item)
+ : subContext.GetValue;
+ subContext.SetValue = gen => gen.Emit(OpCodes.Stloc, item);
+
+ if (elementType.IsPrimitive || elementType.IsPointer)
+ {
+ g.Il.Emit(OpCodes.Ldc_I4_0);
+ subContext.SetValue(g.Il);
+ }
+ else if (elementType.IsValueType)
+ {
+ subContext.GetValueRef(g.Il);
+ g.Il.Emit(OpCodes.Initobj, elementType);
+ }
+ else
+ {
+ g.Il.Emit(OpCodes.Ldnull);
+ subContext.SetValue(g.Il);
+ }
+
+ var tpConf = TypeNameCache.GetTypeConfig(elementType);
+
+ _ = generator.GenerateDeserialize(subContext, tpConf);
+
+ g.Il.Emit(OpCodes.Ldloc, tempListVar);
+ subContext.GetValue!(g.Il);
+ g.EmitCall(addMethod);
+ if (addMethod.ReturnType != typeof(void))
+ g.Il.Emit(OpCodes.Pop);
+
+ });
+
+ generator.Il.Emit(OpCodes.Ldloc, tempListVar);
+ generator.CurrentContext.SetValue!(generator.Il);
+
+ return true;
+ }
+
+ private static int GetInheritanceDepth(Type? type, int lastAmount = -1)
+ {
+ while (type is not null && !type.IsArray)
+ {
+ if (type.GetInterfaces().Any(x => x == typeof(IEnumerable)))
+ return lastAmount;
+ type = type.BaseType;
+ ++lastAmount;
+ }
+
+ return lastAmount;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/MethodCallSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/MethodCallSerializer.cs
new file mode 100644
index 0000000..bfee245
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/MethodCallSerializer.cs
@@ -0,0 +1,51 @@
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class MethodCallSerializer
+{
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var useStaticSerialize = typeConfig.SerializeType != generator.CurrentContext.ValueType.Type;
+ var serializerMethod = MethodResolver.GetBestMatch(typeConfig.SerializeType, typeConfig.Names.SerializeName,
+ BindingFlags.Public | BindingFlags.NonPublic |
+ (useStaticSerialize ? BindingFlags.Static : BindingFlags.Instance),
+ useStaticSerialize ? new[] { generator.CurrentContext.WriterType, generator.CurrentContext.ValueType.Type } : new[] { generator.CurrentContext.WriterType }, null);
+ if (serializerMethod is null)
+ return false;
+ if (serializerMethod.IsStatic)
+ {
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.CurrentContext.GetValue!(generator.Il);
+ }
+ else
+ {
+ generator.CurrentContext.GetValue!(generator.Il);
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ }
+ generator.EmitCall(serializerMethod);
+ return true;
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var deserializerMethod = MethodResolver.GetBestMatch(typeConfig.DeserializeType,
+ typeConfig.Names.DeserializeName,
+ BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
+ new[] { generator.CurrentContext.ReaderType }, generator.CurrentContext.ValueType.Type);
+ if (deserializerMethod is null)
+ {
+ return false;
+ }
+
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Call, deserializerMethod);
+
+ generator.CurrentContext.SetValue!(generator.Il);
+ // generator.CurrentContext.GetValue = ilGenerator => ilGenerator.Emit(OpCodes.Ldloc, serializerVar);
+ // generator.CurrentContext.SetValue = ilGenerator => ilGenerator.Emit(OpCodes.Stloc, serializerVar);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/NullableSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/NullableSerializer.cs
new file mode 100644
index 0000000..ec90fd3
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/NullableSerializer.cs
@@ -0,0 +1,131 @@
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal static class NullableSerializer
+{
+ private static NullabilityInfo RemoveNullability(NullabilityInfo info)
+ {
+ return NullabilityHelper.CreateNullability(info.Type, NullabilityState.NotNull,
+ NullabilityState.NotNull, info.ElementType, info.GenericTypeArguments);
+ }
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType;
+ if (type.NullabilityInfo.ReadState == NullabilityState.NotNull)
+ return false;
+
+ var writeBool = Helper.GetWrite(generator, typeof(bool));
+ var nullableType = type.Type;
+
+ if (type.Type.IsValueType)
+ {
+ nullableType = type.Type.GetGenericArguments()[0];
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ var getHasValue = type.Type.GetProperty("HasValue")!.GetMethod!;
+ generator.EmitCall(getHasValue);
+ }
+ else
+ {
+ generator.CurrentContext.GetValue!(generator.Il);
+ generator.Il.Emit(OpCodes.Ldnull);
+ generator.Il.Emit(OpCodes.Cgt_Un);
+ }
+
+ var isNotNullVar = generator.Il.DeclareLocal(typeof(bool));
+ generator.Il.Emit(OpCodes.Stloc, isNotNullVar);
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, isNotNullVar);
+
+ generator.Il.Emit(OpCodes.Callvirt, writeBool);
+ generator.Il.Emit(OpCodes.Ldloc, isNotNullVar);
+
+
+
+ generator.EmitIf(OpCodes.Brfalse, gen =>
+ {
+ var subContext =
+ gen.CurrentContext.SubContext(
+ new BaseGeneratorContext.TypeContext(nullableType, RemoveNullability(type.NullabilityInfo)),
+ null);
+ if (type.Type.IsValueType)
+ {
+ var v = gen.Il.DeclareLocal(nullableType);
+ var getValue = type.Type.GetProperty("Value")!.GetMethod!;
+
+ gen.CurrentContext.GetValueRef!(gen.Il);
+ gen.EmitCall(getValue);
+ gen.Il.Emit(OpCodes.Stloc, v);
+
+ subContext.GetValue = g => g.Emit(OpCodes.Ldloc, v);
+ subContext.GetValueRef = g => g.Emit(OpCodes.Ldloca, v);
+ }
+ else
+ {
+ subContext.GetValue = gen.CurrentContext.GetValue;
+ subContext.GetValueRef = gen.CurrentContext.GetValueRef;
+ }
+
+
+ _ = ((SerializeGenerator)gen).GenerateSerialize((SerializeGeneratorContext)subContext, typeConfig, baseRecursionDepth);
+ }, null);
+
+
+
+ return true;
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType;
+ if (type.NullabilityInfo.WriteState == NullabilityState.NotNull)
+ return false;
+
+ var nullableType = type.Type;
+ if (type.Type.IsValueType)
+ {
+ nullableType = type.Type.GetGenericArguments()[0];
+ }
+
+ var readBool = Helper.GetRead(generator, typeof(bool));
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Callvirt, readBool);
+
+
+ generator.EmitIf(OpCodes.Brfalse, gen =>
+ {
+ var subContext =
+ gen.CurrentContext.SubContext(
+ new BaseGeneratorContext.TypeContext(nullableType, RemoveNullability(type.NullabilityInfo)),
+ null);
+
+ if (type.Type.IsValueType)
+ {
+ var v = gen.Il.DeclareLocal(nullableType);
+
+ subContext.GetValue = g => g.Emit(OpCodes.Ldloc, v);
+ subContext.GetValueRef = g => g.Emit(OpCodes.Ldloca, v);
+ subContext.SetValue = g => g.Emit(OpCodes.Stloc, v);
+ }
+ else
+ {
+ subContext.SetValue = gen.CurrentContext.SetValue;
+ subContext.GetValue = gen.CurrentContext.GetValue;
+ subContext.GetValueRef = gen.CurrentContext.GetValueRef;
+ }
+
+ _ = ((DeserializeGenerator)gen).GenerateDeserialize((DeserializeGeneratorContext)subContext, typeConfig);
+
+ if (type.Type.IsValueType)
+ {
+ subContext.GetValue!(gen.Il);
+ gen.Il.Emit(OpCodes.Newobj, type.Type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, new []{ nullableType })!);
+ gen.CurrentContext.SetValue!(gen.Il);
+ }
+ }, null);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/PublicPropertySerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/PublicPropertySerializer.cs
new file mode 100644
index 0000000..2674438
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/PublicPropertySerializer.cs
@@ -0,0 +1,319 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal static class PublicPropertySerializer
+{
+ private static IEnumerable<(MemberInfo memberInfo, int depth)> OrderProps(IEnumerable<(MemberInfo memberInfo, int depth)> props)
+ {
+ return props.OrderBy(x =>
+ {
+ var attr = x.memberInfo.GetCustomAttributesData()
+ .FirstOrDefault(y => y.AttributeType.FullName == Consts.NoosonOrderAttribute);
+ return attr == null ? int.MaxValue : (int)attr.ConstructorArguments[0].Value!;
+ }).ThenByDescending(x => x.depth);
+ }
+ private static IEnumerable<(MemberInfo memberInfo, int depth)> FilterPropsForNotWriteOnly(IEnumerable<(MemberInfo memberInfo, int)> props)
+ {
+ return props.Where(x =>
+ x.memberInfo is PropertyInfo { GetMethod: not null } ||
+ x.memberInfo.MemberType == MemberTypes.Field);
+ }
+
+ private static NullabilityInfo FixReadability(NullabilityInfo info, bool canRead, bool canWrite)
+ {
+ return NullabilityHelper.CreateNullability(info.Type,
+ canRead || !canWrite ? info.ReadState : info.WriteState,
+ !canRead || canWrite ? info.WriteState : info.ReadState,
+ info.ElementType, info.GenericTypeArguments);
+ }
+ private static (Type, NullabilityInfo, PropertyInfo?, FieldInfo?) ExtractInfo(MemberInfo memberInfo)
+ {
+ return memberInfo switch
+ {
+ PropertyInfo pi => (pi.PropertyType,
+ FixReadability(NullabilityHelper.Context.Create(pi), pi.CanRead, pi.CanWrite), pi, null),
+ FieldInfo fi => (fi.FieldType, NullabilityHelper.Context.Create(fi), null, fi),
+ _ => throw new ArgumentOutOfRangeException(nameof(memberInfo))
+ };
+ }
+
+ internal static MethodInfo? GetBaseDeserialize(DeserializeGenerator generator, Type type, bool compareWithOwnSignature, AssemblyNameCache.Names names, List? requiredParameter = null)
+ {
+ return Helper.GetFirstMemberWithBase(
+ type,
+ im =>
+ {
+ var parameters = im.GetParameters();
+ return parameters.Length > 1
+ && (requiredParameter is null
+ || parameters.Length == requiredParameter.Count)
+ && !compareWithOwnSignature
+ && im.Name == names.DeserializeOutName
+ && parameters[0].ParameterType.IsAssignableFrom(generator.CurrentContext.ReaderType)
+ && parameters.Skip(1)
+ .All(x => x.IsOut)
+ && (requiredParameter is null
+ || parameters.ForAll(requiredParameter,
+ (a, b) => a.ParameterType == b));
+ });
+ }
+
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+
+ var baseSerialize = MethodResolver.GetBestMatch(type, typeConfig.Names.SerializeName,
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy,
+ new[] { generator.CurrentContext.WriterType }, null);
+
+ var props = Helper.GetMembersWithBase(type, baseSerialize is null ? baseRecursionDepth : 0)
+ .Where(x => x.memberInfo is not PropertyInfo p || p.GetIndexParameters().Length == 0);
+
+ props = FilterPropsForNotWriteOnly(props).ToList();
+
+ if (baseSerialize is not null && !baseSerialize.IsAbstract) // Do not generate when done for a different type
+ {
+ generator.CurrentContext.GetValue!(generator.Il);
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.EmitCall(baseSerialize);
+ }
+
+ foreach ((var prop, int _) in OrderProps(props))
+ {
+ var (subType, nullability, pi, fi) = ExtractInfo(prop);
+ var subContext =
+ generator.CurrentContext.SubContext(
+ new BaseGeneratorContext.TypeContext(subType, nullability),
+ false);
+ var v = generator.Il.DeclareLocal(subType);
+
+ var tpConfig = typeConfig;
+
+ if (fi is not null)
+ {
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ generator.Il.Emit(fi.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, fi);
+ generator.Il.Emit(OpCodes.Stloc, v);
+ tpConfig = TypeNameCache.GetTypeConfig(fi);
+ }
+ else if (pi is not null)
+ {
+ if (!pi.GetMethod!.IsStatic)
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ generator.EmitCall(pi.GetMethod!);
+ generator.Il.Emit(OpCodes.Stloc, v);
+ tpConfig = TypeNameCache.GetTypeConfig(pi);
+ }
+
+ subContext.GetValue = g => g.Emit(OpCodes.Ldloc, v);
+ subContext.GetValueRef = subType.IsValueType
+ ? g => g.Emit(OpCodes.Ldloca, v)
+ : subContext.GetValue;
+
+ _ = generator.GenerateSerialize(subContext, tpConfig, baseRecursionDepth - 1);
+ }
+
+ return true;
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+
+ var baseDeserialize = GetBaseDeserialize(generator, type, false, typeConfig.Names);
+
+ var props = Helper.GetMembersWithBase(type, baseDeserialize is null ? baseRecursionDepth : 0)
+ .Where(x => x.memberInfo is not PropertyInfo p || p.GetIndexParameters().Length == 0);
+
+ props = FilterPropsForNotWriteOnly(props).ToList();
+
+ var parametersToMatch = baseDeserialize is not null
+ ? new List(baseDeserialize.GetParameters().Skip(1))
+ : new List();
+ var outVariables = baseDeserialize is not null
+ ? new LocalBuilder[baseDeserialize.GetParameters().Length - 1]
+ : Array.Empty();
+
+ var otherVariables = new List();
+
+ var allVariables = new Dictionary();
+
+ foreach (var p in props)
+ {
+ if (baseDeserialize is not null)
+ {
+ bool foundParameter = false;
+ for (int i = parametersToMatch.Count - 1; i >= 0; i--)
+ {
+ var outParam = parametersToMatch[i];
+ var t = p.memberInfo switch
+ {
+ FieldInfo fi => fi.FieldType,
+ PropertyInfo pi => pi.PropertyType,
+ _ => null
+ };
+ if (outParam.ParameterType != t || outParam.Name is null || !Helper.MatchIdentifierWithPropName(p.memberInfo.Name, outParam.Name))
+ continue;
+ var v = outVariables[i] = generator.Il.DeclareLocal(outParam.ParameterType.GetElementType()!);
+ parametersToMatch.RemoveAt(i);
+ foundParameter = true;
+ allVariables.Add(p.memberInfo.Name, (p.memberInfo, v));
+ break;
+ }
+
+ if (foundParameter)
+ continue;
+ }
+
+ otherVariables.Add(p.memberInfo);
+ }
+
+ if (baseDeserialize is not null)
+ {
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ foreach (var p in outVariables)
+ {
+ generator.Il.Emit(OpCodes.Ldloca, p);
+ }
+ generator.EmitCall(baseDeserialize);
+ }
+
+ foreach (var prop in otherVariables)
+ {
+ var (subType, nullability, pi, fi) = ExtractInfo(prop);
+ var subContext =
+ generator.CurrentContext.SubContext(
+ new BaseGeneratorContext.TypeContext(subType, nullability),
+ false);
+ var v = generator.Il.DeclareLocal(subType);
+
+
+
+ subContext.GetValue = g => g.Emit(OpCodes.Ldloc, v);
+ subContext.GetValueRef = subType.IsValueType
+ ? g => g.Emit(OpCodes.Ldloca, v)
+ : subContext.GetValue;
+ subContext.SetValue = g => g.Emit(OpCodes.Stloc, v);
+
+ var tpConfig = typeConfig;
+ if (fi is not null)
+ {
+ tpConfig = TypeNameCache.GetTypeConfig(fi);
+ }
+ else if (pi is not null)
+ {
+ tpConfig = TypeNameCache.GetTypeConfig(pi);
+ }
+
+ _ = generator.GenerateDeserialize(subContext, tpConfig, baseRecursionDepth - 1);
+
+
+ allVariables.Add(prop.Name, (prop, v));
+ }
+
+ var ctorToCall = GetCtorArguments(type, allVariables, out var ctorArgs);
+
+ if (ctorToCall is not null)
+ {
+ foreach (var (key, arg) in ctorArgs)
+ {
+ generator.Il.Emit(OpCodes.Ldloc, arg);
+
+ allVariables.Remove(key);
+ }
+
+
+ generator.Il.Emit(OpCodes.Newobj, ctorToCall);
+
+ generator.CurrentContext.SetValue!(generator.Il);
+ }
+
+ foreach (var (_, (m, v)) in allVariables)
+ {
+ if (m is FieldInfo fi)
+ {
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, v);
+ generator.Il.Emit(OpCodes.Stfld, fi);
+ }
+ else if (m is PropertyInfo { SetMethod: not null } pi)
+ {
+ generator.CurrentContext.GetValueRef!(generator.Il);
+ generator.Il.Emit(OpCodes.Ldloc, v);
+ generator.EmitCall(pi.SetMethod);
+ }
+ }
+
+
+ return true;
+ }
+
+ private static IEnumerable OrderCtors(IEnumerable ctors)
+ {
+ return ctors.OrderByDescending(constructor =>
+ (constructor.GetCustomAttributesData()
+ .FirstOrDefault(x => x.AttributeType.FullName ==
+ Consts.NoosonPreferredCtorAttribute) is not null
+ ? 0xFFFF1
+ : 0) //0xFFFF is the maximum amount of Parameters, so we add an additional one
+ + constructor.GetParameters().Length);
+ }
+
+ internal static ConstructorInfo? GetCtorArguments(Type type, Dictionary localDeclarations, out List<(string, LocalBuilder)> ctorArguments)
+ {
+ ctorArguments = new List<(string, LocalBuilder)>();
+ var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
+ if (constructors.Length == 0)
+ {
+ return null;
+ }
+
+ foreach (var constructor in OrderCtors(constructors))
+ {
+ bool constructorMatch = true;
+
+ foreach (var parameter in constructor.GetParameters())
+ {
+ var parameterName = parameter.Name;
+ if (parameterName is null)
+ break;
+
+ if (parameter.GetCustomAttributesData()
+ .FirstOrDefault(x => x.AttributeType.FullName == Consts.NoosonParameterAttribute) is
+ { } newNameAttribute)
+ {
+ parameterName = (string)newNameAttribute.ConstructorArguments[0].Value!;
+ }
+
+ var matchedDeclarationKey
+ = localDeclarations
+ .FirstOrDefault(x => Helper.MatchIdentifierWithPropName(parameterName, x.Value.member.Name)).Key;
+
+ if (matchedDeclarationKey is null)
+ {
+ constructorMatch = false;
+ break;
+ }
+
+ ctorArguments.Add((matchedDeclarationKey, localDeclarations[matchedDeclarationKey].var));
+ }
+
+
+ if (constructorMatch)
+ {
+
+
+ return constructor;
+ }
+
+ ctorArguments.Clear();
+ }
+
+ throw new NotSupportedException();
+ }
+
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/RecursiveCallSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/RecursiveCallSerializer.cs
new file mode 100644
index 0000000..fe306ad
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/RecursiveCallSerializer.cs
@@ -0,0 +1,27 @@
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class RecursiveCallSerializer
+{
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var serializerMethod = generator.CurrentContext.Method;
+ if (!generator.ContextMap.TryGetValue(generator.CurrentContext.ValueType, out var context) || context == generator.CurrentContext || !context.IsTopLevel)
+ return false;
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.CurrentContext.GetValue!(generator.Il);
+ generator.Il.Emit(OpCodes.Call, serializerMethod);
+ return true;
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var deserializerMethod = generator.CurrentContext.Method;
+ if (!generator.ContextMap.TryGetValue(generator.CurrentContext.ValueType, out var context) || context == generator.CurrentContext || !context.IsTopLevel)
+ return false;
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Call, deserializerMethod);
+ generator.CurrentContext.SetValue!(generator.Il);
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/SpecialTypeSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/SpecialTypeSerializer.cs
new file mode 100644
index 0000000..cfd7c8b
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/SpecialTypeSerializer.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Reflection.Emit;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class SpecialTypeSerializer
+{
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+ var typeCode = Type.GetTypeCode(type);
+ if (typeCode != TypeCode.String && typeCode is < TypeCode.Boolean or > TypeCode.Double)
+ return false;
+ var serializerMethod = Helper.TryGetWrite(generator, type);
+ if (serializerMethod is null)
+ return false;
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.CurrentContext.GetValue!(generator.Il);
+
+ generator.Il.Emit(OpCodes.Callvirt, serializerMethod);
+ return true;
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+ var typeCode = Type.GetTypeCode(type);
+ if (typeCode != TypeCode.String && typeCode is < TypeCode.Boolean or > TypeCode.Double)
+ return false;
+ var deserializerMethod = Helper.TryGetRead(generator, type, typeCode);
+ if (deserializerMethod is null)
+ {
+ return false;
+ }
+
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.Il.Emit(OpCodes.Call, deserializerMethod);
+
+ generator.CurrentContext.SetValue!(generator.Il);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/Serializers/UnmanagedTypeSerializer.cs b/NonSucking.Framework.Serialization.Advanced/Serializers/UnmanagedTypeSerializer.cs
new file mode 100644
index 0000000..8f5efd0
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/Serializers/UnmanagedTypeSerializer.cs
@@ -0,0 +1,37 @@
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class UnmanagedTypeSerializer
+{
+
+ public static bool Serialize(SerializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+ if (!type.IsUnmanaged())
+ return false;
+
+ var serializerMethod = Helper.GetWrite(generator, type, "WriteUnmanaged");
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.CurrentContext.GetValue!(generator.Il);
+
+ generator.EmitCall(serializerMethod);
+
+ return true;
+ }
+ public static bool Deserialize(DeserializeGenerator generator, TypeNameCache.TypeConfig typeConfig, int baseRecursionDepth = int.MaxValue)
+ {
+ var type = generator.CurrentContext.ValueType.Type;
+ if (!type.IsUnmanaged())
+ return false;
+
+
+ var deserializerMethod = Helper.GetRead(generator, type, "ReadUnmanaged");
+
+ generator.CurrentContext.GetReaderWriter(generator.Il);
+ generator.EmitCall(deserializerMethod);
+ generator.CurrentContext.SetValue!(generator.Il);
+
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization.Advanced/TypeNameCache.cs b/NonSucking.Framework.Serialization.Advanced/TypeNameCache.cs
new file mode 100644
index 0000000..b85d800
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/TypeNameCache.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace NonSucking.Framework.Serialization.Advanced;
+
+internal class TypeNameCache
+{
+ internal struct TypeConfig
+ {
+ public TypeConfig(bool isCustomMethodCall, Type serializeType, Type deserializeType, AssemblyNameCache.Names names)
+ {
+ IsCustomMethodCall = isCustomMethodCall;
+ SerializeType = serializeType;
+ DeserializeType = deserializeType;
+ Names = names;
+ }
+
+ public bool IsCustomMethodCall { get; }
+ public Type SerializeType { get; }
+ public Type DeserializeType { get; }
+ public AssemblyNameCache.Names Names { get; }
+
+ public override string ToString()
+ {
+ return $"{SerializeType.Name}.{Names.SerializeName}/{DeserializeType.Name}.{Names.DeserializeName}";
+ }
+ }
+
+ internal static CustomAttributeData? GetFirstNoosonCustom(IList data)
+ {
+ return data.FirstOrDefault(
+ x => x.AttributeType.FullName == Consts.NoosonCustomAttribute);
+ }
+
+ private static TypeConfig GetTypeConfig(CustomAttributeData attr, AssemblyNameCache.Names names, Type type)
+ {
+ var deserializeName = AssemblyNameCache.FirstOrNull(attr.NamedArguments,
+ argument => argument.MemberName == "DeserializeMethodName")?.TypedValue.Value?.ToString();
+ var serializeName = AssemblyNameCache.FirstOrNull(attr.NamedArguments,
+ argument => argument.MemberName == "SerializeMethodName")?.TypedValue.Value?.ToString();
+ names = AssemblyNameCache.Names.Combine(names, new AssemblyNameCache.Names(serializeName, deserializeName, null));
+
+ var serializeType = AssemblyNameCache.FirstOrNull(attr.NamedArguments,
+ argument => argument.MemberName == "SerializeImplementationType")?.TypedValue.Value as Type ?? type;
+ var deserializeType = AssemblyNameCache.FirstOrNull(attr.NamedArguments,
+ argument => argument.MemberName == "DeserializeImplementationType")?.TypedValue.Value as Type ?? type;
+
+ return new TypeConfig(true, serializeType, deserializeType, names);
+ }
+
+ private static TypeConfig GetTypeConfig(Type type, AssemblyNameCache.Names names)
+ {
+ var typeAttr = GetFirstNoosonCustom(type.GetCustomAttributesData());
+ if (typeAttr is null)
+ return new TypeConfig(false, type, type, names);
+ return GetTypeConfig(typeAttr, names, type);
+ }
+ public static TypeConfig GetTypeConfig(Type type)
+ {
+ var names = AssemblyNameCache.ResolveAssemblyConfig(type.Assembly);
+ return GetTypeConfig(type, names);
+ }
+
+ public static TypeConfig GetTypeConfig(PropertyInfo prop)
+ {
+ var type = prop.PropertyType;
+ var names = AssemblyNameCache.ResolveAssemblyConfig(type.Assembly);
+ var propAttr = GetFirstNoosonCustom(prop.GetCustomAttributesData());
+ if (propAttr is null)
+ return GetTypeConfig(type, names);
+ return GetTypeConfig(propAttr, names, type);
+ }
+
+ public static TypeConfig GetTypeConfig(FieldInfo prop)
+ {
+ var type = prop.FieldType;
+ var names = AssemblyNameCache.ResolveAssemblyConfig(type.Assembly);
+ var propAttr = GetFirstNoosonCustom(prop.GetCustomAttributesData());
+ if (propAttr is null)
+ return GetTypeConfig(type, names);
+ return GetTypeConfig(propAttr, names, type);
+ }
+
+}
diff --git a/NonSucking.Framework.Serialization.Advanced/TypeSerializerException.cs b/NonSucking.Framework.Serialization.Advanced/TypeSerializerException.cs
new file mode 100644
index 0000000..0ba8cbe
--- /dev/null
+++ b/NonSucking.Framework.Serialization.Advanced/TypeSerializerException.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace NonSucking.Framework.Serialization;
+
+public class TypeSerializerException : Exception
+{
+ public TypeSerializerException(string message)
+ : base(message)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization/Diagnostics.cs b/NonSucking.Framework.Serialization/Diagnostics.cs
index 9b46ad7..86f2628 100644
--- a/NonSucking.Framework.Serialization/Diagnostics.cs
+++ b/NonSucking.Framework.Serialization/Diagnostics.cs
@@ -63,7 +63,7 @@ public static class Diagnostics
public static readonly DiagnosticInfo SingletonImplementationRequired = new(
0021,
"",
- "Singleton property or field 'Instance' required for type converters."
+ "Singleton property or field 'Instance' required for {0}."
);
public static readonly DiagnosticInfo NoValidConverter = new(
@@ -83,6 +83,18 @@ public static class Diagnostics
"",
"Custom method call is not compatible with serializer of type '{0}'"
);
+
+ public static readonly DiagnosticInfo InvalidDynamicResolver = new(
+ 0024,
+ "",
+ "Resolver of '{0}' does not implement necessary {1} interface and is not a valid dynamic resolver."
+ );
+
+ public static readonly DiagnosticInfo InvalidOpenGenericResolver = new(
+ 0025,
+ "",
+ "Resolver of '{0}' is a open generic type{1} which is not supported for resolvers."
+ );
public record DiagnosticInfo(int Id, string Title, string FormatString)
{
diff --git a/NonSucking.Framework.Serialization/Extensions.cs b/NonSucking.Framework.Serialization/Extensions.cs
new file mode 100644
index 0000000..43d6c8e
--- /dev/null
+++ b/NonSucking.Framework.Serialization/Extensions.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+
+namespace NonSucking.Framework.Serialization;
+
+public static class Extensions
+{
+ public static T? FirstOrNull(this IEnumerable enumerable, Func predicate)
+ where T : struct
+ {
+ foreach (var v in enumerable)
+ {
+ if (predicate(v))
+ return v;
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization/Helper.cs b/NonSucking.Framework.Serialization/Helper.cs
index 334e193..eed4b37 100644
--- a/NonSucking.Framework.Serialization/Helper.cs
+++ b/NonSucking.Framework.Serialization/Helper.cs
@@ -14,6 +14,7 @@
using VaVare.Generators.Common.Arguments.ArgumentTypes;
using VaVare.Models;
+using VaVare.Statements;
namespace NonSucking.Framework.Serialization
{
@@ -38,11 +39,11 @@ internal static void Reset()
internal static ITypeParameterSymbol? GetTypeParameter(IParameterSymbol parameter)
{
- return parameter as ITypeParameterSymbol;
+ return parameter.Type as ITypeParameterSymbol;
}
- internal static INamedTypeSymbol? GetParameterTypeConstraint(IParameterSymbol parameter)
+ internal static IEnumerable? GetParameterTypeConstraints(IParameterSymbol parameter)
{
- return GetTypeParameter(parameter)?.ConstraintTypes.OfType().FirstOrDefault();
+ return GetTypeParameter(parameter)?.ConstraintTypes.OfType();
}
internal static List? GetTypeParameterConstraints(GeneratedMethodParameter parameter, GeneratedMethod method)
@@ -50,31 +51,41 @@ internal static void Reset()
return method.TypeParameterConstraints.FirstOrDefault(y => y.Identifier == parameter.Type)?.Constraints;
}
- internal static TypeParameterConstraint? GetParameterTypeConstraint(GeneratedMethodParameter parameter,
+ internal static IEnumerable? GetParameterTypeConstraints(GeneratedMethodParameter parameter,
GeneratedMethod method)
{
- return GetTypeParameterConstraints(parameter, method)
- ?.FirstOrDefault(x => x.Type == TypeParameterConstraint.ConstraintType.Type);
+ return GetTypeParameterConstraints(parameter, method)?
+ .Where(x => x.Type == TypeParameterConstraint.ConstraintType.Type);
}
internal static bool MatchReaderWriterParameter(NoosonGeneratorContext context, IParameterSymbol readerParam)
{
return MatchReaderWriterParameter(context, readerParam.Type.ToDisplayString(),
- GetParameterTypeConstraint(readerParam)?.ToDisplayString());
+ readerParam.ContainingSymbol is IMethodSymbol { IsGenericMethod: true },
+ GetParameterTypeConstraints(readerParam)?.Select(x => x.ToDisplayString()));
}
internal static bool MatchReaderWriterParameter(NoosonGeneratorContext context, GeneratedMethodParameter readerParam, GeneratedMethod method)
{
- return MatchReaderWriterParameter(context, readerParam.Type,
- GetParameterTypeConstraint(readerParam, method)?.ConstraintIdentifier);
+ return MatchReaderWriterParameter(context, readerParam.Type, method.IsGenericMethod,
+ GetParameterTypeConstraints(readerParam, method)?.Select(x => x.ConstraintIdentifier));
}
- internal static bool MatchReaderWriterParameter(NoosonGeneratorContext context, string baseParameterTypeName, string? typeConstraint)
+ internal static bool MatchReaderWriterParameter(NoosonGeneratorContext context, string baseParameterTypeName, bool isGeneric, IEnumerable? typeConstraints)
{
- var readerOrWriter = context.ReaderTypeName ?? context.WriterTypeName;
+ var readerOrWriter = context.MethodType == MethodType.Serialize ? context.WriterTypeName : context.ReaderTypeName;
var matchGenericReader = readerOrWriter is null;
if (matchGenericReader)
- return typeConstraint is not null &&
- typeConstraint == Consts.GenericParameterReaderInterfaceFull;
+ {
+ readerOrWriter = (context.MethodType == MethodType.Serialize
+ ? Consts.GenericParameterWriterInterfaceFull
+ : Consts.GenericParameterReaderInterfaceFull);
+ if (isGeneric)
+ return typeConstraints is null || typeConstraints.Any(x => x == readerOrWriter);
+
+ }
+ // TODO: match generic other way round
+ // Serialize(IBinaryWriter) should also be applicable for Serialize(T) where T : IBinaryWriter
+
return baseParameterTypeName == readerOrWriter;
}
@@ -517,10 +528,56 @@ internal static bool IsAssignable(ITypeSymbol typeToAssignTo, ITypeSymbol? typeT
return false;
if (SymbolEqualityComparer.Default.Equals(typeToAssignFrom, typeToAssignTo))
return true;
+ if (typeToAssignTo is ITypeParameterSymbol typeParameterSymbol)
+ {
+ if (typeParameterSymbol.HasValueTypeConstraint && !typeToAssignFrom.IsValueType
+ || typeParameterSymbol.HasReferenceTypeConstraint && !typeToAssignFrom.IsReferenceType
+ || typeParameterSymbol.HasUnmanagedTypeConstraint && !typeToAssignFrom.IsUnmanagedType
+ || typeParameterSymbol.HasNotNullConstraint && typeToAssignFrom.NullableAnnotation == NullableAnnotation.Annotated)
+ return false;
+ foreach (var constraintType in typeParameterSymbol.ConstraintTypes)
+ {
+ if (!IsAssignable(constraintType, typeToAssignFrom))
+ return false;
+ }
+
+ if (typeParameterSymbol.HasConstructorConstraint && !typeToAssignFrom.GetMembers()
+ .Any(x => x is IMethodSymbol { Parameters.Length: 0, Name: ".ctor" }))
+ return false;
+ return true;
+ }
var assignable = IsAssignable(typeToAssignTo, typeToAssignFrom.BaseType);
if (!assignable && typeToAssignTo.IsAbstract)
return typeToAssignFrom.Interfaces.Any(x => IsAssignable(typeToAssignTo, x));
return assignable;
}
+
+ internal static (MemberInfo tempVariable, string tempVariableAccessorName) CreateTempIfNeeded(MemberInfo property, GeneratedSerializerCode statements)
+ {
+ var needsTemp = property.Parent != "";
+ var tempVariable = needsTemp
+ ? property with { Name = Helper.GetRandomNameFor(property.Name, property.Parent), Parent = "" }
+ : property;
+ var tempVariableAccessorName = Helper.GetMemberAccessString(tempVariable);
+ var setTempVariable = Statement.Declaration.DeclareAndAssign(
+ tempVariableAccessorName,
+ SyntaxFactory.IdentifierName(Helper.GetMemberAccessString(property)));
+ if (needsTemp)
+ statements.Statements.Add(setTempVariable);
+ return (tempVariable, tempVariableAccessorName);
+ }
+
+ internal static bool CheckSingleton(NoosonGeneratorContext context, ITypeSymbol type)
+ {
+ var instanceAccessor = Helper.GetFirstMemberWithBase(type,
+ (x) => x is IPropertySymbol or IFieldSymbol { Name: "Instance", IsStatic: true }
+ && x.DeclaredAccessibility is Accessibility.Internal or Accessibility.Public or Accessibility.ProtectedOrInternal, context);
+ if (instanceAccessor is null)
+ {
+ return false;
+ }
+
+ return true;
+ }
}
}
diff --git a/NonSucking.Framework.Serialization/NonSucking.Framework.Serialization.csproj b/NonSucking.Framework.Serialization/NonSucking.Framework.Serialization.csproj
index 61af0c9..1672e7f 100644
--- a/NonSucking.Framework.Serialization/NonSucking.Framework.Serialization.csproj
+++ b/NonSucking.Framework.Serialization/NonSucking.Framework.Serialization.csproj
@@ -1,6 +1,6 @@
-
+
netstandard2.0
11.0
diff --git a/NonSucking.Framework.Serialization/NoosonGenerator.cs b/NonSucking.Framework.Serialization/NoosonGenerator.cs
index 319492b..64e82f1 100644
--- a/NonSucking.Framework.Serialization/NoosonGenerator.cs
+++ b/NonSucking.Framework.Serialization/NoosonGenerator.cs
@@ -223,7 +223,7 @@ private static string TypeNameToSummaryName(string typeName)
private static void InternalExecute(SourceProductionContext sourceProductionContext, (Compilation Compilation,
ImmutableArray VisitInfos) source, List templates, GlobalContext gc)
{
-
+ gc = gc with { Compilation = source.Compilation };
var configAttribute
= source.Compilation.Assembly.GetAttribute(AttributeTemplates.NoosonConfiguration);
@@ -236,16 +236,17 @@ var configAttribute
source.Compilation.GetTypeByMetadataName(Consts.GenericParameterReaderInterfaceFull) is not null;
foreach (Template template in templates)
{
- if (template.Kind == TemplateKind.AdditionalSource)
+ if (template.Kind == TemplateKind.AdditionalSource && template.CheckConditionalInclude(gc))
+ {
//&& source.Compilation.GetTypeByMetadataName(template.FullName) is ITypeSymbol ts TODO: Needs work, detecting existing public, same namespace and more
sourceProductionContext.AddSource(template.Name, template.ToString());
+ }
}
//TODO: When caching somewhere somehow than this bad
gc.Clean();
Helper.Reset();
- gc = gc with { Compilation = source.Compilation };
static int CountBaseTypes(ITypeSymbol symbol, int counter = 0)
{
@@ -423,19 +424,19 @@ internal static void GenerateForTypeSymbol(SourceProductionContext sourceProduct
{
if (generateDefaultWriter)
AddSerializeMethods(generatedType, typeSymbol,
- serializeContext with { WriterTypeName = null });
+ serializeContext with { WriterTypeName = null, ReaderTypeName = null });
if (generateDefaultReader)
AddDeserializeMethods(generatedType, typeSymbol,
- deserializeContext with { ReaderTypeName = null });
+ deserializeContext with { WriterTypeName = null, ReaderTypeName = null });
}
else
{
if (generateDefaultWriter)
AddSerializeMethods(generatedType, typeSymbol,
- serializeContext with { WriterTypeName = binaryWriterName });
+ serializeContext with { WriterTypeName = binaryWriterName, ReaderTypeName = binaryReaderName });
if (generateDefaultReader)
AddDeserializeMethods(generatedType, typeSymbol,
- deserializeContext with { ReaderTypeName = binaryReaderName });
+ deserializeContext with { WriterTypeName = binaryWriterName, ReaderTypeName = binaryReaderName });
}
foreach (var directWriter in directWriters)
diff --git a/NonSucking.Framework.Serialization/Properties/launchSettings.json b/NonSucking.Framework.Serialization/Properties/launchSettings.json
index 26dd110..ad2b16b 100644
--- a/NonSucking.Framework.Serialization/Properties/launchSettings.json
+++ b/NonSucking.Framework.Serialization/Properties/launchSettings.json
@@ -2,7 +2,7 @@
"profiles": {
"NonSucking.Framework.Serialization": {
"commandName": "DebugRoslynComponent",
- "targetProject": "..\\DEMO\\DEMO.csproj"
+ "targetProject": "../DEMO/DEMO.csproj"
}
}
}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization/Serializers/CtorSerializer.cs b/NonSucking.Framework.Serialization/Serializers/CtorSerializer.cs
index c964dbd..8f2a792 100644
--- a/NonSucking.Framework.Serialization/Serializers/CtorSerializer.cs
+++ b/NonSucking.Framework.Serialization/Serializers/CtorSerializer.cs
@@ -28,7 +28,7 @@ internal enum Initializer
internal static class CtorSerializer
{
- internal static GeneratedSerializerCode CallCtorAndSetProps(INamedTypeSymbol typeSymbol, List localVariableNames, MemberInfo instance, string instanceName, Initializer initializer)
+ internal static GeneratedSerializerCode? CallCtorAndSetProps(ITypeSymbol typeSymbol, List localVariableNames, MemberInfo instance, string instanceName, Initializer initializer, NoosonGeneratorContext context)
{
List localDeclarations = GetLocalDeclarations(localVariableNames, instanceName);
@@ -46,9 +46,11 @@ internal static GeneratedSerializerCode CallCtorAndSetProps(INamedTypeSymbol typ
ArgumentListSyntax? ctorArguments = null;
if ((initializer & Initializer.Ctor) > 0)
{
- List constructors = GetCtors(typeSymbol);
+ List? constructors = GetCtors(typeSymbol, context);
ctorArguments
= GetStatementForCtorCall(constructors, localDeclarations, out ctorArgumentNames);
+ if (ctorArguments is null)
+ return null;
}
@@ -78,15 +80,33 @@ var currentType
return ret;
}
- private static List GetCtors(INamedTypeSymbol typeSymbol)
+ private static List? GetCtors(ITypeSymbol typeSymbol, NoosonGeneratorContext context)
{
- var constructors
- = typeSymbol
- .Constructors
- .OrderByDescending(constructor =>
- (constructor.TryGetAttribute(AttributeTemplates.PreferredCtor, out _) ? 0xFFFF1 : 0) //0xFFFF is the maximum amount of Parameters, so we add an additional one
- + constructor.Parameters.Length)
- .ToList();
+ List constructors;
+ if (typeSymbol is INamedTypeSymbol namedTypeSymbol)
+ {
+ constructors = namedTypeSymbol
+ .Constructors
+ .OrderByDescending(constructor =>
+ (constructor.TryGetAttribute(AttributeTemplates.PreferredCtor, out _) ? 0xFFFF1 : 0) //0xFFFF is the maximum amount of Parameters, so we add an additional one
+ + constructor.Parameters.Length)
+ .ToList();
+ }
+ else if (typeSymbol is ITypeParameterSymbol { HasConstructorConstraint: true } typeParameterSymbol && context.GlobalContext.Compilation.GetTypeByMetadataName("System.Activator") is {} activator)
+ {
+ var createI = activator.GetMembers("CreateInstance")
+ .Where(x => x is IMethodSymbol { TypeParameters.Length: 1 } m).OfType().FirstOrDefault();
+ if (createI is not null)
+ {
+ return new List() { createI.Construct(typeParameterSymbol) };
+ }
+
+ return null;
+ }
+ else
+ {
+ constructors = new List();
+ }
return constructors;
}
@@ -217,9 +237,11 @@ var declaration
return properties;
}
- internal static ArgumentListSyntax GetStatementForCtorCall(List constructors, List localDeclarations, out List ctorArguments)
+ internal static ArgumentListSyntax? GetStatementForCtorCall(List? constructors, List localDeclarations, out List ctorArguments)
{
ctorArguments = new List();
+ if (constructors is null)
+ return null;
if (constructors.Count == 0)
{
return SyntaxFactory.ArgumentList();
diff --git a/NonSucking.Framework.Serialization/Serializers/CustomMethodCallSerializer.cs b/NonSucking.Framework.Serialization/Serializers/CustomMethodCallSerializer.cs
index f52461c..b509421 100644
--- a/NonSucking.Framework.Serialization/Serializers/CustomMethodCallSerializer.cs
+++ b/NonSucking.Framework.Serialization/Serializers/CustomMethodCallSerializer.cs
@@ -27,7 +27,7 @@ internal static class CustomMethodCallSerializer
{
typeToCallOn = customType;
- matchAdditionalParam = context.WriterTypeName is not null;
+ matchAdditionalParam = context.MethodType == MethodType.Serialize;
}
else if (isClassAttribute
|| (property.Symbol is not IPropertySymbol
diff --git a/NonSucking.Framework.Serialization/Serializers/DynamicTypeSerializer.cs b/NonSucking.Framework.Serialization/Serializers/DynamicTypeSerializer.cs
index 90f8770..9180cab 100644
--- a/NonSucking.Framework.Serialization/Serializers/DynamicTypeSerializer.cs
+++ b/NonSucking.Framework.Serialization/Serializers/DynamicTypeSerializer.cs
@@ -6,7 +6,6 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using NonSucking.Framework.Serialization.Attributes;
using NonSucking.Framework.Serialization.Serializers;
using VaVare.Generators.Common;
@@ -16,7 +15,7 @@
namespace NonSucking.Framework.Serialization
{
[StaticSerializer(11)]
- public class DynamicTypeSerializer
+ public static class DynamicTypeSerializer
{
private static bool TryGetAttribute(ITypeSymbol? symbol, out AttributeData? attr)
{
@@ -32,18 +31,20 @@ private static bool TryGetAttribute(ITypeSymbol? symbol, out AttributeData? attr
return true;
}
- private static bool IsValidType(MemberInfo property, out IEnumerable? possibleTypes)
+ private static bool IsValidType(MemberInfo property, out IEnumerable? possibleTypes, out ITypeSymbol? resolverType, out AttributeData? attr)
{
- if (!property.Symbol.TryGetAttribute(AttributeTemplates.DynamicType, out var attr))
+ if (!property.Symbol.TryGetAttribute(AttributeTemplates.DynamicType, out attr))
{
if (!TryGetAttribute(property.TypeSymbol, out attr))
{
possibleTypes = null;
+ resolverType = null;
return false;
}
}
possibleTypes = attr!.ConstructorArguments[0].Values.Select(x => x.Value).OfType();
+ resolverType = attr.NamedArguments.FirstOrNull(x => x.Key == "Resolver")?.Value.Value as ITypeSymbol;
return true;
}
private static ThrowStatementSyntax ThrowException(string exceptionName)
@@ -66,25 +67,31 @@ internal static Continuation TrySerialize(ref MemberInfo property, NoosonGenerat
GeneratedSerializerCode statements, ref SerializerMask includedSerializers,
int baseTypesLevelProperties = int.MaxValue)
{
- if (!IsValidType(property, out var possibleTypes))
+ if (!IsValidType(property, out var possibleTypes, out var resolverType, out var dynamicTypeAttribute))
return Continuation.NotExecuted;
+
+ if (!TryGetResolver(context, resolverType, dynamicTypeAttribute, out var resolver))
+ {
+ return Continuation.NotExecuted;
+ }
+ var (tempVariable, tempVariableAccessorName) = Helper.CreateTempIfNeeded(property, statements);
int typeId = 0;
var switchSections = new List();
- var castedName = Helper.GetRandomNameFor("casted", property.CreateUniqueName());
+ var castedName = Helper.GetRandomNameFor("casted", tempVariable.CreateUniqueName());
foreach (var t in possibleTypes!)
{
typeId++;
- if (!Helper.IsAssignable(property.TypeSymbol, t))
+ if (!Helper.IsAssignable(tempVariable.TypeSymbol, t))
continue;
var newMemberInfo =
- property with { Name = castedName, TypeSymbol = t.WithNullableAnnotation(property.TypeSymbol.NullableAnnotation), Parent = "" };
+ tempVariable with { Name = castedName, TypeSymbol = t.WithNullableAnnotation(tempVariable.TypeSymbol.NullableAnnotation), Parent = "" };
var innerSerialize = NoosonGenerator.CreateStatementForSerializing(newMemberInfo, context, writerName, includedSerializers, SerializerMask.DynamicTypeSerializer);
@@ -107,42 +114,289 @@ var invocationExpression
));
}
+
+ SyntaxList defaultCaseStatements;
+
+ if (resolver is null)
+ {
+ defaultCaseStatements = SyntaxFactory.SingletonList(ThrowNotSupportedException());
+ }
+ else
+ {
+ var resolverVal = resolver.Value;
+ var writeInvalidTypeId = Statement
+ .Expression
+ .Invoke(writerName, "Write", new[] { new ValueArgument(0) })
+ .AsStatement();
+ var solveMethod = GetSolveResolve(resolverVal, "Solve", context.WriterTypeName ?? Consts.GenericParameterWriterName);
+
+ var getType = SyntaxFactory.InvocationExpression(SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName(tempVariableAccessorName),
+ SyntaxFactory.IdentifierName("GetType")
+ ));
+ var resolveInfoName = Helper.GetRandomNameFor("serializeResolve");
+
+ var assignment = GetMappedSolveResolve(resolveInfoName, solveMethod, getType);
+
+ var dummySymbol =
+ context.GlobalContext.Compilation.GetTypeByMetadataName(AttributeTemplates.GenSerializationAttribute
+ .FullName);
+ var m = new MemberInfo(resolverVal.identifierType, dummySymbol!, "Identifier",resolveInfoName);
+ var innerSerialize = NoosonGenerator.CreateStatementForSerializing(m, context, writerName);
+ context.GeneratedFile.Usings.Add("System.Runtime.CompilerServices");
+
+ var accessSerializerDelegate = SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName(resolveInfoName),
+ SyntaxFactory.IdentifierName("Serialize"));
+ var delegateType = GetDelegateType(context, property);
+ var castedDelegateName = Helper.GetRandomNameFor("serializeCall");
+ var castedDelegate = CreateCastedDelegate(castedDelegateName, delegateType, accessSerializerDelegate);
+
+ var suppressNullableCallDelegate = SyntaxFactory.PostfixUnaryExpression(
+ SyntaxKind.SuppressNullableWarningExpression,
+ SyntaxFactory.IdentifierName(castedDelegateName));
+ var callSerialize = SyntaxFactory.ExpressionStatement(SyntaxFactory.InvocationExpression(suppressNullableCallDelegate)
+ .WithArgumentList(SyntaxFactory.ArgumentList(
+ SyntaxFactory.SeparatedList(new[]
+ {
+ SyntaxFactory.Argument(
+ SyntaxFactory.IdentifierName(writerName)),
+ SyntaxFactory.Argument(
+ SyntaxFactory.IdentifierName(
+ Helper.GetMemberAccessString(property)))
+ }))));
+
+ defaultCaseStatements = SyntaxFactory.List(
+ innerSerialize.ToMergedBlock()
+ .Prepend(assignment)
+ .Prepend(writeInvalidTypeId)
+ .Append(castedDelegate)
+ .Append(callSerialize)
+ .Append(SyntaxFactory.BreakStatement()));
+ }
+
switchSections.Add(SyntaxFactory.SwitchSection(
SyntaxFactory.SingletonList(SyntaxFactory.DefaultSwitchLabel()),
- SyntaxFactory.SingletonList(ThrowNotSupportedException())));
+ defaultCaseStatements));
- statements.Statements.Add(SyntaxFactory.SwitchStatement(SyntaxFactory.IdentifierName(property.FullName))
+ statements.Statements.Add(SyntaxFactory.SwitchStatement(SyntaxFactory.IdentifierName(tempVariableAccessorName))
.WithSections(new SyntaxList(
switchSections
)));
return Continuation.Done;
}
- private static INamedTypeSymbol ResolveType(Compilation compilation, Type? type)
+ private static GenericNameSyntax GetDelegateType(NoosonGeneratorContext context, MemberInfo property)
+ {
+ bool serialize = context.MethodType == MethodType.Serialize;
+ var readerWriterName = serialize
+ ? (context.WriterTypeName ?? Consts.GenericParameterWriterName)
+ : (context.ReaderTypeName ?? Consts.GenericParameterReaderName);
+ return SyntaxFactory.GenericName(serialize ? "Action" : "Func")
+ .WithTypeArgumentList(SyntaxFactory.TypeArgumentList(
+ SyntaxFactory.SeparatedList(new[]
+ {
+ SyntaxFactory.ParseTypeName(readerWriterName),
+ SyntaxFactory.ParseTypeName(property.TypeSymbol
+ .ToDisplayString())
+ })));
+ }
+
+ static LocalDeclarationStatementSyntax GetMappedSolveResolve(string resolveInfoName, ExpressionSyntax solveResolveMethod, ExpressionSyntax mappingIdentifier)
+ {
+ return Statement.Declaration.DeclareAndAssign(resolveInfoName,
+ SyntaxFactory.InvocationExpression(
+ solveResolveMethod,
+ SyntaxFactory.ArgumentList(
+ SyntaxFactory.SeparatedList(new[]
+ {
+ SyntaxFactory.Argument(mappingIdentifier)
+ }))));
+ }
+
+ static MemberAccessExpressionSyntax GetSolveResolve((SimpleNameSyntax identifier, SimpleNameSyntax interfaceTypeIdentifier, ITypeSymbol identifierType) resolver, string methodName, string readerWriterTypeName)
+ {
+ var instanceAccess = SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ resolver.identifier,
+ SyntaxFactory.IdentifierName("Instance")
+ );
+ var instanceAccessCasted = SyntaxFactory.ParenthesizedExpression(
+ SyntaxFactory.CastExpression(resolver.interfaceTypeIdentifier!, instanceAccess));
+ return SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ instanceAccessCasted,
+ SyntaxFactory.GenericName(methodName)
+ .WithTypeArgumentList(
+ SyntaxFactory.TypeArgumentList(
+ SyntaxFactory.SingletonSeparatedList(
+ SyntaxFactory.IdentifierName(readerWriterTypeName))))
+ );
+ }
+
+ static LocalDeclarationStatementSyntax CreateCastedDelegate(string variableName, TypeSyntax delegateType, ExpressionSyntax accessDelegate)
{
- var fullName = type?.FullName;
- if (fullName is null)
- throw new ArgumentException();
- var resolvedType = compilation.GetTypeByMetadataName(fullName);
- if (resolvedType is null)
- throw new NotSupportedException($"Unresolvable types are not supported: Could not resolve '{fullName}'");
-
- return resolvedType;
+ var unsafeAs = SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName("Unsafe"),
+ SyntaxFactory.GenericName("As")
+ .WithTypeArgumentList(SyntaxFactory.TypeArgumentList(
+ SyntaxFactory.SeparatedList(new[] { delegateType })))
+ );
+ var unsafeAsCall = SyntaxFactory.InvocationExpression(
+ unsafeAs,
+ SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[]
+ {
+ SyntaxFactory.Argument(
+ accessDelegate)
+ })));
+
+ return Statement.Declaration.DeclareAndAssign(variableName, unsafeAsCall);
+ }
+
+ private static ITypeSymbol ConstructGenericType(ITypeSymbol type, INamedTypeSymbol? resolverTypeSymbol)
+ {
+ if (type is ITypeParameterSymbol typeParameterSymbol)
+ {
+ if (resolverTypeSymbol is null)
+ return type;
+ var matchedTypeParam =
+ resolverTypeSymbol.TypeParameters.Select((param, index) => (param, index)).FirstOrDefault(
+ x => SymbolEqualityComparer.Default.Equals(x.param, typeParameterSymbol));
+ return resolverTypeSymbol.TypeArguments[matchedTypeParam.index];
+ }
+
+ if (type is INamedTypeSymbol { TypeParameters.Length: > 0 } genericTypeParam)
+ {
+ var resolved = new ITypeSymbol[genericTypeParam.TypeParameters.Length];
+ for (int i = 0; i < resolved.Length; i++)
+ {
+ resolved[i] = ConstructGenericType(genericTypeParam.TypeArguments[i], resolverTypeSymbol);
+ }
+ return genericTypeParam.OriginalDefinition.Construct(resolved);
+ }
+
+ return type;
+ }
+
+ private static bool TryGetResolver(NoosonGeneratorContext context, ITypeSymbol? resolverType, AttributeData? attrData,
+ out (SimpleNameSyntax identifier, SimpleNameSyntax interfaceTypeIdentifier, ITypeSymbol identifierType)? resolver)
+ {
+ resolver = null;
+ if (resolverType is not null)
+ {
+ const string ResolverInterface =
+ "NonSucking.Framework.Serialization.INoosonRuntimeTypeResolver";
+ var baseInterface = resolverType.OriginalDefinition.AllInterfaces.FirstOrDefault(
+ x => x.OriginalDefinition.ToDisplayString() == ResolverInterface);
+
+ if (baseInterface is null)
+ {
+ context.AddDiagnostic(Diagnostics.InvalidDynamicResolver.Format(resolverType, ResolverInterface), attrData?.ApplicationSyntaxReference?.GetLocation() ?? Location.None, DiagnosticSeverity.Error);
+ return false;
+ }
+
+ INamedTypeSymbol? resolverTypeSymbol = resolverType as INamedTypeSymbol;
+
+ if (resolverTypeSymbol is not null && resolverTypeSymbol.IsUnboundGenericType)
+ {
+ if (resolverTypeSymbol.TypeParameters.Length > 0)
+ {
+ context.AddDiagnostic(Diagnostics.InvalidOpenGenericResolver.Format(resolverType, " without any type parameters"), resolverType,
+ DiagnosticSeverity.Error);
+ return false;
+ }
+
+ static Continuation CheckAndResolve(NoosonGeneratorContext context, string? typeName, string genericName, ITypeParameterSymbol typeParameter, ITypeSymbol typeArgument, ref ITypeSymbol res)
+ {
+ if (!SymbolEqualityComparer.Default.Equals(typeParameter, typeArgument))
+ return Continuation.Continue;
+ var compilation = context.GlobalContext.Compilation;
+ if (typeName is not null && compilation.GetTypeByMetadataName(typeName) is { } resolvedTypeSymbol)
+ {
+ if (!Helper.IsAssignable(typeArgument, resolvedTypeSymbol))
+ {
+ return Continuation.NotExecuted;
+ }
+ res = resolvedTypeSymbol;
+ }
+ else
+ {
+ res = compilation.CreateErrorTypeSymbol(null, genericName, 0);
+ }
+
+ return Continuation.Done;
+ }
+
+ var res = new ITypeSymbol[resolverTypeSymbol.TypeParameters.Length];
+ for (int i = 0; i < res.Length; i++)
+ {
+ var typeParam = resolverTypeSymbol.TypeParameters[i];
+ var matched = CheckAndResolve(context, context.ReaderTypeName, Consts.GenericParameterReaderName,
+ typeParam, baseInterface.TypeArguments[0], ref res[i]);
+ if (matched == Continuation.Continue)
+ {
+ matched = CheckAndResolve(context, context.WriterTypeName, Consts.GenericParameterWriterName,
+ typeParam, baseInterface.TypeArguments[1], ref res[i]);
+ }
+
+ if (matched != Continuation.Done)
+ {
+ context.AddDiagnostic(Diagnostics.InvalidOpenGenericResolver.Format(resolverType, " and the open parameter could not be resolved"), resolverType,
+ DiagnosticSeverity.Error);
+ return false;
+ }
+ }
+
+ resolverType = resolverTypeSymbol = resolverTypeSymbol.OriginalDefinition.Construct(res);
+ }
+
+ var baseInterfaceConstr = (INamedTypeSymbol)ConstructGenericType(baseInterface, resolverTypeSymbol);
+ var identifierType = baseInterfaceConstr.TypeArguments.First();
+
+ if (identifierType is ITypeParameterSymbol)
+ {
+ context.AddDiagnostic(Diagnostics.InvalidOpenGenericResolver.Format(resolverType, ""), resolverType,
+ DiagnosticSeverity.Error);
+ return false;
+ }
+
+ if (!Helper.CheckSingleton(context, resolverType.OriginalDefinition))
+ {
+ context.AddDiagnostic(Diagnostics.SingletonImplementationRequired.Format("type resolvers"), resolverType,
+ DiagnosticSeverity.Error);
+ return false;
+ }
+
+ var interfaceTypeIdentifier = SyntaxFactory.IdentifierName(baseInterfaceConstr.ToDisplayString());
+
+ var resolverIdentifier = SyntaxFactory.IdentifierName(resolverType.ToDisplayString());
+
+ resolver = (resolverIdentifier, interfaceTypeIdentifier, identifierType);
+ }
+
+ return true;
}
internal static Continuation TryDeserialize(ref MemberInfo property, NoosonGeneratorContext context, string readerName,
GeneratedSerializerCode statements, ref SerializerMask includedSerializers,
int baseTypesLevelProperties = int.MaxValue)
{
- if (!IsValidType(property, out var possibleTypes))
+ if (!IsValidType(property, out var possibleTypes, out var resolverType, out var dynamicTypeAttr))
return Continuation.NotExecuted;
-
+ if (!TryGetResolver(context, resolverType, dynamicTypeAttr, out var resolver))
+ {
+ return Continuation.NotExecuted;
+ }
var invocationExpression
= Statement
.Expression
.Invoke(readerName, "ReadInt32")
.AsExpression();
var typeIdName = Helper.GetRandomNameFor("typeID", property.CreateUniqueName());
+ var typeIdIdentifier = SyntaxFactory.IdentifierName(typeIdName);
statements.Statements.Add(Statement.Declaration.DeclareAndAssign(typeIdName, invocationExpression));
var propName = property.CreateUniqueName();
@@ -151,18 +405,45 @@ var invocationExpression
int typeId = 0;
- var switchSections = new List();
+ var ifSections = new List();
+
+ static ExpressionSyntax CompareTypeId(TypeSyntax identifierType, IdentifierNameSyntax typeIdIdentifier, ExpressionSyntax typeIdLiteral)
+ {
+ return SyntaxFactory.InvocationExpression(
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.GenericName(
+ SyntaxFactory.Identifier("EqualityComparer"))
+ .WithTypeArgumentList(
+ SyntaxFactory.TypeArgumentList(
+ SyntaxFactory.SingletonSeparatedList(
+ identifierType))),
+ SyntaxFactory.IdentifierName("Default")),
+ SyntaxFactory.IdentifierName("Equals")))
+ .WithArgumentList(SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[]
+ {
+ SyntaxFactory.Argument(typeIdIdentifier),
+ SyntaxFactory.Argument(typeIdLiteral)
+ })));
+ }
+
+ var typeIdType = SyntaxFactory.ParseTypeName("int");
foreach (var t in possibleTypes!)
{
typeId++;
+ var typeIdLiteral = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression,
+ SyntaxFactory.Literal(typeId));
if (!Helper.IsAssignable(property.TypeSymbol, t))
{
- switchSections.Add(SyntaxFactory.SwitchSection(
- SyntaxFactory.SingletonList(SyntaxFactory.CaseSwitchLabel(
- SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(typeId)))),
- SyntaxFactory.SingletonList(ThrowInvalidCastException())));
+ ifSections.Add(
+ SyntaxFactory.IfStatement(
+ CompareTypeId(typeIdType, typeIdIdentifier, typeIdLiteral),
+ SyntaxFactory.Block(ThrowInvalidCastException())
+ ));
continue;
}
@@ -175,23 +456,81 @@ var invocationExpression
innerDeserialize.Statements.Add(Statement.Declaration.Assign(propName, SyntaxFactory.IdentifierName(resValue.UniqueName)));
- var b = BodyGenerator.Create(innerDeserialize.ToMergedBlock().Append(SyntaxFactory.BreakStatement()).ToArray());
- switchSections.Add(SyntaxFactory.SwitchSection(
- SyntaxFactory.SingletonList(
- SyntaxFactory.CaseSwitchLabel(
- SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(typeId)))),
- SyntaxFactory.SingletonList(b)
+ var b = SyntaxFactory.Block(innerDeserialize.ToMergedBlock());
+ ifSections.Add(
+ SyntaxFactory.IfStatement(
+ CompareTypeId(typeIdType, typeIdIdentifier, typeIdLiteral),
+ b
));
}
+
+
+ List defaultCaseStatement = new();
+
+ if (resolver is null)
+ {
+ defaultCaseStatement.Add(ThrowNotSupportedException());
+ }
+ else
+ {
+ var resolverVal = resolver.Value;
+ var resolveMethod = GetSolveResolve(resolverVal, "Resolve", context.ReaderTypeName ?? Consts.GenericParameterReaderName);
+
+ var dummySymbol =
+ context.GlobalContext.Compilation.GetTypeByMetadataName(AttributeTemplates.GenSerializationAttribute
+ .FullName);
+
+ var resolveInfoName = Helper.GetRandomNameFor("serializeResolve");
+ var m = new MemberInfo(resolverVal.identifierType, dummySymbol!, "Identifier",resolveInfoName);
+ var innerDeserialize = NoosonGenerator.CreateStatementForDeserializing(m, context, readerName);
+ var identifierVariable = innerDeserialize.VariableDeclarations.Single();
+
+ var assignment = GetMappedSolveResolve(
+ resolveInfoName,
+ resolveMethod,
+ SyntaxFactory.IdentifierName(identifierVariable.UniqueName));
+
+ context.GeneratedFile.Usings.Add("System.Runtime.CompilerServices");
+
+ var accessSerializerDelegate = SyntaxFactory.MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ SyntaxFactory.IdentifierName(resolveInfoName),
+ SyntaxFactory.IdentifierName("Deserialize"));
+ var delegateType = GetDelegateType(context, property);
+ var castedDelegateName = Helper.GetRandomNameFor("deserializeCall");
+ var castedDelegate = CreateCastedDelegate(castedDelegateName, delegateType, accessSerializerDelegate);
+
+ var suppressNullableCallDelegate = SyntaxFactory.PostfixUnaryExpression(
+ SyntaxKind.SuppressNullableWarningExpression,
+ SyntaxFactory.IdentifierName(castedDelegateName));
+ var callDeserialize = SyntaxFactory.InvocationExpression(suppressNullableCallDelegate)
+ .WithArgumentList(SyntaxFactory.ArgumentList(
+ SyntaxFactory.SeparatedList(new[]
+ {
+ SyntaxFactory.Argument(
+ SyntaxFactory.IdentifierName(readerName))
+ })));
+ var setDeserializedObject = Statement.Declaration.Assign(propName, callDeserialize);
+ defaultCaseStatement.AddRange(innerDeserialize.ToMergedBlock());
+ defaultCaseStatement.Add(assignment);
+ defaultCaseStatement.Add(castedDelegate);
+ defaultCaseStatement.Add(setDeserializedObject);
+ }
- switchSections.Add(SyntaxFactory.SwitchSection(
- SyntaxFactory.SingletonList(SyntaxFactory.DefaultSwitchLabel()),
- SyntaxFactory.SingletonList(ThrowNotSupportedException())));
+ if (ifSections.Count == 0)
+ {
+ statements.Statements.AddRange(defaultCaseStatement);
+ }
+ else
+ {
+ var currentElse = SyntaxFactory.ElseClause(SyntaxFactory.Block(defaultCaseStatement));
+ for (int i = ifSections.Count - 1; i >= 1; i--)
+ {
+ currentElse = SyntaxFactory.ElseClause(ifSections[i].WithElse(currentElse));
+ }
+ statements.Statements.Add(ifSections[0].WithElse(currentElse));
+ }
- statements.Statements.Add(SyntaxFactory.SwitchStatement(SyntaxFactory.IdentifierName(typeIdName))
- .WithSections(new SyntaxList(
- switchSections
- )));
return Continuation.Done;
}
}
diff --git a/NonSucking.Framework.Serialization/Serializers/MethodCallSerializer.cs b/NonSucking.Framework.Serialization/Serializers/MethodCallSerializer.cs
index 7eecf5e..dbce1d3 100644
--- a/NonSucking.Framework.Serialization/Serializers/MethodCallSerializer.cs
+++ b/NonSucking.Framework.Serialization/Serializers/MethodCallSerializer.cs
@@ -107,9 +107,11 @@ private static void CreateInvocationForOutDeserialize(MemberInfo property, Nooso
Initializer initializer = Initializer.InitializerList;
string name = property.CreateUniqueName();
- GeneratedSerializerCode ctorSyntax = CtorSerializer.CallCtorAndSetProps(
+ var ctorSyntax = CtorSerializer.CallCtorAndSetProps(
(INamedTypeSymbol)property.TypeSymbol,
- declerationNames, property, name, initializer);
+ declerationNames, property, name, initializer, context);
+ if (ctorSyntax is null)
+ return;
statements.MergeWith(ctorSyntax);
}
catch (NotSupportedException)
diff --git a/NonSucking.Framework.Serialization/Serializers/NullableSerializer.cs b/NonSucking.Framework.Serialization/Serializers/NullableSerializer.cs
index 6f7ac5d..e214601 100644
--- a/NonSucking.Framework.Serialization/Serializers/NullableSerializer.cs
+++ b/NonSucking.Framework.Serialization/Serializers/NullableSerializer.cs
@@ -60,13 +60,8 @@ internal static Continuation TrySerialize(ref MemberInfo property, NoosonGenerat
return Continuation.NotExecuted;
var nullLiteral = SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression);
- var needsTemp = property.Parent != "";
- var tempVariable = !needsTemp ? property : property with { Name = Helper.GetRandomNameFor(property.Name, property.Parent), Parent = "" };
- var tempVariableAccessorName = Helper.GetMemberAccessString(tempVariable);
+ var (tempVariable, tempVariableAccessorName) = Helper.CreateTempIfNeeded(property, statements);
var tempVariableIdentifier = SyntaxFactory.IdentifierName(tempVariableAccessorName);
- var setTempVariable = Statement.Declaration.DeclareAndAssign(
- tempVariableAccessorName,
- SyntaxFactory.IdentifierName(Helper.GetMemberAccessString(property)));
var isNotNullCheck = SyntaxFactory.IsPatternExpression(
tempVariableIdentifier,
@@ -81,8 +76,7 @@ internal static Continuation TrySerialize(ref MemberInfo property, NoosonGenerat
var writeNullable = Statement.Expression.Invoke(writerName, "Write",
new[] { new InvocationArgument(isNotNullCheck) });
- if (needsTemp)
- statements.Statements.Add(setTempVariable);
+
statements.Statements.Add(writeNullable.AsStatement());
statements.Statements.Add(SyntaxFactory.IfStatement(isNotNullCheck, b));
diff --git a/NonSucking.Framework.Serialization/Serializers/PublicPropertySerializer.cs b/NonSucking.Framework.Serialization/Serializers/PublicPropertySerializer.cs
index b518669..a005c85 100644
--- a/NonSucking.Framework.Serialization/Serializers/PublicPropertySerializer.cs
+++ b/NonSucking.Framework.Serialization/Serializers/PublicPropertySerializer.cs
@@ -173,9 +173,11 @@ internal static Continuation TryDeserialize(
Initializer initializer = context.MethodType == MethodType.DeserializeWithCtor ? Initializer.InitializerList : Initializer.Properties;
string name = context.MethodType == MethodType.DeserializeWithCtor ? property.CreateUniqueName() : Consts.InstanceParameterName;
- GeneratedSerializerCode ctorSyntax = CtorSerializer.CallCtorAndSetProps(
- (INamedTypeSymbol)property.TypeSymbol,
- declerationNames, property, name, initializer);
+ var ctorSyntax = CtorSerializer.CallCtorAndSetProps(
+ property.TypeSymbol,
+ declerationNames, property, name, initializer, context);
+ if (ctorSyntax is null)
+ return Continuation.Continue;
statements.MergeWith(ctorSyntax);
}
catch (NotSupportedException)
@@ -454,6 +456,8 @@ static Accessibility GetCommonAccessibility(ITypeSymbol typeSymbol)
foreach ((bool assign, GeneratedSerializerCode propCode) in propCodes)
{
+ if (propCode.VariableDeclarations.Count == 0)
+ continue;
GeneratedSerializerCode.SerializerVariable variable = propCode.VariableDeclarations[0];
string outVarName = variable.UniqueName; // + "_out";
if (!SymbolEqualityComparer.Default.Equals(typeSymbol, variable.OriginalMember.TypeSymbol))
diff --git a/NonSucking.Framework.Serialization/Serializers/TypeConverterSerializer.cs b/NonSucking.Framework.Serialization/Serializers/TypeConverterSerializer.cs
index d49a19a..ee5ee5e 100644
--- a/NonSucking.Framework.Serialization/Serializers/TypeConverterSerializer.cs
+++ b/NonSucking.Framework.Serialization/Serializers/TypeConverterSerializer.cs
@@ -57,12 +57,9 @@ internal static bool CheckPrerequisites(MemberInfo property, NoosonGeneratorCont
&& x.OriginalDefinition.ToDisplayString() == "NonSucking.Framework.Serialization.INoosonConverter<, >").ToArray();
if (genericConverterInterfaces.Length > 0)
{
- var instanceAccessor = Helper.GetFirstMemberWithBase(conversionType,
- (x) => x is IPropertySymbol or IFieldSymbol { Name: "Instance", IsStatic: true }
- && x.DeclaredAccessibility is Accessibility.Internal or Accessibility.Public or Accessibility.ProtectedOrInternal, context);
- if (instanceAccessor is null)
+ if (!Helper.CheckSingleton(context, conversionType))
{
- context.AddDiagnostic(Diagnostics.SingletonImplementationRequired, conversionType, DiagnosticSeverity.Error);
+ context.AddDiagnostic(Diagnostics.SingletonImplementationRequired.Format("type converters"), conversionType, DiagnosticSeverity.Error);
return false;
}
diff --git a/NonSucking.Framework.Serialization/Templates/AdditionalSource/NoosonRuntimeTypeResolver.cs b/NonSucking.Framework.Serialization/Templates/AdditionalSource/NoosonRuntimeTypeResolver.cs
new file mode 100644
index 0000000..3523ace
--- /dev/null
+++ b/NonSucking.Framework.Serialization/Templates/AdditionalSource/NoosonRuntimeTypeResolver.cs
@@ -0,0 +1,325 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Xml.Serialization;
+using System.Reflection;
+using System.Linq;
+using System.Linq.Expressions;
+
+namespace NonSucking.Framework.Serialization;
+
+internal class AssemblyNameCache
+{
+ internal struct Names
+ {
+ private readonly string? serializeName;
+ private readonly string? deserializeName;
+ const string defaultSerializeName = "Serialize";
+ const string defaultDeserializeName = "Deserialize";
+ public Names(string? serializeName, string? deserializeName)
+ {
+ this.serializeName = serializeName;
+ this.deserializeName = deserializeName;
+ }
+
+ public static Names Combine(Names a, Names b)
+ {
+ return new Names(a.serializeName ?? b.serializeName, a.deserializeName ?? b.deserializeName);
+ }
+
+ public readonly string SerializeName => serializeName ?? defaultSerializeName;
+
+ public readonly string DeserializeName => deserializeName ?? defaultDeserializeName;
+ }
+ private static readonly Dictionary AssemblyNameMap = new();
+
+ internal static T? FirstOrNull(IEnumerable enumerable, Func predicate)
+ where T : struct
+ {
+ foreach (var v in enumerable)
+ {
+ if (predicate(v))
+ return v;
+ }
+ return null;
+ }
+ private static Names GetAssemblyConfig(Assembly assembly)
+ {
+ var assemblyAttr = assembly.GetCustomAttributesData().FirstOrDefault(
+ x => x.AttributeType.FullName == "NonSucking.Framework.Serialization.NoosonConfigurationAttribute");
+ if (assemblyAttr is null)
+ return new Names(null, null);
+ var deserializeName = FirstOrNull(assemblyAttr.NamedArguments,
+ argument => argument.MemberName == "NameOfStaticDeserializeWithCtor")?.TypedValue.Value?.ToString();
+ var serializeName = FirstOrNull(assemblyAttr.NamedArguments,
+ argument => argument.MemberName == "NameOfSerialize")?.TypedValue.Value?.ToString();
+ return new Names(serializeName, deserializeName);
+ }
+ public static Names ResolveAssemblyConfig(Assembly assembly)
+ {
+#if NET6_0_OR_GREATER
+ ref var names = ref CollectionsMarshal.GetValueRefOrAddDefault(AssemblyNameMap, assembly, out var exists);
+ if (!exists)
+ {
+ names = GetAssemblyConfig(assembly);
+ }
+ return names;
+#else
+ if (!AssemblyNameMap.TryGetValue(assembly, out var names))
+ {
+ names = GetAssemblyConfig(assembly);
+ AssemblyNameMap.Add(assembly, names);
+ }
+ return names;
+#endif
+ }
+
+
+}
+internal class TypeNameCache
+{
+ internal struct TypeConfig
+ {
+ public TypeConfig(bool isCustomMethodCall, Type serializeType, Type deserializeType, AssemblyNameCache.Names names)
+ {
+ IsCustomMethodCall = isCustomMethodCall;
+ SerializeType = serializeType;
+ DeserializeType = deserializeType;
+ Names = names;
+ }
+
+ public bool IsCustomMethodCall { get; }
+ public Type SerializeType { get; }
+ public Type DeserializeType { get; }
+ public AssemblyNameCache.Names Names { get; }
+ }
+ public static TypeConfig GetTypeConfig(Type type)
+ {
+ var names = AssemblyNameCache.ResolveAssemblyConfig(type.Assembly);
+ // TODO: NoosonCustom attribute can be applied to properties
+ var typeAttr = type.GetCustomAttributesData().FirstOrDefault(
+ x => x.AttributeType.FullName == "NonSucking.Framework.Serialization.NoosonCustomAttribute");
+ if (typeAttr is null)
+ return new TypeConfig(false, type, type, names);
+ var deserializeName = AssemblyNameCache.FirstOrNull(typeAttr.NamedArguments,
+ argument => argument.MemberName == "DeserializeMethodName")?.TypedValue.Value?.ToString();
+ var serializeName = AssemblyNameCache.FirstOrNull(typeAttr.NamedArguments,
+ argument => argument.MemberName == "SerializeMethodName")?.TypedValue.Value?.ToString();
+ names = AssemblyNameCache.Names.Combine(names, new AssemblyNameCache.Names(serializeName, deserializeName));
+
+ var serializeType = AssemblyNameCache.FirstOrNull(typeAttr.NamedArguments,
+ argument => argument.MemberName == "SerializeImplementationType")?.TypedValue.Value as Type ?? type;
+ var deserializeType = AssemblyNameCache.FirstOrNull(typeAttr.NamedArguments,
+ argument => argument.MemberName == "DeserializeImplementationType")?.TypedValue.Value as Type ?? type;
+
+ return new TypeConfig(true, serializeType, deserializeType, names);
+ }
+}
+
+internal class MethodResolver
+{
+ private static Type[] MatchGenericArguments(MethodInfo x, ParameterInfo[] parameters, Type[] parameterTypes)
+ {
+ // TODO: cannot match generic parameters that are not used as parameter types
+ var genArguments = x.GetGenericArguments();
+ var matchedParameters = new Type[genArguments.Length];
+ for (var index = 0; index < genArguments.Length; index++)
+ {
+ var genArg = genArguments[index];
+ for (var pIndex = 0; pIndex < parameters.Length; pIndex++)
+ {
+ var genParam = parameters[pIndex];
+ if (genParam.ParameterType != genArg)
+ continue;
+ var previouslyMatched = matchedParameters[index];
+ var newlyMatched = parameterTypes[pIndex];
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (previouslyMatched is null || newlyMatched.IsAssignableFrom(previouslyMatched))
+ {
+ matchedParameters[index] = newlyMatched;
+ }
+ }
+ }
+
+ return matchedParameters;
+ }
+ internal static MethodInfo? GetBestMatch(Type type, string methodName, BindingFlags bindingFlags, Type[] parameterTypes, Type? returnType)
+ {
+ var methods = type.GetMethods(bindingFlags)
+ .Where(x => x.Name == methodName
+ && x.ReturnType == (returnType ?? typeof(void)))
+ .Select(x =>
+ {
+ try
+ {
+ var parameters = x.GetParameters();
+ if (parameters.Length != parameterTypes.Length)
+ return null;
+ if (x.IsGenericMethodDefinition)
+ {
+ var matched = MatchGenericArguments(x, parameters, parameterTypes);
+ return x.MakeGenericMethod(matched);
+ }
+ return x;
+ }
+ catch (ArgumentException)
+ {
+ return null;
+ }
+ }).Where(x => x is not null).OfType().ToArray();
+
+ var perfectMatch = methods.FirstOrDefault(x =>
+ {
+ if (x.IsGenericMethod)
+ return false;
+ var parameters = x.GetParameters();
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ if (parameters[i].ParameterType != parameterTypes[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ });
+ if (perfectMatch is not null)
+ return perfectMatch;
+ if (methods.Length == 0)
+ return null;
+ // ReSharper disable once CoVariantArrayConversion
+ return Type.DefaultBinder.SelectMethod(bindingFlags, methods, parameterTypes, null) as MethodInfo;
+ }
+}
+
+internal class SerializerInformationBase
+{
+ public SerializerInformationBase(TTypeIdentifier identifier, Type type)
+ {
+ Identifier = identifier;
+ Type = type;
+ }
+ public TTypeIdentifier Identifier { get; }
+ public Type Type { get; }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Identifier is null ? 0 : (EqualityComparer.Default.GetHashCode(Identifier) * 397)) ^ Type.GetHashCode();
+ }
+ }
+}
+
+internal class
+ SerializerInformation : SerializerInformationBase, IEquatable<
+ SerializerInformation>
+{
+ public SerializerInformation(TTypeIdentifier identifier, Type type)
+ : base(identifier, type)
+ {
+#if __NOOSON_ADVANCED_SERIALIZATION__
+ Serialize = NonSucking.Framework.Serialization.Advanced.SerializeGenerating.GenerateSerializeDelegate(type, false);
+#else
+ var typeConfig = TypeNameCache.GetTypeConfig(type);
+ var useStaticSerialize = typeConfig.SerializeType != type;
+ var serializerMethod = MethodResolver.GetBestMatch(typeConfig.SerializeType, typeConfig.Names.SerializeName,
+ BindingFlags.Public | BindingFlags.NonPublic |
+ (useStaticSerialize ? BindingFlags.Static : BindingFlags.Instance),
+ useStaticSerialize ? new[] { typeof(TWriter), type } : new[] { typeof(TWriter) }, null);
+
+ if (serializerMethod is not null)
+ {
+ var valueParam = Expression.Parameter(type);
+ var writerParam = Expression.Parameter(typeof(TWriter));
+ var methodCall = useStaticSerialize
+ ? Expression.Call(null, serializerMethod, writerParam, valueParam)
+ : Expression.Call(valueParam, serializerMethod, writerParam);
+ Serialize = Expression.Lambda(methodCall, writerParam, valueParam).Compile();
+ }
+ else
+ {
+ throw new NotSupportedException(
+ $"Serialization of {type.Name} is not supported. More complex types are only supported using \"NonSucking.Framework.Serialization.Advanced\"");
+ }
+#endif
+ }
+ public Delegate? Serialize { get; }
+
+ public bool Equals(SerializerInformation? other)
+ {
+ if (other is null) return false;
+ return Type == other.Type;
+ }
+
+ public override bool Equals(object? other)
+ {
+ return other is SerializerInformation serializerInformation
+ && Equals(serializerInformation);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (base.GetHashCode() * 397) ^ typeof(TWriter).GetHashCode();
+ }
+ }
+}
+
+internal class
+ DeserializerInformation : SerializerInformationBase, IEquatable<
+ DeserializerInformation>
+{
+ public DeserializerInformation(TTypeIdentifier identifier, Type type)
+ : base(identifier, type)
+ {
+#if __NOOSON_ADVANCED_SERIALIZATION__
+ Deserialize = NonSucking.Framework.Serialization.Advanced.DeserializeGenerating.GenerateDeserializeDelegate(type, false);
+#else
+ var typeConfig = TypeNameCache.GetTypeConfig(type);
+ var useStaticSerialize = typeConfig.SerializeType != type;
+ var deserializerMethod = MethodResolver.GetBestMatch(typeConfig.DeserializeType,
+ typeConfig.Names.DeserializeName,
+ BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
+ new[] { typeof(TReader) }, type);
+
+
+ if (deserializerMethod is not null)
+ {
+ var readerParam = Expression.Parameter(typeof(TReader));
+
+ Deserialize = Expression.Lambda(Expression.Call(null, deserializerMethod, readerParam), readerParam)
+ .Compile();
+ }
+ else
+ {
+ throw new NotSupportedException(
+ $"Deserialization of {type.Name} is not supported. More complex types are only supported using \"NonSucking.Framework.Serialization.Advanced\"");
+ }
+#endif
+ }
+ public Delegate? Deserialize { get; }
+
+ public bool Equals(DeserializerInformation? other)
+ {
+ if (other is null) return false;
+ return Type == other.Type;
+ }
+
+ public override bool Equals(object? other)
+ {
+ return other is DeserializerInformation serializerInformation
+ && Equals(serializerInformation);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (base.GetHashCode() * 397) ^ typeof(TReader).GetHashCode();
+ }
+ }
+}
diff --git a/NonSucking.Framework.Serialization/Templates/Attribute/NoosonDynamicTypeAttribute.cs b/NonSucking.Framework.Serialization/Templates/Attribute/NoosonDynamicTypeAttribute.cs
index ab1e2a0..a90536c 100644
--- a/NonSucking.Framework.Serialization/Templates/Attribute/NoosonDynamicTypeAttribute.cs
+++ b/NonSucking.Framework.Serialization/Templates/Attribute/NoosonDynamicTypeAttribute.cs
@@ -1,5 +1,9 @@
-using System;
+#nullable enable
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Collections.Generic;
namespace NonSucking.Framework.Serialization
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
@@ -10,6 +14,58 @@ public NoosonDynamicTypeAttribute(params Type[] possibleTypes)
PossibleTypes = possibleTypes;
}
public Type[] PossibleTypes { get; }
+ public Type? Resolver { get; set; }
+ }
+
+ internal interface INoosonRuntimeTypeResolver
+ {
+ SerializerInformation Solve(Type type);
+ DeserializerInformation Resolve(TTypeIdentifier identifier);
+ }
+ internal abstract class NoosonRuntimeTypeResolver
+ : INoosonRuntimeTypeResolver where TTypeIdentifier : notnull
+ {
+ private readonly Dictionary typeMap = new Dictionary();
+ private readonly Dictionary identifierMap = new Dictionary();
+
+ protected abstract Type ResolveType(TTypeIdentifier identifier);
+ protected abstract TTypeIdentifier SolveType(Type type);
+
+ DeserializerInformation INoosonRuntimeTypeResolver.Resolve(TTypeIdentifier identifier)
+ {
+ #if NET6_0_OR_GREATER
+ ref var item = ref CollectionsMarshal.GetValueRefOrAddDefault(typeMap, identifier, out var exists);
+ if (!exists)
+ {
+ item = new DeserializerInformation(identifier, ResolveType(identifier));
+ }
+ #else
+ if (!typeMap.TryGetValue(identifier, out var item))
+ {
+ item = new DeserializerInformation(identifier, ResolveType(identifier));
+ typeMap.Add(identifier, item);
+ }
+ #endif
+ return Unsafe.As>(item!);
+ }
+
+ SerializerInformation INoosonRuntimeTypeResolver.Solve(Type type)
+ {
+ #if NET6_0_OR_GREATER
+ ref var item = ref CollectionsMarshal.GetValueRefOrAddDefault(identifierMap, type, out var exists);
+ if (!exists)
+ {
+ item = new SerializerInformation(SolveType(type), type);
+ }
+ #else
+ if (!identifierMap.TryGetValue(type, out var item))
+ {
+ item = new SerializerInformation(SolveType(type), type);
+ identifierMap.Add(type, item);
+ }
+ #endif
+ return Unsafe.As>(item!);
+ }
}
}
diff --git a/NonSucking.Framework.Serialization/Templates/NoosonRuntimeTypeResolverTemplate.cs b/NonSucking.Framework.Serialization/Templates/NoosonRuntimeTypeResolverTemplate.cs
new file mode 100644
index 0000000..e96354f
--- /dev/null
+++ b/NonSucking.Framework.Serialization/Templates/NoosonRuntimeTypeResolverTemplate.cs
@@ -0,0 +1,12 @@
+using NonSucking.Framework.Serialization.Attributes;
+using NonSucking.Framework.Serialization.Templates;
+
+namespace NonSucking.Framework.Serialization.AdditionalSource
+{
+ public class NoosonRuntimeTypeResolverTemplate : Template
+ {
+ public override string Namespace { get; } = "NonSucking.Framework.Serialization";
+ public override string Name { get; } = "NoosonRuntimeTypeResolver";
+ public override TemplateKind Kind { get; } = TemplateKind.AdditionalSource;
+ }
+}
\ No newline at end of file
diff --git a/NonSucking.Framework.Serialization/Templates/Template.cs b/NonSucking.Framework.Serialization/Templates/Template.cs
index 3600767..493757a 100644
--- a/NonSucking.Framework.Serialization/Templates/Template.cs
+++ b/NonSucking.Framework.Serialization/Templates/Template.cs
@@ -27,6 +27,12 @@ public Template()
text = reader.ReadToEnd();
}
+ public virtual bool CheckConditionalInclude(GlobalContext context)
+ {
+ var typeSymbol = context.Compilation.GetTypeByMetadataName("NonSucking.Framework.Serialization.Advanced.Generating`2");
+ return true;
+ }
+
public override string ToString()
=> text;
}