diff --git a/Apex.Serialization/Apex.Serialization.csproj b/Apex.Serialization/Apex.Serialization.csproj index b8e00bc..3160533 100644 --- a/Apex.Serialization/Apex.Serialization.csproj +++ b/Apex.Serialization/Apex.Serialization.csproj @@ -1,8 +1,8 @@  - net6 - 4.0.5 + net8 + 5.0.0 Dominic Bolin A high performance contract-less binary serializer https://github.com/dbolin/Apex.Serialization @@ -30,7 +30,7 @@ - + @@ -43,8 +43,8 @@ - - + + diff --git a/Apex.Serialization/Internal/DynamicCode.Helpers.cs b/Apex.Serialization/Internal/DynamicCode.Helpers.cs index 1b4e1fd..c59090d 100644 --- a/Apex.Serialization/Internal/DynamicCode.Helpers.cs +++ b/Apex.Serialization/Internal/DynamicCode.Helpers.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; @@ -14,7 +15,7 @@ internal static partial class DynamicCode where TStream : IBinaryStream where TBinary : ISerializer { - internal static MethodInfo GetUnitializedObjectMethodInfo = typeof(FormatterServices).GetMethod("GetUninitializedObject")!; + internal static MethodInfo GetUnitializedObjectMethodInfo = typeof(RuntimeHelpers).GetMethod("GetUninitializedObject")!; private static MethodInfo fieldInfoSetValueMethod = typeof(FieldInfo).GetMethod("SetValue", new[] { typeof(object), typeof(object) })!; private static Expression ReserveConstantSize(ParameterExpression stream, int size) diff --git a/Apex.Serialization/Internal/DynamicCode.cs b/Apex.Serialization/Internal/DynamicCode.cs index 57c50d0..4ed0ca2 100644 --- a/Apex.Serialization/Internal/DynamicCode.cs +++ b/Apex.Serialization/Internal/DynamicCode.cs @@ -936,7 +936,16 @@ private static Expression AfterDeserializeCallExpression(Type type, MethodInfo m var custom = HandleCustomRead(type, output, stream, result, settings, readMetadata); if (custom != null) { - created = false; + created = true; + if (!type.IsValueType && settings.SerializationMode == Mode.Graph) + { + return Expression.Block( + custom, + Expression.Call(Expression.Call(output, SavedReferencesGetter), + SavedReferencesListAdd, result), + result + ); + } return custom; } @@ -976,22 +985,26 @@ private static Expression AfterDeserializeCallExpression(Type type, MethodInfo m if (customContextType != null) { var customContext = Expression.Call(output, CustomContextGetter.MakeGenericMethod(customContextType)); - customReadStatements.Add(Expression.Call( + customReadStatements.Add( + Expression.Assign(result, + Expression.Call( Expression.Convert( Expression.Constant(entry.Value.Action), - typeof(Action<,,>).MakeGenericType(type, typeof(IBinaryReader), customContextType)), - entry.Value.InvokeMethodInfo, result, + typeof(Func<,,>).MakeGenericType(typeof(IBinaryReader), customContextType, type)), + entry.Value.InvokeMethodInfo, Expression.Call(output, BinaryReaderGetter), - customContext)); + customContext))); } else { - customReadStatements.Add(Expression.Call( + customReadStatements.Add( + Expression.Assign(result, + Expression.Call( Expression.Convert( Expression.Constant(entry.Value.Action), - typeof(Action<,>).MakeGenericType(type, typeof(IBinaryReader))), - entry.Value.InvokeMethodInfo, result, - Expression.Call(output, BinaryReaderGetter))); + typeof(Func<,>).MakeGenericType(typeof(IBinaryReader), type)), + entry.Value.InvokeMethodInfo, + Expression.Call(output, BinaryReaderGetter)))); } } } diff --git a/Apex.Serialization/Internal/Reflection/FieldInfoModifier.cs b/Apex.Serialization/Internal/Reflection/FieldInfoModifier.cs index 63c4563..753f77f 100644 --- a/Apex.Serialization/Internal/Reflection/FieldInfoModifier.cs +++ b/Apex.Serialization/Internal/Reflection/FieldInfoModifier.cs @@ -34,31 +34,14 @@ static FieldInfoModifier() var fieldInfo_m_Attributes = type?.GetField("m_fieldAttributes", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (fieldInfo_m_Attributes != null) { - var fieldInfoParam = Expression.Parameter(typeof(FieldInfo)); - var castedType = Expression.Convert(fieldInfoParam, type!); - var returnLabel = Expression.Label(); - SetFieldInfoNotReadonly = (Action)Expression.Lambda( - Expression.Block( - Expression.Assign(Expression.MakeMemberAccess(castedType, fieldInfo_m_Attributes), - Expression.Convert(Expression.And(Expression.Convert(Expression.MakeMemberAccess(castedType, fieldInfo_m_Attributes), typeof(int)), Expression.Constant((int)(~FieldAttributes.InitOnly))) - ,typeof(FieldAttributes)) - ) - , Expression.Return(returnLabel), - Expression.Label(returnLabel) - ) - , fieldInfoParam - ).Compile(); - SetFieldInfoReadonly = (Action)Expression.Lambda( - Expression.Block( - Expression.Assign(Expression.MakeMemberAccess(castedType, fieldInfo_m_Attributes), - Expression.Convert(Expression.Or(Expression.Convert(Expression.MakeMemberAccess(castedType, fieldInfo_m_Attributes), typeof(int)), Expression.Constant((int)(FieldAttributes.InitOnly))) - , typeof(FieldAttributes)) - ) - , Expression.Return(returnLabel), - Expression.Label(returnLabel) - ) - , fieldInfoParam - ).Compile(); + SetFieldInfoNotReadonly = f => + { + fieldInfo_m_Attributes.SetValue(f, ((FieldAttributes)fieldInfo_m_Attributes.GetValue(f)!) & ~FieldAttributes.InitOnly); + }; + SetFieldInfoReadonly = f => + { + fieldInfo_m_Attributes.SetValue(f, ((FieldAttributes)fieldInfo_m_Attributes.GetValue(f)!) | FieldAttributes.InitOnly); + }; var s = Binary.Create(new Settings { UseSerializedVersionId = false }); try diff --git a/Apex.Serialization/Settings.cs b/Apex.Serialization/Settings.cs index 91ec90f..966b1ff 100644 --- a/Apex.Serialization/Settings.cs +++ b/Apex.Serialization/Settings.cs @@ -46,7 +46,8 @@ public sealed class Settings /// /// Type to which the custom serialization will apply. Does not support primitives. /// Method to be called when a type matching T is to be serialized. - public Settings RegisterCustomSerializer(Action writeMethod, Action readMethod) + /// Method to be called when a type matching T is to be deserialized. + public Settings RegisterCustomSerializer(Action writeMethod, Func readMethod) { CustomActionSerializers.Add(typeof(T), new CustomSerializerDelegate( writeMethod, @@ -55,7 +56,7 @@ public Settings RegisterCustomSerializer(Action writeMethod )); CustomActionDeserializers.Add(typeof(T), new CustomSerializerDelegate( readMethod, - typeof(Action).GetMethod("Invoke")!, + typeof(Func).GetMethod("Invoke")!, null)); return this; } @@ -66,7 +67,8 @@ public Settings RegisterCustomSerializer(Action writeMethod /// Type to which the custom serialization will apply. Does not support primitives. /// Type of custom serialization context. Will be null if the current context is not set or cannot be cast to this type. /// Method to be called when a type matching T is to be serialized. - public Settings RegisterCustomSerializer(Action writeMethod, Action readMethod) + /// Method to be called when a type matching T is to be deserialized. + public Settings RegisterCustomSerializer(Action writeMethod, Func readMethod) where TContext : class { CustomActionSerializers.Add(typeof(T), new CustomSerializerDelegate( @@ -76,7 +78,7 @@ public Settings RegisterCustomSerializer(Action).GetMethod("Invoke")!, + typeof(Func).GetMethod("Invoke")!, typeof(TContext) )); return this; diff --git a/Benchmark/Benchmark.csproj b/Benchmark/Benchmark.csproj index 146d417..3bfb51a 100644 --- a/Benchmark/Benchmark.csproj +++ b/Benchmark/Benchmark.csproj @@ -2,7 +2,7 @@ Exe - net6 + net8 true Benchmark.snk 8.0 @@ -12,11 +12,11 @@ - - + + - + diff --git a/Benchmark/Program.cs b/Benchmark/Program.cs index b248fef..a8c6bd5 100644 --- a/Benchmark/Program.cs +++ b/Benchmark/Program.cs @@ -26,7 +26,7 @@ public Config() AddExporter(DefaultConfig.Instance.GetExporters().ToArray()); // manual config has no exporters by default AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default - AddJob(Job.Default.WithToolchain(CsProjCoreToolchain.NetCoreApp60).WithGcServer(true)); + AddJob(Job.Default.WithToolchain(CsProjCoreToolchain.NetCoreApp80).WithGcServer(true)); //AddJob(Job.Clr.With(CsProjClassicNetToolchain.Net472)); //AddJob(Job.CoreRT); //Add(HardwareCounter.BranchMispredictions, HardwareCounter.BranchInstructions); diff --git a/DeserializeTest/DeserializeTest.csproj b/DeserializeTest/DeserializeTest.csproj index c54222c..b2650a8 100644 --- a/DeserializeTest/DeserializeTest.csproj +++ b/DeserializeTest/DeserializeTest.csproj @@ -2,7 +2,7 @@ Exe - net6 + net8 diff --git a/DeserializeTest2/DeserializeTest2.csproj b/DeserializeTest2/DeserializeTest2.csproj index c54222c..b2650a8 100644 --- a/DeserializeTest2/DeserializeTest2.csproj +++ b/DeserializeTest2/DeserializeTest2.csproj @@ -2,7 +2,7 @@ Exe - net6 + net8 diff --git a/Tests/Apex.Serialization.Tests/Apex.Serialization.Tests.csproj b/Tests/Apex.Serialization.Tests/Apex.Serialization.Tests.csproj index 4b0d0cc..958dc86 100644 --- a/Tests/Apex.Serialization.Tests/Apex.Serialization.Tests.csproj +++ b/Tests/Apex.Serialization.Tests/Apex.Serialization.Tests.csproj @@ -1,7 +1,7 @@  - net6 + net8 false 8.0 true @@ -13,16 +13,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - + + + + all runtime; build; native; contentfiles; analyzers diff --git a/Tests/Apex.Serialization.Tests/ConstructedSettingsTests.cs b/Tests/Apex.Serialization.Tests/ConstructedSettingsTests.cs index 14c9cf4..d669ea0 100644 --- a/Tests/Apex.Serialization.Tests/ConstructedSettingsTests.cs +++ b/Tests/Apex.Serialization.Tests/ConstructedSettingsTests.cs @@ -31,9 +31,9 @@ public static void Serialize(Test t, IBinaryWriter writer) writer.Write(t.Value - 1); } - public static void Deserialize(Test t, IBinaryReader reader) + public static Test Deserialize(IBinaryReader reader) { - t.Value = reader.Read(); + return new Test { Value = reader.Read() }; } } diff --git a/Tests/Apex.Serialization.Tests/ConstructorTests.cs b/Tests/Apex.Serialization.Tests/ConstructorTests.cs index cea48e3..58e5518 100644 --- a/Tests/Apex.Serialization.Tests/ConstructorTests.cs +++ b/Tests/Apex.Serialization.Tests/ConstructorTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Runtime.Serialization; +using System.Runtime.CompilerServices; using System.Text; using FluentAssertions; using Xunit; @@ -239,7 +240,7 @@ public void Throw() [Fact] public void ConstructorThatThrows() { - var x = (ThrowsOnConstruct) FormatterServices.GetUninitializedObject(typeof(ThrowsOnConstruct)); + var x = (ThrowsOnConstruct) RuntimeHelpers.GetUninitializedObject(typeof(ThrowsOnConstruct)); x.A = 3; RoundTrip(x); @@ -259,7 +260,7 @@ public ConstructorSettingStaticField(int a) [Fact] public void ConstructorThatSetsStaticField() { - var x = (ConstructorSettingStaticField)FormatterServices.GetUninitializedObject(typeof(ConstructorSettingStaticField)); + var x = (ConstructorSettingStaticField) RuntimeHelpers.GetUninitializedObject(typeof(ConstructorSettingStaticField)); x.A = 3; RoundTrip(x, (x, y) => { ConstructorSettingStaticField.B.Should().Be(0); y.A.Should().Be(3); }); @@ -286,7 +287,7 @@ private void Set(int a) [Fact] public void ConstructorThatSetsStaticFieldIndirect() { - var x = (ConstructorSettingStaticFieldIndirect)FormatterServices.GetUninitializedObject(typeof(ConstructorSettingStaticFieldIndirect)); + var x = (ConstructorSettingStaticFieldIndirect) RuntimeHelpers.GetUninitializedObject(typeof(ConstructorSettingStaticFieldIndirect)); x.A = 3; RoundTrip(x, (x, y) => { ConstructorSettingStaticFieldIndirect.B.Should().Be(0); y.A.Should().Be(3); }); @@ -315,7 +316,7 @@ public DerivedClassWithBaseConstructor(int a) : base() [Fact] public void TestDerivedClassWithBaseConstructorSideEffects() { - var x = (DerivedClassWithBaseConstructor)FormatterServices.GetUninitializedObject(typeof(DerivedClassWithBaseConstructor)); + var x = (DerivedClassWithBaseConstructor) RuntimeHelpers.GetUninitializedObject(typeof(DerivedClassWithBaseConstructor)); x.A = 3; RoundTrip(x); diff --git a/Tests/Apex.Serialization.Tests/CustomSerializationTests.cs b/Tests/Apex.Serialization.Tests/CustomSerializationTests.cs index 04d023c..05e52e3 100644 --- a/Tests/Apex.Serialization.Tests/CustomSerializationTests.cs +++ b/Tests/Apex.Serialization.Tests/CustomSerializationTests.cs @@ -28,14 +28,17 @@ public static void Serialize(Test t, IBinaryWriter writer) } } - public static void Deserialize(Test t, IBinaryReader reader) + public static Test Deserialize(IBinaryReader reader) { + var t = new Test(); t.Value = reader.Read(); var isNotNullByte = reader.Read(); if (isNotNullByte == 1) { t.Nested = reader.ReadObject(); } + + return t; } } @@ -53,9 +56,9 @@ public static void Serialize(TestCustomContext t, IBinaryWriter writer, CustomCo writer.Write(context.ValueOverride); } - public static void Deserialize(TestCustomContext t, IBinaryReader reader, CustomContext context) + public static TestCustomContext Deserialize(IBinaryReader reader, CustomContext context) { - t.Value = context.ValueOverride; + return new TestCustomContext { Value = context.ValueOverride }; } } @@ -76,14 +79,16 @@ public CustomSerializationTests() { s.WriteObject(i); } - }, (o, s) => + }, (s) => { + var o = new HashSet(); var count = s.Read(); o.EnsureCapacity(count); for (int i = 0; i < count; ++i) { o.Add(s.ReadObject()); } + return o; }); }; } @@ -170,10 +175,9 @@ public static void Serialize(TestWithConstructor t, IBinaryWriter writer) writer.Write(t.Value - 1); } - public static void Deserialize(TestWithConstructor t, IBinaryReader reader) + public static TestWithConstructor Deserialize(IBinaryReader reader) { - t.Value.Should().Be(0); - t.Value = reader.Read(); + return new TestWithConstructor(reader.Read()); } } diff --git a/Tests/Apex.Serialization.Tests/stryker-config.json b/Tests/Apex.Serialization.Tests/stryker-config.json index dd29615..9c2f250 100644 --- a/Tests/Apex.Serialization.Tests/stryker-config.json +++ b/Tests/Apex.Serialization.Tests/stryker-config.json @@ -1,15 +1,14 @@ { "stryker-config": { - "test-runner": "vstest", "reporters": [ "progress", "html" ], - "exluded-mutations": ["string"], - "log-level": "info", - "timeout-ms": 25000, - "threshold-high": 100, - "threshold-low": 100, - "threshold-break": 99 + "ignore-mutations": [ "string" ], + "thresholds": { + "high": 100, + "low": 100, + "break": 99 + } } } \ No newline at end of file